summaryrefslogtreecommitdiffstats
path: root/framework/source/services
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 09:06:44 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 09:06:44 +0000
commited5640d8b587fbcfed7dd7967f3de04b37a76f26 (patch)
tree7a5f7c6c9d02226d7471cb3cc8fbbf631b415303 /framework/source/services
parentInitial commit. (diff)
downloadlibreoffice-ed5640d8b587fbcfed7dd7967f3de04b37a76f26.tar.xz
libreoffice-ed5640d8b587fbcfed7dd7967f3de04b37a76f26.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 '')
-rw-r--r--framework/source/services/ContextChangeEventMultiplexer.cxx366
-rw-r--r--framework/source/services/autorecovery.cxx4243
-rw-r--r--framework/source/services/desktop.cxx1775
-rw-r--r--framework/source/services/dispatchhelper.cxx218
-rw-r--r--framework/source/services/frame.cxx3340
-rw-r--r--framework/source/services/mediatypedetectionhelper.cxx92
-rw-r--r--framework/source/services/modulemanager.cxx350
-rw-r--r--framework/source/services/pathsettings.cxx1422
-rw-r--r--framework/source/services/sessionlistener.cxx413
-rw-r--r--framework/source/services/substitutepathvars.cxx695
-rw-r--r--framework/source/services/taskcreatorsrv.cxx357
-rw-r--r--framework/source/services/uriabbreviation.cxx75
-rw-r--r--framework/source/services/urltransformer.cxx310
13 files changed, 13656 insertions, 0 deletions
diff --git a/framework/source/services/ContextChangeEventMultiplexer.cxx b/framework/source/services/ContextChangeEventMultiplexer.cxx
new file mode 100644
index 000000000..b2f6db457
--- /dev/null
+++ b/framework/source/services/ContextChangeEventMultiplexer.cxx
@@ -0,0 +1,366 @@
+/* -*- 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 <helper/mischelper.hxx>
+#include <com/sun/star/lang/IllegalArgumentException.hpp>
+#include <com/sun/star/lang/XComponent.hpp>
+#include <com/sun/star/lang/XEventListener.hpp>
+#include <com/sun/star/lang/XServiceInfo.hpp>
+#include <com/sun/star/ui/XContextChangeEventMultiplexer.hpp>
+#include <com/sun/star/ui/ContextChangeEventMultiplexer.hpp>
+#include <com/sun/star/uno/XComponentContext.hpp>
+
+#include <comphelper/compbase.hxx>
+#include <cppuhelper/supportsservice.hxx>
+#include <rtl/ref.hxx>
+#include <osl/diagnose.h>
+
+#include <algorithm>
+#include <map>
+#include <vector>
+
+using namespace css;
+using namespace css::uno;
+
+namespace {
+
+typedef comphelper::WeakComponentImplHelper <
+ css::ui::XContextChangeEventMultiplexer,
+ css::lang::XServiceInfo,
+ css::lang::XEventListener
+ > ContextChangeEventMultiplexerInterfaceBase;
+
+class ContextChangeEventMultiplexer
+ : public ContextChangeEventMultiplexerInterfaceBase
+{
+public:
+ ContextChangeEventMultiplexer();
+ ContextChangeEventMultiplexer(const ContextChangeEventMultiplexer&) = delete;
+ ContextChangeEventMultiplexer& operator=(const ContextChangeEventMultiplexer&) = delete;
+
+ virtual void disposing(std::unique_lock<std::mutex>&) override;
+
+ // XContextChangeEventMultiplexer
+ virtual void SAL_CALL addContextChangeEventListener (
+ const css::uno::Reference<css::ui::XContextChangeEventListener>& rxListener,
+ const css::uno::Reference<css::uno::XInterface>& rxEventFocus) override;
+ virtual void SAL_CALL removeContextChangeEventListener (
+ const css::uno::Reference<css::ui::XContextChangeEventListener>& rxListener,
+ const css::uno::Reference<css::uno::XInterface>& rxEventFocus) override;
+ virtual void SAL_CALL removeAllContextChangeEventListeners (
+ const css::uno::Reference<css::ui::XContextChangeEventListener>& rxListener) override;
+ virtual void SAL_CALL broadcastContextChangeEvent (
+ const css::ui::ContextChangeEventObject& rContextChangeEventObject,
+ const css::uno::Reference<css::uno::XInterface>& rxEventFocus) override;
+
+ // XServiceInfo
+ virtual OUString SAL_CALL getImplementationName() override;
+ virtual sal_Bool SAL_CALL supportsService (
+ const OUString& rsServiceName) override;
+ virtual css::uno::Sequence< OUString> SAL_CALL getSupportedServiceNames() override;
+
+ // XEventListener
+ virtual void SAL_CALL disposing (
+ const css::lang::EventObject& rEvent) override;
+
+ typedef ::std::vector<css::uno::Reference<css::ui::XContextChangeEventListener> > ListenerContainer;
+ class FocusDescriptor
+ {
+ public:
+ ListenerContainer maListeners;
+ OUString msCurrentApplicationName;
+ OUString msCurrentContextName;
+ };
+ typedef ::std::map<css::uno::Reference<css::uno::XInterface>, FocusDescriptor> ListenerMap;
+ ListenerMap maListeners;
+
+ /** Notify all listeners in the container that is associated with
+ the given event focus.
+
+ Typically called twice from broadcastEvent(), once for the
+ given event focus and once for NULL.
+ */
+ void BroadcastEventToSingleContainer (
+ const css::ui::ContextChangeEventObject& rEventObject,
+ const css::uno::Reference<css::uno::XInterface>& rxEventFocus);
+ FocusDescriptor* GetFocusDescriptor (
+ const css::uno::Reference<css::uno::XInterface>& rxEventFocus,
+ const bool bCreateWhenMissing);
+};
+
+ContextChangeEventMultiplexer::ContextChangeEventMultiplexer()
+{
+}
+
+void ContextChangeEventMultiplexer::disposing(std::unique_lock<std::mutex>& rGuard)
+{
+ ListenerMap aListeners;
+ aListeners.swap(maListeners);
+
+ rGuard.unlock();
+
+ css::uno::Reference<css::uno::XInterface> xThis (static_cast<XWeak*>(this));
+ css::lang::EventObject aEvent (xThis);
+ for (auto const& container : aListeners)
+ {
+ // Unregister from the focus object.
+ Reference<lang::XComponent> xComponent (container.first, UNO_QUERY);
+ if (xComponent.is())
+ xComponent->removeEventListener(this);
+
+ // Tell all listeners that we are being disposed.
+ const FocusDescriptor& rFocusDescriptor (container.second);
+ for (auto const& listener : rFocusDescriptor.maListeners)
+ {
+ listener->disposing(aEvent);
+ }
+ }
+}
+
+// XContextChangeEventMultiplexer
+void SAL_CALL ContextChangeEventMultiplexer::addContextChangeEventListener (
+ const css::uno::Reference<css::ui::XContextChangeEventListener>& rxListener,
+ const css::uno::Reference<css::uno::XInterface>& rxEventFocus)
+{
+ if ( ! rxListener.is())
+ throw css::lang::IllegalArgumentException(
+ "can not add an empty reference",
+ static_cast<XWeak*>(this),
+ 0);
+
+ FocusDescriptor* pFocusDescriptor = GetFocusDescriptor(rxEventFocus, true);
+ if (pFocusDescriptor != nullptr)
+ {
+ ListenerContainer& rContainer (pFocusDescriptor->maListeners);
+ if (::std::find(rContainer.begin(), rContainer.end(), rxListener) != rContainer.end())
+ {
+ // The listener was added for the same event focus
+ // previously. That is an error.
+ throw css::lang::IllegalArgumentException("listener added twice", static_cast<XWeak*>(this), 0);
+ }
+ rContainer.push_back(rxListener);
+ }
+
+ // Send out an initial event that informs the new listener about
+ // the current context.
+ if (!(rxEventFocus.is() && pFocusDescriptor!=nullptr))
+ return;
+
+ css::ui::ContextChangeEventObject aEvent (
+ nullptr,
+ pFocusDescriptor->msCurrentApplicationName,
+ pFocusDescriptor->msCurrentContextName);
+ rxListener->notifyContextChangeEvent(aEvent);
+}
+
+void SAL_CALL ContextChangeEventMultiplexer::removeContextChangeEventListener (
+ const css::uno::Reference<css::ui::XContextChangeEventListener>& rxListener,
+ const css::uno::Reference<css::uno::XInterface>& rxEventFocus)
+{
+ if ( ! rxListener.is())
+ throw css::lang::IllegalArgumentException(
+ "can not remove an empty reference",
+ static_cast<XWeak*>(this), 0);
+
+ FocusDescriptor* pFocusDescriptor = GetFocusDescriptor(rxEventFocus, false);
+ if (pFocusDescriptor == nullptr)
+ return;
+
+ ListenerContainer& rContainer (pFocusDescriptor->maListeners);
+ const ListenerContainer::iterator iListener (
+ ::std::find(rContainer.begin(), rContainer.end(), rxListener));
+ if (iListener != rContainer.end())
+ {
+ rContainer.erase(iListener);
+
+ // We hold on to the focus descriptor even when the last listener has been removed.
+ // This allows us to keep track of the current context and send it to new listeners.
+ }
+
+}
+
+void SAL_CALL ContextChangeEventMultiplexer::removeAllContextChangeEventListeners (
+ const css::uno::Reference<css::ui::XContextChangeEventListener>& rxListener)
+{
+ if ( ! rxListener.is())
+ throw css::lang::IllegalArgumentException(
+ "can not remove an empty reference",
+ static_cast<XWeak*>(this), 0);
+
+ for (auto& rContainer : maListeners)
+ {
+ const ListenerContainer::iterator iListener (
+ ::std::find(rContainer.second.maListeners.begin(), rContainer.second.maListeners.end(), rxListener));
+ if (iListener != rContainer.second.maListeners.end())
+ {
+ rContainer.second.maListeners.erase(iListener);
+
+ // We hold on to the focus descriptor even when the last listener has been removed.
+ // This allows us to keep track of the current context and send it to new listeners.
+ }
+ }
+}
+
+void SAL_CALL ContextChangeEventMultiplexer::broadcastContextChangeEvent (
+ const css::ui::ContextChangeEventObject& rEventObject,
+ const css::uno::Reference<css::uno::XInterface>& rxEventFocus)
+{
+ // Remember the current context.
+ if (rxEventFocus.is())
+ {
+ FocusDescriptor* pFocusDescriptor = GetFocusDescriptor(rxEventFocus, true);
+ if (pFocusDescriptor != nullptr)
+ {
+ pFocusDescriptor->msCurrentApplicationName = rEventObject.ApplicationName;
+ pFocusDescriptor->msCurrentContextName = rEventObject.ContextName;
+ }
+ }
+
+ BroadcastEventToSingleContainer(rEventObject, rxEventFocus);
+ if (rxEventFocus.is())
+ BroadcastEventToSingleContainer(rEventObject, nullptr);
+}
+
+void ContextChangeEventMultiplexer::BroadcastEventToSingleContainer (
+ const css::ui::ContextChangeEventObject& rEventObject,
+ const css::uno::Reference<css::uno::XInterface>& rxEventFocus)
+{
+ FocusDescriptor* pFocusDescriptor = GetFocusDescriptor(rxEventFocus, false);
+ if (pFocusDescriptor != nullptr)
+ {
+ // Create a copy of the listener container to avoid problems
+ // when one of the called listeners calls add... or remove...
+ ListenerContainer aContainer (pFocusDescriptor->maListeners);
+ for (auto const& listener : aContainer)
+ {
+ listener->notifyContextChangeEvent(rEventObject);
+ }
+ }
+}
+
+ContextChangeEventMultiplexer::FocusDescriptor* ContextChangeEventMultiplexer::GetFocusDescriptor (
+ const css::uno::Reference<css::uno::XInterface>& rxEventFocus,
+ const bool bCreateWhenMissing)
+{
+ ListenerMap::iterator iDescriptor (maListeners.find(rxEventFocus));
+ if (iDescriptor == maListeners.end() && bCreateWhenMissing)
+ {
+ // Listen for the focus being disposed.
+ Reference<lang::XComponent> xComponent (rxEventFocus, UNO_QUERY);
+ if (xComponent.is())
+ xComponent->addEventListener(this);
+
+ // Create a new listener container for the event focus.
+ iDescriptor = maListeners.emplace(
+ rxEventFocus,
+ FocusDescriptor()).first;
+ }
+ if (iDescriptor != maListeners.end())
+ return &iDescriptor->second;
+ else
+ return nullptr;
+}
+
+OUString SAL_CALL ContextChangeEventMultiplexer::getImplementationName()
+{
+ return "org.apache.openoffice.comp.framework.ContextChangeEventMultiplexer";
+}
+
+sal_Bool SAL_CALL ContextChangeEventMultiplexer::supportsService ( const OUString& rsServiceName)
+{
+ return cppu::supportsService(this, rsServiceName);
+}
+
+css::uno::Sequence<OUString> SAL_CALL ContextChangeEventMultiplexer::getSupportedServiceNames()
+{
+ // it's a singleton, not a service
+ return css::uno::Sequence<OUString>();
+}
+
+void SAL_CALL ContextChangeEventMultiplexer::disposing ( const css::lang::EventObject& rEvent)
+{
+ ListenerMap::iterator iDescriptor (maListeners.find(rEvent.Source));
+
+ if (iDescriptor == maListeners.end())
+ {
+ OSL_ASSERT(iDescriptor != maListeners.end());
+ return;
+ }
+
+ // Should we notify the remaining listeners?
+
+ maListeners.erase(iDescriptor);
+}
+
+}
+
+namespace framework {
+
+// right now we assume there's one matching listener
+static uno::Reference<ui::XContextChangeEventListener> GetFirstListenerWith_ImplImpl(
+ css::uno::Reference<css::uno::XComponentContext> const & xComponentContext,
+ uno::Reference<uno::XInterface> const& xEventFocus,
+ std::function<bool (uno::Reference<ui::XContextChangeEventListener> const&)> const& rPredicate)
+{
+ assert(xEventFocus.is()); // in current usage it's a bug if the XController is null here
+ uno::Reference<ui::XContextChangeEventListener> xRet;
+
+ rtl::Reference<ContextChangeEventMultiplexer> pMultiplexer =
+ dynamic_cast<ContextChangeEventMultiplexer *>(ui::ContextChangeEventMultiplexer::get(xComponentContext).get());
+ assert(pMultiplexer);
+
+ ContextChangeEventMultiplexer::FocusDescriptor const*const pFocusDescriptor(
+ pMultiplexer->GetFocusDescriptor(xEventFocus, false));
+ if (!pFocusDescriptor)
+ return xRet;
+
+ for (auto & xListener : pFocusDescriptor->maListeners)
+ {
+ if (rPredicate(xListener))
+ {
+ assert(!xRet.is()); // generalize this if it is used for more than 1:1 mapping?
+ xRet = xListener;
+ }
+ }
+ return xRet;
+}
+
+namespace {
+
+struct Hook
+{
+ Hook() { g_pGetMultiplexerListener = &GetFirstListenerWith_ImplImpl; }
+ ~Hook() { g_pGetMultiplexerListener = nullptr; }
+};
+
+Hook g_hook;
+
+}
+
+}
+
+extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface *
+org_apache_openoffice_comp_framework_ContextChangeEventMultiplexer_get_implementation(
+ css::uno::XComponentContext *,
+ css::uno::Sequence<css::uno::Any> const &)
+{
+ return cppu::acquire(new ContextChangeEventMultiplexer());
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/framework/source/services/autorecovery.cxx b/framework/source/services/autorecovery.cxx
new file mode 100644
index 000000000..073f7094c
--- /dev/null
+++ b/framework/source/services/autorecovery.cxx
@@ -0,0 +1,4243 @@
+/* -*- 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_features.h>
+
+#include <loadenv/loadenv.hxx>
+
+#include <strings.hrc>
+#include <classes/fwkresid.hxx>
+#include <properties.h>
+#include <targets.h>
+
+#include <helper/mischelper.hxx>
+
+#include <com/sun/star/ucb/NameClash.hpp>
+#include <com/sun/star/container/XNameAccess.hpp>
+#include <com/sun/star/configuration/theDefaultProvider.hpp>
+#include <com/sun/star/frame/Desktop.hpp>
+#include <com/sun/star/frame/theGlobalEventBroadcaster.hpp>
+#include <com/sun/star/frame/XLoadable.hpp>
+#include <com/sun/star/frame/XModel3.hpp>
+#include <com/sun/star/frame/ModuleManager.hpp>
+#include <com/sun/star/frame/XTitle.hpp>
+#include <com/sun/star/frame/XFrame.hpp>
+#include <com/sun/star/frame/XController.hpp>
+#include <com/sun/star/frame/XModel.hpp>
+#include <com/sun/star/frame/XStorable.hpp>
+#include <com/sun/star/util/XModifiable.hpp>
+#include <com/sun/star/util/URLTransformer.hpp>
+#include <com/sun/star/util/XURLTransformer.hpp>
+#include <com/sun/star/frame/XDesktop.hpp>
+#include <com/sun/star/container/XNameContainer.hpp>
+#include <com/sun/star/util/XChangesNotifier.hpp>
+#include <com/sun/star/beans/XPropertySet.hpp>
+#include <com/sun/star/beans/PropertyAttribute.hpp>
+#include <com/sun/star/document/XDocumentPropertiesSupplier.hpp>
+#include <com/sun/star/document/XDocumentRecovery.hpp>
+#include <com/sun/star/document/XExtendedFilterDetection.hpp>
+#include <com/sun/star/util/XCloseable.hpp>
+#include <com/sun/star/awt/XWindow2.hpp>
+#include <com/sun/star/task/XStatusIndicatorFactory.hpp>
+#include <com/sun/star/task/ErrorCodeIOException.hpp>
+#include <com/sun/star/lang/IndexOutOfBoundsException.hpp>
+#include <com/sun/star/lang/XTypeProvider.hpp>
+#include <com/sun/star/lang/XServiceInfo.hpp>
+#include <com/sun/star/lang/XSingleServiceFactory.hpp>
+#include <com/sun/star/frame/XDispatch.hpp>
+#include <com/sun/star/document/XDocumentEventListener.hpp>
+#include <com/sun/star/document/XDocumentEventBroadcaster.hpp>
+#include <com/sun/star/util/XChangesListener.hpp>
+#include <com/sun/star/task/XStatusIndicator.hpp>
+#include <com/sun/star/util/XModifyListener.hpp>
+
+#include <comphelper/configuration.hxx>
+#include <cppuhelper/basemutex.hxx>
+#include <cppuhelper/exc_hlp.hxx>
+#include <cppuhelper/compbase.hxx>
+#include <cppuhelper/propshlp.hxx>
+#include <cppuhelper/supportsservice.hxx>
+#include <o3tl/safeint.hxx>
+#include <o3tl/typed_flags_set.hxx>
+#include <o3tl/string_view.hxx>
+#include <unotools/mediadescriptor.hxx>
+#include <comphelper/multiinterfacecontainer3.hxx>
+#include <comphelper/namedvaluecollection.hxx>
+#include <comphelper/sequence.hxx>
+#include <utility>
+#include <vcl/evntpost.hxx>
+#include <vcl/svapp.hxx>
+#include <vcl/timer.hxx>
+#include <unotools/pathoptions.hxx>
+#include <tools/diagnose_ex.h>
+#include <unotools/tempfile.hxx>
+#include <ucbhelper/content.hxx>
+#include <svtools/sfxecode.hxx>
+
+#include <vcl/weld.hxx>
+#include <osl/file.hxx>
+#include <sal/log.hxx>
+#include <unotools/bootstrap.hxx>
+#include <unotools/configmgr.hxx>
+#include <svl/documentlockfile.hxx>
+#include <tools/urlobj.hxx>
+#include <officecfg/Office/Common.hxx>
+#include <officecfg/Office/Recovery.hxx>
+#include <officecfg/Setup.hxx>
+
+using namespace css::uno;
+using namespace css::document;
+using namespace css::frame;
+using namespace css::lang;
+using namespace framework;
+
+namespace {
+
+/** @short hold all needed information for an asynchronous dispatch alive.
+
+ @descr Because some operations are forced to be executed asynchronously
+ (e.g. requested by our CrashSave/Recovery dialog) ... we must make sure
+ that this information won't be set as "normal" members of our AutoRecovery
+ instance. Otherwise they can disturb our normal AutoSave-timer handling.
+ e.g. it can be unclear then, which progress has to be used for storing documents...
+ */
+struct DispatchParams
+{
+public:
+ DispatchParams();
+ DispatchParams(const ::comphelper::SequenceAsHashMap& lArgs ,
+ const css::uno::Reference< css::uno::XInterface >& xOwner);
+
+ void forget();
+
+public:
+
+ /** @short can be set from outside and is provided to
+ our internal started operations.
+
+ @descr Normally we use the normal status indicator
+ of the document windows to show a progress.
+ But in case we are used by any special UI,
+ it can provide its own status indicator object
+ to us - so we use it instead of the normal one.
+ */
+ css::uno::Reference< css::task::XStatusIndicator > m_xProgress;
+
+ /** TODO document me */
+ OUString m_sSavePath;
+
+ /** @short define the current cache entry, which should be used for current
+ backup or cleanUp operation ... which is may be done asynchronous */
+ sal_Int32 m_nWorkingEntryID;
+
+ /** @short used for async operations, to prevent us from dying.
+
+ @descr If our dispatch() method was forced to start the
+ internal operation asynchronous... we send an event
+ to start and return immediately. But we must be sure that
+ our instance live if the event callback reach us.
+ So we hold a uno reference to ourself.
+ */
+ css::uno::Reference< css::uno::XInterface > m_xHoldRefForAsyncOpAlive;
+};
+
+/** These values are used as flags and represent the current state of a document.
+ Every state of the life time of a document has to be recognized here.
+
+ @attention Do not change (means reorganize) already used numbers.
+ There exists some code inside SVX, which uses the same numbers,
+ to analyze such document states.
+ Not the best design ... but may be it will be changed later .-)
+*/
+enum class DocState: sal_Int32
+{
+ /* TEMP STATES */
+
+ /// default state, if a document was new created or loaded
+ Unknown = 0,
+ /// modified against the original file
+ Modified = 1,
+ /// an active document can be postponed to be saved later.
+ Postponed = 2,
+ /// was already handled during one AutoSave/Recovery session.
+ Handled = 4,
+ /** an action was started (saving/loading) ... Can be interesting later if the process may be was interrupted by an exception. */
+ TrySave = 8,
+ TryLoadBackup = 16,
+ TryLoadOriginal = 32,
+
+ /* FINAL STATES */
+
+ /// the Auto/Emergency saved document is not usable any longer
+ Damaged = 64,
+ /// the Auto/Emergency saved document is not really up-to-date (some changes can be missing)
+ Incomplete = 128,
+ /// the Auto/Emergency saved document was processed successfully
+ Succeeded = 512
+};
+
+}
+
+template<> struct o3tl::typed_flags<DocState>: o3tl::is_typed_flags<DocState, 0x2FF> {};
+
+namespace {
+
+// TODO document me ... flag field
+// Emergency_Save and Recovery overwrites Auto_Save!
+enum class Job
+{
+ NoJob = 0,
+ AutoSave = 1,
+ EmergencySave = 2,
+ Recovery = 4,
+ EntryBackup = 8,
+ EntryCleanup = 16,
+ PrepareEmergencySave = 32,
+ SessionSave = 64,
+ SessionRestore = 128,
+ DisableAutorecovery = 256,
+ SetAutosaveState = 512,
+ SessionQuietQuit = 1024,
+ UserAutoSave = 2048
+};
+
+}
+
+template<> struct o3tl::typed_flags<Job>: o3tl::is_typed_flags<Job, 0xFFF> {};
+
+namespace {
+
+/**
+ implements the functionality of AutoSave and AutoRecovery
+ of documents - including features of an EmergencySave in
+ case a GPF occurs.
+ */
+typedef ::cppu::WeakComponentImplHelper<
+ css::lang::XServiceInfo,
+ css::frame::XDispatch,
+ css::document::XDocumentEventListener, // => css.lang.XEventListener
+ css::util::XChangesListener, // => css.lang.XEventListener
+ css::util::XModifyListener > // => css.lang.XEventListener
+ AutoRecovery_BASE;
+
+class AutoRecovery : private cppu::BaseMutex
+ , public AutoRecovery_BASE
+ , public ::cppu::OPropertySetHelper // => XPropertySet, XFastPropertySet, XMultiPropertySet
+{
+public:
+ /** @short indicates the results of a FAILURE_SAFE operation
+
+ @descr We must know, which reason was the real one in case
+ we couldn't copy a "failure document" to a user specified path.
+ We must know, if we can forget our cache entry or not.
+ */
+ enum EFailureSafeResult
+ {
+ E_COPIED,
+ E_ORIGINAL_FILE_MISSING,
+ E_WRONG_TARGET_PATH
+ };
+
+ // TODO document me
+ enum ETimerType
+ {
+ /** the timer should not be used next time */
+ E_DONT_START_TIMER,
+ /** timer (was/must be) started with normal AutoSaveTimeIntervall */
+ E_NORMAL_AUTOSAVE_INTERVALL,
+ /** timer must be started with special short time interval,
+ to poll for an user idle period */
+ E_POLL_FOR_USER_IDLE,
+ /** timer must be started with a very(!) short time interval,
+ to poll for the end of an user action, which does not allow saving documents in general */
+ E_POLL_TILL_AUTOSAVE_IS_ALLOWED,
+ /** don't start the timer - but calls the same action then before immediately again! */
+ E_CALL_ME_BACK
+ };
+
+ /** @short combine different information about one office document. */
+ struct TDocumentInfo
+ {
+ public:
+
+ TDocumentInfo()
+ : DocumentState (DocState::Unknown)
+ , UsedForSaving (false)
+ , ListenForModify (false)
+ , IgnoreClosing (false)
+ , ID (-1 )
+ {}
+
+ /** @short points to the document. */
+ css::uno::Reference< css::frame::XModel > Document;
+
+ /** @short knows, if the document is really modified since the last autosave,
+ or was postponed, because it was an active one etcpp...
+
+ @descr Because we have no CHANGE TRACKING mechanism, based on office document,
+ we implements it by ourself. We listen for MODIFIED events
+ of each document and update this state flag here.
+
+ Further we postpone saving of active documents, e.g. if the user
+ works currently on it. We wait for an idle period then...
+ */
+ DocState DocumentState;
+
+ /** Because our applications not ready for concurrent save requests at the same time,
+ we have suppress our own AutoSave for the moment, a document will be already saved
+ by others.
+ */
+ bool UsedForSaving;
+
+ /** For every user action, which modifies a document (e.g. key input) we get
+ a notification as XModifyListener. That seems to be a "performance issue" .-)
+ So we decided to listen for such modify events only for the time in which the document
+ was stored as temp. file and was not modified again by the user.
+ */
+ bool ListenForModify;
+
+ /** For SessionSave we must close all open documents by ourself.
+ But because we are listen for documents events, we get some ...
+ and deregister these documents from our configuration.
+ That's why we mark these documents as "Closed by ourself" so we can
+ ignore these "OnUnload" or disposing() events .-)
+ */
+ bool IgnoreClosing;
+
+ /** TODO: document me */
+ OUString OrgURL;
+ OUString FactoryURL;
+ OUString TemplateURL;
+
+ OUString OldTempURL;
+ OUString NewTempURL;
+
+ OUString AppModule; // e.g. com.sun.star.text.TextDocument - used to identify app module
+ OUString FactoryService; // the service to create a document of the module
+ OUString RealFilter; // real filter, which was used at loading time
+ OUString DefaultFilter; // supports saving of the default format without losing data
+ OUString Extension; // file extension of the default filter
+ OUString Title; // can be used as "DisplayName" on every recovery UI!
+ css::uno::Sequence< OUString >
+ ViewNames; // names of the view which were active at emergency-save time
+
+ sal_Int32 ID;
+ };
+
+ /** @short used to know every currently open document. */
+ typedef ::std::vector< TDocumentInfo > TDocumentList;
+
+// member
+
+private:
+
+ /** @short the global uno service manager.
+ @descr Must be used to create own needed services.
+ */
+ css::uno::Reference< css::uno::XComponentContext > m_xContext;
+
+ /** @short points to the underlying recovery configuration.
+ @descr This instance does not cache - it calls directly the
+ configuration API!
+ */
+ css::uno::Reference< css::container::XNameAccess > m_xRecoveryCFG;
+
+ /** @short proxy weak binding to forward Events to ourself without
+ an ownership cycle
+ */
+ css::uno::Reference< css::util::XChangesListener > m_xRecoveryCFGListener;
+
+ /** @short points to the used configuration package or.openoffice.Setup
+ @descr This instance does not cache - it calls directly the
+ configuration API!
+ */
+ css::uno::Reference< css::container::XNameAccess > m_xModuleCFG;
+
+ /** @short holds the global event broadcaster alive,
+ where we listen for new created documents.
+ */
+ css::uno::Reference< css::frame::XGlobalEventBroadcaster > m_xNewDocBroadcaster;
+
+ /** @short proxy weak binding to forward Events to ourself without
+ an ownership cycle
+ */
+ css::uno::Reference< css::document::XDocumentEventListener > m_xNewDocBroadcasterListener;
+
+ /** @short because we stop/restart listening sometimes, it's a good idea to know
+ if we already registered as listener .-)
+ */
+ bool m_bListenForDocEvents;
+ bool m_bListenForConfigChanges;
+
+ /** @short specify the time interval between two save actions.
+ @descr tools::Time is measured in [min].
+ */
+ sal_Int32 m_nAutoSaveTimeIntervall;
+
+ /** @short for an asynchronous operation we must know, if there is
+ at least one running job (may be asynchronous!).
+ */
+ Job m_eJob;
+
+ /** @short the timer, which is used to be informed about the next
+ saving time ...
+ @remark must lock SolarMutex to use
+ */
+ Timer m_aTimer;
+
+ /** @short make our dispatch asynchronous ... if required to do so! */
+ std::unique_ptr<vcl::EventPoster> m_xAsyncDispatcher;
+
+ /** @see DispatchParams
+ */
+ DispatchParams m_aDispatchParams;
+
+ /** @short indicates, which time period is currently used by the
+ internal timer.
+ */
+ ETimerType m_eTimerType;
+
+ /** @short this cache is used to hold all information about
+ recovery/emergency save documents alive.
+ */
+ TDocumentList m_lDocCache;
+
+ // TODO document me
+ sal_Int32 m_nIdPool;
+
+ /** @short contains all status listener registered at this instance.
+ */
+ comphelper::OMultiTypeInterfaceContainerHelperVar3<css::frame::XStatusListener, OUString> m_lListener;
+
+ /** @descr This member is used to prevent us against re-entrance problems.
+ A mutex can't help to prevent us from concurrent using of members
+ inside the same thread. But e.g. our internally used stl structures
+ are not threadsafe ... and furthermore they can't be used at the same time
+ for iteration and add/remove requests!
+ So we have to detect such states and ... show a warning.
+ May be there will be a better solution next time ... (copying the cache temp.
+ bevor using).
+
+ And further it's not possible to use a simple boolean value here.
+ Because if more than one operation iterates over the same stl container ...
+ (only to modify it's elements but don't add new or removing existing ones!)
+ it should be possible doing so. But we must guarantee that the last operation reset
+ this lock ... not the first one ! So we use a "ref count" mechanism for that."
+ */
+ sal_Int32 m_nDocCacheLock;
+
+ /** @descr These members are used to check the minimum disc space, which must exists
+ to start the corresponding operation.
+ */
+ sal_Int32 m_nMinSpaceDocSave;
+ sal_Int32 m_nMinSpaceConfigSave;
+
+// interface
+
+public:
+
+ explicit AutoRecovery(css::uno::Reference< css::uno::XComponentContext > xContext);
+ virtual ~AutoRecovery( ) override;
+
+ virtual OUString SAL_CALL getImplementationName() override
+ {
+ return "com.sun.star.comp.framework.AutoRecovery";
+ }
+
+ virtual sal_Bool SAL_CALL supportsService(OUString const & ServiceName) override
+ {
+ return cppu::supportsService(this, ServiceName);
+ }
+
+ virtual css::uno::Sequence<OUString> SAL_CALL getSupportedServiceNames() override
+ {
+ return {"com.sun.star.frame.AutoRecovery"};
+ }
+
+ // XInterface
+ virtual void SAL_CALL acquire() noexcept override
+ { OWeakObject::acquire(); }
+ virtual void SAL_CALL release() noexcept override
+ { OWeakObject::release(); }
+ virtual css::uno::Any SAL_CALL queryInterface( const css::uno::Type& type) override;
+
+ /// Initialization function after having acquire()'d.
+ void initListeners();
+
+ // XTypeProvider
+ virtual css::uno::Sequence< css::uno::Type > SAL_CALL getTypes( ) override;
+
+ // css.frame.XDispatch
+ virtual void SAL_CALL dispatch(const css::util::URL& aURL ,
+ const css::uno::Sequence< css::beans::PropertyValue >& lArguments) override;
+
+ virtual void SAL_CALL addStatusListener(const css::uno::Reference< css::frame::XStatusListener >& xListener,
+ const css::util::URL& aURL ) override;
+
+ virtual void SAL_CALL removeStatusListener(const css::uno::Reference< css::frame::XStatusListener >& xListener,
+ const css::util::URL& aURL ) override;
+
+ // css.document.XDocumentEventListener
+ /** @short informs about created/opened documents.
+
+ @descr Every new opened/created document will be saved internally
+ so it can be checked if it's modified. This modified state
+ is used later to decide, if it must be saved or not.
+
+ @param aEvent
+ points to the new created/opened document.
+ */
+ virtual void SAL_CALL documentEventOccured(const css::document::DocumentEvent& aEvent) override;
+
+ // css.util.XChangesListener
+ virtual void SAL_CALL changesOccurred(const css::util::ChangesEvent& aEvent) override;
+
+ // css.util.XModifyListener
+ virtual void SAL_CALL modified(const css::lang::EventObject& aEvent) override;
+
+ // css.lang.XEventListener
+ virtual void SAL_CALL disposing(const css::lang::EventObject& aEvent) override;
+
+protected:
+
+ // OPropertySetHelper
+
+ virtual sal_Bool SAL_CALL convertFastPropertyValue( css::uno::Any& aConvertedValue,
+ css::uno::Any& aOldValue ,
+ sal_Int32 nHandle ,
+ const css::uno::Any& aValue ) override;
+
+ virtual void SAL_CALL setFastPropertyValue_NoBroadcast( sal_Int32 nHandle,
+ const css::uno::Any& aValue ) override;
+ using cppu::OPropertySetHelper::getFastPropertyValue;
+ virtual void SAL_CALL getFastPropertyValue(css::uno::Any& aValue ,
+ sal_Int32 nHandle) const override;
+
+ virtual ::cppu::IPropertyArrayHelper& SAL_CALL getInfoHelper() override;
+
+ virtual css::uno::Reference< css::beans::XPropertySetInfo > SAL_CALL getPropertySetInfo() override;
+
+private:
+ virtual void SAL_CALL disposing() final override;
+
+ /** @short open the underlying configuration.
+
+ @descr This method must be called every time
+ a configuration call is needed. Because
+ method works together with the member
+ m_xCFG, open it on demand and cache it
+ afterwards.
+
+ @throw [com.sun.star.uno.RuntimeException]
+ if config could not be opened successfully!
+
+ @threadsafe
+ */
+ void implts_openConfig();
+
+ /** @short read the underlying configuration.
+
+ @descr After that we know the initial state - means:
+ - if AutoSave was enabled by the user
+ - which time interval has to be used
+ - which recovery entries may already exists
+
+ @throw [com.sun.star.uno.RuntimeException]
+ if config could not be opened or read successfully!
+
+ @threadsafe
+ */
+ void implts_readConfig();
+
+ /** @short read the underlying configuration...
+
+ @descr ... but only keys related to the AutoSave mechanism.
+ Means: State and Timer interval.
+ E.g. the recovery list is not addressed here.
+
+ @throw [com.sun.star.uno.RuntimeException]
+ if config could not be opened or read successfully!
+
+ @threadsafe
+ */
+ void implts_readAutoSaveConfig();
+
+ // TODO document me
+ void implts_flushConfigItem(const AutoRecovery::TDocumentInfo& rInfo ,
+ bool bRemoveIt = false);
+
+ // TODO document me
+ void implts_startListening();
+ void implts_startModifyListeningOnDoc(AutoRecovery::TDocumentInfo& rInfo);
+
+ // TODO document me
+ void implts_stopListening();
+ void implts_stopModifyListeningOnDoc(AutoRecovery::TDocumentInfo& rInfo);
+
+ /** @short stops and may be(!) restarts the timer.
+
+ @descr A running timer is stopped every time here.
+ But starting depends from the different internal
+ timer variables (e.g. AutoSaveEnabled, AutoSaveTimeIntervall,
+ TimerType etcpp.)
+
+ @throw [com.sun.star.uno.RuntimeException]
+ if timer could not be stopped or started!
+
+ @threadsafe
+ */
+ void implts_updateTimer();
+
+ /** @short stop the timer.
+
+ @descr Double calls will be ignored - means we do
+ nothing here, if the timer is already disabled.
+
+ @throw [com.sun.star.uno.RuntimeException]
+ if timer could not be stopped!
+
+ @threadsafe
+ */
+ void implts_stopTimer();
+
+ /** @short callback of our internal timer.
+ */
+ DECL_LINK(implts_timerExpired, Timer*, void);
+
+ /** @short makes our dispatch() method asynchronous!
+ */
+ DECL_LINK(implts_asyncDispatch, LinkParamNone*, void);
+
+ /** @short implements the dispatch real. */
+ void implts_dispatch(const DispatchParams& aParams);
+
+ /** @short validate new detected document and add it into the internal
+ document list.
+
+ @descr This method should be called only if it's clear that a new
+ document was opened/created during office runtime.
+ This method checks if it's a top level document (means not an embedded one).
+ Only such top level documents can be recognized by this auto save mechanism.
+
+ @param xDocument
+ the new document, which should be checked and registered.
+
+ @threadsafe
+ */
+ void implts_registerDocument(const css::uno::Reference< css::frame::XModel3 >& xDocument);
+
+ /** @short remove the specified document from our internal document list.
+
+ @param xDocument
+ the new document, which should be deregistered.
+
+ @param bStopListening
+ sal_False: must be used in case this method is called within disposing() of the document,
+ where it makes no sense to deregister our listener. The container dies...
+ sal_True : must be used in case this method is used on "deregistration" of this document, where
+ we must deregister our listener .-)
+
+ @threadsafe
+ */
+ void implts_deregisterDocument(const css::uno::Reference< css::frame::XModel >& xDocument ,
+ bool bStopListening = true);
+
+ // TODO document me
+ void implts_markDocumentModifiedAgainstLastBackup(const css::uno::Reference< css::frame::XModel >& xDocument);
+
+ // TODO document me
+ void implts_updateModifiedState(const css::uno::Reference< css::frame::XModel >& xDocument);
+
+ // TODO document me
+ void implts_updateDocumentUsedForSavingState(const css::uno::Reference< css::frame::XModel >& xDocument ,
+ bool bSaveInProgress);
+
+ // TODO document me
+ void implts_markDocumentAsSaved(const css::uno::Reference< css::frame::XModel >& xDocument);
+
+ /** @short search a document inside given list.
+
+ @param rList
+ reference to a vector, which can contain such
+ document.
+
+ @param xDocument
+ the document, which should be located inside the
+ given list.
+
+ @return [TDocumentList::iterator]
+ which points to the located document.
+ If document does not exists - it's set to
+ rList.end()!
+ */
+ static TDocumentList::iterator impl_searchDocument( AutoRecovery::TDocumentList& rList ,
+ const css::uno::Reference< css::frame::XModel >& xDocument);
+
+ /** TODO document me */
+ void implts_changeAllDocVisibility(bool bVisible);
+ void implts_prepareSessionShutdown();
+
+ /** @short save all current opened documents to a specific
+ backup directory.
+
+ @descr Only really changed documents will be saved here.
+
+ Further this method returns a suggestion, if and how it should
+ be called again. May be some documents was not saved yet
+ and must wait for an user idle period ...
+
+ @param bAllowUserIdleLoop
+ Because this method is used for different uses cases, it must
+ know, which actions are allowed or not.
+ Job::AutoSave =>
+ If a document is the most active one, saving it
+ will be postponed if there exists other unsaved
+ documents. This feature was implemented, because
+ we don't wish to disturb the user on it's work.
+ ... bAllowUserIdleLoop should be set to sal_True
+ Job::EmergencySave / Job::SessionSave =>
+ Here we must finish our work ASAP! It's not allowed
+ to postpone any document.
+ ... bAllowUserIdleLoop must(!) be set to sal_False
+
+ @param pParams
+ sometimes this method is required inside an external dispatch request.
+ The it contains some special environment variables, which overwrites
+ our normal environment.
+ AutoSave => pParams == 0
+ SessionSave/CrashSave => pParams != 0
+
+ @return A suggestion, how the timer (if it's not already disabled!)
+ should be restarted to fulfill the requirements.
+
+ @threadsafe
+ */
+ AutoRecovery::ETimerType implts_saveDocs( bool bAllowUserIdleLoop,
+ bool bRemoveLockFiles,
+ const DispatchParams* pParams = nullptr);
+
+ /** @short save one of the current documents to a specific
+ backup directory.
+
+ @descr It:
+ - defines a new(!) unique temp file name
+ - save the new temp file
+ - remove the old temp file
+ - patch the given info struct
+ - and return errors.
+
+ It does not:
+ - patch the configuration.
+
+ Note further: it patches the info struct
+ more than ones. E.g. the new temp URL is set
+ before the file is saved. And the old URL is removed
+ only if removing of the old file was successful.
+ If this method returns without an exception - everything
+ was OK. Otherwise the info struct can be analyzed to
+ get more information, e.g. when the problem occurs.
+
+ @param sBackupPath
+ the base path for saving such temp files.
+
+ @param rInfo
+ points to an information structure, where
+ e.g. the document, its modified state, the count
+ of autosave-retries etcpp. exists.
+ It's used also to return the new temp file name
+ and some other state values!
+
+ @threadsafe
+ */
+ void implts_saveOneDoc(const OUString& sBackupPath ,
+ AutoRecovery::TDocumentInfo& rInfo ,
+ const css::uno::Reference< css::task::XStatusIndicator >& xExternalProgress);
+
+ /** @short recovery all documents, which was saved during
+ a crash before.
+
+ @return A suggestion, how this method must be called back!
+
+ @threadsafe
+ */
+ AutoRecovery::ETimerType implts_openDocs(const DispatchParams& aParams);
+
+ // TODO document me
+ void implts_openOneDoc(const OUString& sURL ,
+ utl::MediaDescriptor& lDescriptor,
+ AutoRecovery::TDocumentInfo& rInfo );
+
+ // TODO document me
+ void implts_generateNewTempURL(const OUString& sBackupPath ,
+ utl::MediaDescriptor& rMediaDescriptor,
+ AutoRecovery::TDocumentInfo& rInfo );
+
+ /** @short notifies all interested listener about the current state
+ of the currently running operation.
+
+ @descr We support different set's of functions. Job::AutoSave, Job::EmergencySave,
+ Job::Recovery ... etcpp.
+ Listener can register itself for any type of supported
+ functionality ... but not for document URL's in special.
+
+ @param eJob
+ is used to know, which set of listener we must notify.
+
+ @param aEvent
+ describe the event more in detail.
+
+ @threadsafe
+ */
+ void implts_informListener( Job eJob ,
+ const css::frame::FeatureStateEvent& aEvent);
+
+ /** short create a feature event struct, which can be send
+ to any interested listener.
+
+ @param eJob
+ describe the current running operation
+ Job::AutoSave, Job::EmergencySave, Job::Recovery
+
+ @param sEventType
+ describe the type of this event
+ START, STOP, UPDATE
+
+ @param pInfo
+ if sOperation is an update, this parameter must be different from NULL
+ and is used to send information regarding the current handled document.
+
+ @return [css::frame::FeatureStateEvent]
+ the event structure for sending.
+ */
+ static css::frame::FeatureStateEvent implst_createFeatureStateEvent( Job eJob ,
+ const OUString& sEventType,
+ AutoRecovery::TDocumentInfo const * pInfo );
+
+ class ListenerInformer
+ {
+ private:
+ AutoRecovery &m_rRecovery;
+ Job m_eJob;
+ bool m_bStopped;
+ public:
+ ListenerInformer(AutoRecovery &rRecovery, Job eJob)
+ : m_rRecovery(rRecovery), m_eJob(eJob), m_bStopped(false)
+ {
+ }
+ void start();
+ void stop();
+ ~ListenerInformer()
+ {
+ stop();
+ }
+ };
+
+ // TODO document me
+ void implts_resetHandleStates();
+
+ // TODO document me
+ void implts_specifyDefaultFilterAndExtension(AutoRecovery::TDocumentInfo& rInfo);
+
+ // TODO document me
+ void implts_specifyAppModuleAndFactory(AutoRecovery::TDocumentInfo& rInfo);
+
+ /** retrieves the names of all active views of the given document
+ @param rInfo
+ the document info, whose <code>Document</code> member must not be <NULL/>.
+ */
+ void implts_collectActiveViewNames( AutoRecovery::TDocumentInfo& rInfo );
+
+ /** updates the configuration so that for all documents, their current view/names are stored
+ */
+ void implts_persistAllActiveViewNames();
+
+ // TODO document me
+ void implts_prepareEmergencySave();
+
+ // TODO document me
+ void implts_doEmergencySave(const DispatchParams& aParams);
+
+ // TODO document me
+ void implts_doRecovery(const DispatchParams& aParams);
+
+ // TODO document me
+ void implts_doSessionSave(const DispatchParams& aParams);
+
+ // TODO document me
+ void implts_doSessionQuietQuit();
+
+ // TODO document me
+ void implts_doSessionRestore(const DispatchParams& aParams);
+
+ // TODO document me
+ void implts_backupWorkingEntry(const DispatchParams& aParams);
+
+ // TODO document me
+ void implts_cleanUpWorkingEntry(const DispatchParams& aParams);
+
+ /** try to make sure that all changed config items (not our used
+ config access only) will be flushed back to disc.
+
+ E.g. our svtools::ConfigItems() has to be flushed explicitly .-(
+
+ Note: This method can't fail. Flushing of config entries is an
+ optional feature. Errors can be ignored.
+ */
+ void impl_flushALLConfigChanges();
+
+ // TODO document me
+ AutoRecovery::EFailureSafeResult implts_copyFile(const OUString& sSource ,
+ const OUString& sTargetPath,
+ const OUString& sTargetName);
+
+ /** @short converts m_eJob into a job description, which
+ can be used to inform an outside listener
+ about the current running operation
+
+ @param eJob
+ describe the current running operation
+ Job::AutoSave, Job::EmergencySave, Job::Recovery
+
+ @return [string]
+ a suitable job description of form:
+ vnd.sun.star.autorecovery:/do...
+ */
+ static OUString implst_getJobDescription(Job eJob);
+
+ /** @short map the given URL to an internal int representation.
+
+ @param aURL
+ the url, which describe the next starting or may be already running
+ operation.
+
+ @return [long]
+ the internal int representation
+ see enum class Job
+ */
+ static Job implst_classifyJob(const css::util::URL& aURL);
+
+ /// TODO
+ void implts_verifyCacheAgainstDesktopDocumentList();
+
+ /// TODO document me
+ bool impl_enoughDiscSpace(sal_Int32 nRequiredSpace);
+
+ /// TODO document me
+ static void impl_showFullDiscError();
+
+ /** @short try to create/use a progress and set it inside the
+ environment.
+
+ @descr The problem behind: There exists different use case of this method.
+ a) An external progress is provided by our CrashSave or Recovery dialog.
+ b) We must create our own progress e.g. for an AutoSave
+ c) Sometimes our application filters don't use the progress
+ provided by the MediaDescriptor. They use the Frame every time to create
+ its own progress. So we implemented a HACK for these and now we set
+ an InterceptedProgress there for the time WE use this frame for loading/storing documents .-)
+
+ @param xNewFrame
+ must be set only in case WE create a new frame (e.g. for loading documents
+ on session restore or recovery). Then search for a frame using rInfo.Document must
+ be suppressed and xFrame must be preferred instead .-)
+
+ @param rInfo
+ used e.g. to find the frame corresponding to a document.
+ This frame must be used to create a new progress e.g. for an AutoSave.
+
+ @param rArgs
+ is used to set the new created progress as parameter on these set.
+ */
+ void impl_establishProgress(const AutoRecovery::TDocumentInfo& rInfo ,
+ utl::MediaDescriptor& rArgs ,
+ const css::uno::Reference< css::frame::XFrame >& xNewFrame);
+
+ void impl_forgetProgress(const AutoRecovery::TDocumentInfo& rInfo ,
+ utl::MediaDescriptor& rArgs ,
+ const css::uno::Reference< css::frame::XFrame >& xNewFrame);
+
+ /** try to remove the specified file from disc.
+
+ Every URL supported by our UCB component can be used here.
+ Further it doesn't matter if the file really exists or not.
+ Because removing a non existent file will have the same
+ result at the end... a non existing file .-)
+
+ On the other side removing of files from disc is an optional
+ feature. If we are not able doing so... it's not a real problem.
+ Ok - users disc place will be smaller then... but we should produce
+ a crash during crash save because we can't delete a temporary file only!
+
+ @param sURL
+ the url of the file, which should be removed.
+ */
+ void st_impl_removeFile(const OUString& sURL);
+
+ /** try to remove ".lock" file from disc if office will be terminated
+ not using the official way .-)
+
+ This method has to be handled "optional". So every error inside
+ has to be ignored ! This method CAN NOT FAIL ... it can forget something only .-)
+ */
+ void st_impl_removeLockFile();
+};
+
+// recovery.xcu
+constexpr OUStringLiteral CFG_PACKAGE_RECOVERY = u"org.openoffice.Office.Recovery/";
+
+const char CFG_ENTRY_AUTOSAVE_ENABLED[] = "AutoSave/Enabled";
+const char CFG_ENTRY_AUTOSAVE_TIMEINTERVALL[] = "AutoSave/TimeIntervall"; //sic!
+
+constexpr OUStringLiteral CFG_ENTRY_REALDEFAULTFILTER = u"ooSetupFactoryActualFilter";
+
+constexpr OUStringLiteral CFG_ENTRY_PROP_TEMPURL = u"TempURL";
+constexpr OUStringLiteral CFG_ENTRY_PROP_ORIGINALURL = u"OriginalURL";
+constexpr OUStringLiteral CFG_ENTRY_PROP_TEMPLATEURL = u"TemplateURL";
+constexpr OUStringLiteral CFG_ENTRY_PROP_FACTORYURL = u"FactoryURL";
+constexpr OUStringLiteral CFG_ENTRY_PROP_MODULE = u"Module";
+constexpr OUStringLiteral CFG_ENTRY_PROP_DOCUMENTSTATE = u"DocumentState";
+constexpr OUStringLiteral CFG_ENTRY_PROP_FILTER = u"Filter";
+constexpr OUStringLiteral CFG_ENTRY_PROP_TITLE = u"Title";
+constexpr OUStringLiteral CFG_ENTRY_PROP_ID = u"ID";
+constexpr OUStringLiteral CFG_ENTRY_PROP_VIEWNAMES = u"ViewNames";
+
+constexpr OUStringLiteral FILTER_PROP_TYPE = u"Type";
+constexpr OUStringLiteral TYPE_PROP_EXTENSIONS = u"Extensions";
+
+// setup.xcu
+constexpr OUStringLiteral CFG_ENTRY_PROP_EMPTYDOCUMENTURL = u"ooSetupFactoryEmptyDocumentURL";
+constexpr OUStringLiteral CFG_ENTRY_PROP_FACTORYSERVICE = u"ooSetupFactoryDocumentService";
+
+const char EVENT_ON_NEW[] = "OnNew";
+const char EVENT_ON_LOAD[] = "OnLoad";
+const char EVENT_ON_UNLOAD[] = "OnUnload";
+const char EVENT_ON_MODIFYCHANGED[] = "OnModifyChanged";
+const char EVENT_ON_SAVE[] = "OnSave";
+const char EVENT_ON_SAVEAS[] = "OnSaveAs";
+const char EVENT_ON_SAVETO[] = "OnCopyTo";
+const char EVENT_ON_SAVEDONE[] = "OnSaveDone";
+const char EVENT_ON_SAVEASDONE[] = "OnSaveAsDone";
+const char EVENT_ON_SAVETODONE[] = "OnCopyToDone";
+const char EVENT_ON_SAVEFAILED[] = "OnSaveFailed";
+const char EVENT_ON_SAVEASFAILED[] = "OnSaveAsFailed";
+const char EVENT_ON_SAVETOFAILED[] = "OnCopyToFailed";
+
+constexpr OUStringLiteral RECOVERY_ITEM_BASE_IDENTIFIER = u"recovery_item_";
+
+const char CMD_PROTOCOL[] = "vnd.sun.star.autorecovery:";
+
+const char CMD_DO_AUTO_SAVE[] = "/doAutoSave"; // force AutoSave ignoring the AutoSave timer
+const char CMD_DO_PREPARE_EMERGENCY_SAVE[] = "/doPrepareEmergencySave"; // prepare the office for the following EmergencySave step (hide windows etcpp.)
+const char CMD_DO_EMERGENCY_SAVE[] = "/doEmergencySave"; // do EmergencySave on crash
+const char CMD_DO_RECOVERY[] = "/doAutoRecovery"; // recover all crashed documents
+const char CMD_DO_ENTRY_BACKUP[] = "/doEntryBackup"; // try to store a temp or original file to a user defined location
+const char CMD_DO_ENTRY_CLEANUP[] = "/doEntryCleanUp"; // remove the specified entry from the recovery cache
+const char CMD_DO_SESSION_SAVE[] = "/doSessionSave"; // save all open documents if e.g. a window manager closes an user session
+const char CMD_DO_SESSION_QUIET_QUIT[] = "/doSessionQuietQuit"; // let the current session be quietly closed ( the saving should be done using doSessionSave previously ) if e.g. a window manager closes an user session
+const char CMD_DO_SESSION_RESTORE[] = "/doSessionRestore"; // restore a saved user session from disc
+const char CMD_DO_DISABLE_RECOVERY[] = "/disableRecovery"; // disable recovery and auto save (!) temp. for this office session
+const char CMD_DO_SET_AUTOSAVE_STATE[] = "/setAutoSaveState"; // disable/enable auto save (not crash save) for this office session
+
+constexpr OUStringLiteral REFERRER_USER = u"private:user";
+
+constexpr OUStringLiteral PROP_DISPATCH_ASYNCHRON = u"DispatchAsynchron";
+constexpr OUStringLiteral PROP_PROGRESS = u"StatusIndicator";
+constexpr OUStringLiteral PROP_SAVEPATH = u"SavePath";
+constexpr OUStringLiteral PROP_ENTRY_ID = u"EntryID";
+constexpr OUStringLiteral PROP_AUTOSAVE_STATE = u"AutoSaveState";
+
+constexpr OUStringLiteral OPERATION_START = u"start";
+constexpr OUStringLiteral OPERATION_STOP = u"stop";
+constexpr OUStringLiteral OPERATION_UPDATE = u"update";
+
+const sal_Int32 MIN_DISCSPACE_DOCSAVE = 5; // [MB]
+const sal_Int32 MIN_DISCSPACE_CONFIGSAVE = 1; // [MB]
+const sal_Int32 RETRY_STORE_ON_FULL_DISC_FOREVER = 300; // not forever ... but often enough .-)
+const sal_Int32 RETRY_STORE_ON_MIGHT_FULL_DISC_USEFULL = 3; // in case FULL DISC does not seem the real problem
+const sal_Int32 GIVE_UP_RETRY = 1; // in case FULL DISC does not seem the real problem
+
+#define SAVE_IN_PROGRESS true
+#define SAVE_FINISHED false
+
+#define LOCK_FOR_CACHE_ADD_REMOVE true
+#define LOCK_FOR_CACHE_USE false
+
+#define MIN_TIME_FOR_USER_IDLE 10000 // 10s user idle
+
+// enable the following defines in case you wish to simulate a full disc for debug purposes .-)
+
+// this define throws every time a document is stored or a configuration change
+// should be flushed an exception ... so the special error handler for this scenario is triggered
+// #define TRIGGER_FULL_DISC_CHECK
+
+// force "return sal_False" for the method impl_enoughDiscSpace().
+// #define SIMULATE_FULL_DISC
+
+class CacheLockGuard
+{
+ private:
+
+ // holds the outside caller alive, so it's shared resources
+ // are valid every time
+ css::uno::Reference< css::uno::XInterface > m_xOwner;
+
+ // mutex shared with outside caller!
+ osl::Mutex& m_rSharedMutex;
+
+ // this variable knows the state of the "cache lock"
+ sal_Int32& m_rCacheLock;
+
+ // to prevent increasing/decreasing of m_rCacheLock more than once
+ // we must know if THIS guard has an actual lock set there!
+ bool m_bLockedByThisGuard;
+
+ public:
+
+ CacheLockGuard(AutoRecovery* pOwner ,
+ osl::Mutex& rMutex ,
+ sal_Int32& rCacheLock ,
+ bool bLockForAddRemoveVectorItems);
+ ~CacheLockGuard();
+
+ void lock(bool bLockForAddRemoveVectorItems);
+ void unlock();
+};
+
+CacheLockGuard::CacheLockGuard(AutoRecovery* pOwner ,
+ osl::Mutex& rMutex ,
+ sal_Int32& rCacheLock ,
+ bool bLockForAddRemoveVectorItems)
+ : m_xOwner (static_cast< css::frame::XDispatch* >(pOwner))
+ , m_rSharedMutex (rMutex )
+ , m_rCacheLock (rCacheLock )
+ , m_bLockedByThisGuard(false )
+{
+ lock(bLockForAddRemoveVectorItems);
+}
+
+CacheLockGuard::~CacheLockGuard()
+{
+ unlock();
+ m_xOwner.clear();
+}
+
+void CacheLockGuard::lock(bool bLockForAddRemoveVectorItems)
+{
+ /* SAFE */
+ osl::MutexGuard g(m_rSharedMutex);
+
+ if (m_bLockedByThisGuard)
+ return;
+
+ // This cache lock is needed only to prevent us from removing/adding
+ // items from/into the recovery cache ... during it's used at another code place
+ // for iterating .-)
+
+ // Modifying of item properties is allowed and sometimes needed!
+ // So we should detect only the dangerous state of concurrent add/remove
+ // requests and throw an exception then ... which can of course break the whole
+ // operation. On the other side a crash reasoned by an invalid stl iterator
+ // will have the same effect .-)
+
+ if ( (m_rCacheLock > 0) && bLockForAddRemoveVectorItems )
+ {
+ OSL_FAIL("Re-entrance problem detected. Using of an stl structure in combination with iteration, adding, removing of elements etcpp.");
+ throw css::uno::RuntimeException(
+ "Re-entrance problem detected. Using of an stl structure in combination with iteration, adding, removing of elements etcpp.",
+ m_xOwner);
+ }
+
+ ++m_rCacheLock;
+ m_bLockedByThisGuard = true;
+ /* SAFE */
+}
+
+void CacheLockGuard::unlock()
+{
+ /* SAFE */
+ osl::MutexGuard g(m_rSharedMutex);
+
+ if ( ! m_bLockedByThisGuard)
+ return;
+
+ --m_rCacheLock;
+ m_bLockedByThisGuard = false;
+
+ if (m_rCacheLock < 0)
+ {
+ OSL_FAIL("Wrong using of member m_nDocCacheLock detected. A ref counted value shouldn't reach values <0 .-)");
+ throw css::uno::RuntimeException(
+ "Wrong using of member m_nDocCacheLock detected. A ref counted value shouldn't reach values <0 .-)",
+ m_xOwner);
+ }
+ /* SAFE */
+}
+
+DispatchParams::DispatchParams()
+ : m_nWorkingEntryID(-1)
+{
+};
+
+DispatchParams::DispatchParams(const ::comphelper::SequenceAsHashMap& lArgs ,
+ const css::uno::Reference< css::uno::XInterface >& xOwner)
+{
+ m_nWorkingEntryID = lArgs.getUnpackedValueOrDefault(PROP_ENTRY_ID, sal_Int32(-1) );
+ m_xProgress = lArgs.getUnpackedValueOrDefault(PROP_PROGRESS, css::uno::Reference< css::task::XStatusIndicator >());
+ m_sSavePath = lArgs.getUnpackedValueOrDefault(PROP_SAVEPATH, OUString() );
+ m_xHoldRefForAsyncOpAlive = xOwner;
+};
+
+void DispatchParams::forget()
+{
+ m_sSavePath.clear();
+ m_nWorkingEntryID = -1;
+ m_xProgress.clear();
+ m_xHoldRefForAsyncOpAlive.clear();
+};
+
+AutoRecovery::AutoRecovery(css::uno::Reference< css::uno::XComponentContext > xContext)
+ : AutoRecovery_BASE (m_aMutex)
+ , ::cppu::OPropertySetHelper(cppu::WeakComponentImplHelperBase::rBHelper)
+ , m_xContext (std::move(xContext ))
+ , m_bListenForDocEvents (false )
+ , m_bListenForConfigChanges (false )
+ , m_nAutoSaveTimeIntervall (0 )
+ , m_eJob (Job::NoJob)
+ , m_aTimer( "framework::AutoRecovery m_aTimer" )
+ , m_xAsyncDispatcher (new vcl::EventPoster( LINK( this, AutoRecovery, implts_asyncDispatch ) ))
+ , m_eTimerType (E_DONT_START_TIMER )
+ , m_nIdPool (0 )
+ , m_lListener (cppu::WeakComponentImplHelperBase::rBHelper.rMutex)
+ , m_nDocCacheLock (0 )
+ , m_nMinSpaceDocSave (MIN_DISCSPACE_DOCSAVE )
+ , m_nMinSpaceConfigSave (MIN_DISCSPACE_CONFIGSAVE )
+{
+}
+
+void AutoRecovery::initListeners()
+{
+ // read configuration to know if autosave/recovery is on/off etcpp...
+ implts_readConfig();
+
+ implts_startListening();
+
+ // establish callback for our internal used timer.
+ // Note: Its only active, if the timer will be started ...
+ SolarMutexGuard g;
+ m_aTimer.SetInvokeHandler(LINK(this, AutoRecovery, implts_timerExpired));
+}
+
+AutoRecovery::~AutoRecovery()
+{
+ assert(!m_aTimer.IsActive());
+}
+
+void AutoRecovery::disposing()
+{
+ implts_stopTimer();
+ SolarMutexGuard g;
+ m_xAsyncDispatcher.reset();
+}
+
+Any SAL_CALL AutoRecovery::queryInterface( const css::uno::Type& _rType )
+{
+ Any aRet = AutoRecovery_BASE::queryInterface( _rType );
+ if ( !aRet.hasValue() )
+ aRet = OPropertySetHelper::queryInterface( _rType );
+ return aRet;
+}
+
+Sequence< css::uno::Type > SAL_CALL AutoRecovery::getTypes( )
+{
+ return comphelper::concatSequences(
+ AutoRecovery_BASE::getTypes(),
+ ::cppu::OPropertySetHelper::getTypes()
+ );
+}
+
+void SAL_CALL AutoRecovery::dispatch(const css::util::URL& aURL ,
+ const css::uno::Sequence< css::beans::PropertyValue >& lArguments)
+{
+ SAL_INFO("fwk.autorecovery", "AutoRecovery::dispatch() starts ..." << aURL.Complete);
+
+ // valid request ?
+ Job eNewJob = AutoRecovery::implst_classifyJob(aURL);
+ if (eNewJob == Job::NoJob)
+ return;
+
+ bool bAsync;
+ DispatchParams aParams;
+ /* SAFE */ {
+ osl::ClearableMutexGuard g(cppu::WeakComponentImplHelperBase::rBHelper.rMutex);
+
+ // still running operation ... ignoring Job::AutoSave.
+ // All other requests has higher prio!
+ if (
+ ( m_eJob != Job::NoJob ) &&
+ ((m_eJob & Job::AutoSave ) != Job::AutoSave)
+ )
+ {
+ SAL_INFO("fwk.autorecovery", "AutoRecovery::dispatch(): There is already an asynchronous dispatch() running. New request will be ignored!");
+ return;
+ }
+
+ ::comphelper::SequenceAsHashMap lArgs(lArguments);
+
+ // check if somewhere wish to disable recovery temp. for this office session
+ // This can be done immediately... must not been done asynchronous.
+ if ((eNewJob & Job::DisableAutorecovery) == Job::DisableAutorecovery)
+ {
+ // it's important to set a flag internally, so AutoRecovery will be suppressed - even if it's requested.
+ m_eJob |= eNewJob;
+ implts_stopTimer();
+ implts_stopListening();
+ return;
+ }
+
+ // disable/enable AutoSave for this office session only
+ // independent from the configuration entry.
+ if ((eNewJob & Job::SetAutosaveState) == Job::SetAutosaveState)
+ {
+ bool bOn = lArgs.getUnpackedValueOrDefault(PROP_AUTOSAVE_STATE, true);
+ if (bOn)
+ {
+ // don't enable AutoSave hardly !
+ // reload configuration to know the current state.
+ implts_readAutoSaveConfig();
+ g.clear();
+ implts_updateTimer();
+ // can it happen that might be the listener was stopped? .-)
+ // make sure it runs always... even if AutoSave itself was disabled temporarily.
+ implts_startListening();
+ }
+ else
+ {
+ implts_stopTimer();
+ m_eJob &= ~Job::AutoSave;
+ m_eTimerType = AutoRecovery::E_DONT_START_TIMER;
+ }
+ return;
+ }
+
+ m_eJob |= eNewJob;
+
+ bAsync = lArgs.getUnpackedValueOrDefault(PROP_DISPATCH_ASYNCHRON, false);
+ aParams = DispatchParams(lArgs, static_cast< css::frame::XDispatch* >(this));
+
+ // Hold this instance alive till the asynchronous operation will be finished.
+ if (bAsync)
+ m_aDispatchParams = aParams;
+
+ } /* SAFE */
+
+ if (bAsync)
+ m_xAsyncDispatcher->Post();
+ else
+ implts_dispatch(aParams);
+}
+
+void AutoRecovery::ListenerInformer::start()
+{
+ m_rRecovery.implts_informListener(m_eJob,
+ AutoRecovery::implst_createFeatureStateEvent(m_eJob, OPERATION_START, nullptr));
+}
+
+void AutoRecovery::ListenerInformer::stop()
+{
+ if (m_bStopped)
+ return;
+ m_rRecovery.implts_informListener(m_eJob,
+ AutoRecovery::implst_createFeatureStateEvent(m_eJob, OPERATION_STOP, nullptr));
+ m_bStopped = true;
+}
+
+void AutoRecovery::implts_dispatch(const DispatchParams& aParams)
+{
+ Job eJob;
+ /* SAFE */ {
+ osl::MutexGuard g(cppu::WeakComponentImplHelperBase::rBHelper.rMutex);
+ eJob = m_eJob;
+ } /* SAFE */
+
+ // in case a new dispatch overwrites a may ba active AutoSave session
+ // we must restore this session later. see below ...
+ bool bWasAutoSaveActive = ((eJob & Job::AutoSave) == Job::AutoSave);
+ bool bWasUserAutoSaveActive =
+ ((eJob & Job::UserAutoSave) == Job::UserAutoSave);
+
+ // On the other side it makes no sense to reactivate the AutoSave operation
+ // if the new dispatch indicates a final decision...
+ // E.g. an EmergencySave/SessionSave indicates the end of life of the current office session.
+ // It makes no sense to reactivate an AutoSave then.
+ // But a Recovery or SessionRestore should reactivate a may be already active AutoSave.
+ bool bAllowAutoSaveReactivation = true;
+
+ implts_stopTimer();
+ implts_stopListening();
+
+ ListenerInformer aListenerInformer(*this, eJob);
+ aListenerInformer.start();
+
+ try
+ {
+ // Auto save is called from our internal timer ... not via dispatch() API !
+ // else
+ if (
+ ((eJob & Job::PrepareEmergencySave) == Job::PrepareEmergencySave) &&
+ ((eJob & Job::DisableAutorecovery ) != Job::DisableAutorecovery )
+ )
+ {
+ SAL_INFO("fwk.autorecovery", "... prepare emergency save ...");
+ bAllowAutoSaveReactivation = false;
+ implts_prepareEmergencySave();
+ }
+ else
+ if (
+ ((eJob & Job::EmergencySave ) == Job::EmergencySave ) &&
+ ((eJob & Job::DisableAutorecovery) != Job::DisableAutorecovery)
+ )
+ {
+ SAL_INFO("fwk.autorecovery", "... do emergency save ...");
+ bAllowAutoSaveReactivation = false;
+ implts_doEmergencySave(aParams);
+ }
+ else
+ if (
+ ((eJob & Job::Recovery ) == Job::Recovery ) &&
+ ((eJob & Job::DisableAutorecovery) != Job::DisableAutorecovery)
+ )
+ {
+ SAL_INFO("fwk.autorecovery", "... do recovery ...");
+ implts_doRecovery(aParams);
+ }
+ else
+ if (
+ ((eJob & Job::SessionSave ) == Job::SessionSave ) &&
+ ((eJob & Job::DisableAutorecovery) != Job::DisableAutorecovery)
+ )
+ {
+ SAL_INFO("fwk.autorecovery", "... do session save ...");
+ bAllowAutoSaveReactivation = false;
+ implts_doSessionSave(aParams);
+ }
+ else
+ if (
+ ((eJob & Job::SessionQuietQuit ) == Job::SessionQuietQuit ) &&
+ ((eJob & Job::DisableAutorecovery) != Job::DisableAutorecovery)
+ )
+ {
+ SAL_INFO("fwk.autorecovery", "... do session quiet quit ...");
+ bAllowAutoSaveReactivation = false;
+ implts_doSessionQuietQuit();
+ }
+ else
+ if (
+ ((eJob & Job::SessionRestore ) == Job::SessionRestore ) &&
+ ((eJob & Job::DisableAutorecovery) != Job::DisableAutorecovery)
+ )
+ {
+ SAL_INFO("fwk.autorecovery", "... do session restore ...");
+ implts_doSessionRestore(aParams);
+ }
+ else
+ if (
+ ((eJob & Job::EntryBackup ) == Job::EntryBackup ) &&
+ ((eJob & Job::DisableAutorecovery) != Job::DisableAutorecovery)
+ )
+ implts_backupWorkingEntry(aParams);
+ else
+ if (
+ ((eJob & Job::EntryCleanup ) == Job::EntryCleanup ) &&
+ ((eJob & Job::DisableAutorecovery) != Job::DisableAutorecovery)
+ )
+ implts_cleanUpWorkingEntry(aParams);
+ }
+ catch(const css::uno::RuntimeException&)
+ {
+ throw;
+ }
+ catch(const css::uno::Exception&)
+ {
+ // TODO better error handling
+ }
+
+ aListenerInformer.stop();
+
+ /* SAFE */ {
+ osl::MutexGuard g(cppu::WeakComponentImplHelperBase::rBHelper.rMutex);
+ m_eJob = Job::NoJob;
+ if ( bAllowAutoSaveReactivation && bWasAutoSaveActive )
+ {
+ m_eJob |= Job::AutoSave;
+
+ if (bWasUserAutoSaveActive)
+ {
+ m_eJob |= Job::UserAutoSave;
+ }
+ }
+
+ } /* SAFE */
+
+ // depends on bAllowAutoSaveReactivation implicitly by looking on m_eJob=Job::AutoSave! see before ...
+ implts_updateTimer();
+
+ if (bAllowAutoSaveReactivation)
+ implts_startListening();
+}
+
+void SAL_CALL AutoRecovery::addStatusListener(const css::uno::Reference< css::frame::XStatusListener >& xListener,
+ const css::util::URL& aURL )
+{
+ if (!xListener.is())
+ throw css::uno::RuntimeException("Invalid listener reference.", static_cast< css::frame::XDispatch* >(this));
+ // container is threadsafe by using a shared mutex!
+ m_lListener.addInterface(aURL.Complete, xListener);
+
+ // REENTRANT !? -> --------------------------------
+ CacheLockGuard aCacheLock(this, cppu::WeakComponentImplHelperBase::rBHelper.rMutex, m_nDocCacheLock, LOCK_FOR_CACHE_USE);
+
+ /* SAFE */ {
+ osl::ResettableMutexGuard g(cppu::WeakComponentImplHelperBase::rBHelper.rMutex);
+
+ for (auto const& elem : m_lDocCache)
+ {
+ css::frame::FeatureStateEvent aEvent = AutoRecovery::implst_createFeatureStateEvent(m_eJob, OPERATION_UPDATE, &elem);
+
+ // } /* SAFE */
+ g.clear();
+ xListener->statusChanged(aEvent);
+ g.reset();
+ // /* SAFE */ {
+ }
+
+ } /* SAFE */
+}
+
+void SAL_CALL AutoRecovery::removeStatusListener(const css::uno::Reference< css::frame::XStatusListener >& xListener,
+ const css::util::URL& aURL )
+{
+ if (!xListener.is())
+ throw css::uno::RuntimeException("Invalid listener reference.", static_cast< css::frame::XDispatch* >(this));
+ // container is threadsafe by using a shared mutex!
+ m_lListener.removeInterface(aURL.Complete, xListener);
+}
+
+void SAL_CALL AutoRecovery::documentEventOccured(const css::document::DocumentEvent& aEvent)
+{
+ css::uno::Reference< css::frame::XModel3 > xDocument(aEvent.Source, css::uno::UNO_QUERY);
+
+ // new document => put it into the internal list
+ if (
+ (aEvent.EventName == EVENT_ON_NEW) ||
+ (aEvent.EventName == EVENT_ON_LOAD)
+ )
+ {
+ implts_registerDocument(xDocument);
+ }
+ // document modified => set its modify state new (means modified against the original file!)
+ else if ( aEvent.EventName == EVENT_ON_MODIFYCHANGED )
+ {
+ implts_updateModifiedState(xDocument);
+ }
+ /* at least one document starts saving process =>
+ Our application code is not ready for multiple save requests
+ at the same time. So we have to suppress our AutoSave feature
+ for the moment, till this other save requests will be finished.
+ */
+ else if (
+ (aEvent.EventName == EVENT_ON_SAVE) ||
+ (aEvent.EventName == EVENT_ON_SAVEAS) ||
+ (aEvent.EventName == EVENT_ON_SAVETO)
+ )
+ {
+ implts_updateDocumentUsedForSavingState(xDocument, SAVE_IN_PROGRESS);
+ }
+ // document saved => remove tmp. files - but hold config entries alive!
+ else if (
+ (aEvent.EventName == EVENT_ON_SAVEDONE) ||
+ (aEvent.EventName == EVENT_ON_SAVEASDONE)
+ )
+ {
+ SolarMutexGuard g;
+ implts_markDocumentAsSaved(xDocument);
+ implts_updateDocumentUsedForSavingState(xDocument, SAVE_FINISHED);
+ }
+ /* document saved as copy => mark it as "non used by concurrent save operation".
+ so we can try to create a backup copy if next time AutoSave is started too.
+ Don't remove temp. files or change the modified state of the document!
+ It was not really saved to the original file...
+ */
+ else if ( aEvent.EventName == EVENT_ON_SAVETODONE )
+ {
+ implts_updateDocumentUsedForSavingState(xDocument, SAVE_FINISHED);
+ }
+ // If saving of a document failed by an error ... we have to save this document
+ // by ourself next time AutoSave or EmergencySave is triggered.
+ // But we can reset the state "used for other save requests". Otherwise
+ // these documents will never be saved!
+ else if (
+ (aEvent.EventName == EVENT_ON_SAVEFAILED) ||
+ (aEvent.EventName == EVENT_ON_SAVEASFAILED) ||
+ (aEvent.EventName == EVENT_ON_SAVETOFAILED)
+ )
+ {
+ implts_updateDocumentUsedForSavingState(xDocument, SAVE_FINISHED);
+ }
+ // document closed => remove temp. files and configuration entries
+ else if ( aEvent.EventName == EVENT_ON_UNLOAD )
+ {
+ implts_deregisterDocument(xDocument); // sal_True => stop listening for disposing() !
+ }
+}
+
+void SAL_CALL AutoRecovery::changesOccurred(const css::util::ChangesEvent& aEvent)
+{
+ const css::uno::Sequence< css::util::ElementChange > lChanges (aEvent.Changes);
+ const css::util::ElementChange* pChanges = lChanges.getConstArray();
+
+ sal_Int32 c = lChanges.getLength();
+ sal_Int32 i = 0;
+
+ /* SAFE */ {
+ osl::MutexGuard g(cppu::WeakComponentImplHelperBase::rBHelper.rMutex);
+
+ // Changes of the configuration must be ignored if AutoSave/Recovery was disabled for this
+ // office session. That can happen if e.g. the command line arguments "--norestore" or "--headless"
+ // was set.
+ if ((m_eJob & Job::DisableAutorecovery) == Job::DisableAutorecovery)
+ return;
+
+ for (i=0; i<c; ++i)
+ {
+ OUString sPath;
+ pChanges[i].Accessor >>= sPath;
+
+ if ( sPath == CFG_ENTRY_AUTOSAVE_ENABLED )
+ {
+ bool bEnabled = false;
+ if (pChanges[i].Element >>= bEnabled)
+ {
+ if (bEnabled)
+ {
+ m_eJob |= Job::AutoSave;
+ m_eTimerType = AutoRecovery::E_NORMAL_AUTOSAVE_INTERVALL;
+ }
+ else
+ {
+ m_eJob &= ~Job::AutoSave;
+ m_eTimerType = AutoRecovery::E_DONT_START_TIMER;
+ }
+ }
+ }
+ else
+ if ( sPath == CFG_ENTRY_AUTOSAVE_TIMEINTERVALL )
+ pChanges[i].Element >>= m_nAutoSaveTimeIntervall;
+ }
+
+ } /* SAFE */
+
+ // Note: This call stops the timer and starts it again.
+ // But it checks the different timer states internally and
+ // may be suppress the restart!
+ implts_updateTimer();
+}
+
+void SAL_CALL AutoRecovery::modified(const css::lang::EventObject& aEvent)
+{
+ css::uno::Reference< css::frame::XModel > xDocument(aEvent.Source, css::uno::UNO_QUERY);
+ if (! xDocument.is())
+ return;
+
+ implts_markDocumentModifiedAgainstLastBackup(xDocument);
+}
+
+void SAL_CALL AutoRecovery::disposing(const css::lang::EventObject& aEvent)
+{
+ /* SAFE */
+ osl::MutexGuard g(cppu::WeakComponentImplHelperBase::rBHelper.rMutex);
+
+ if (aEvent.Source == m_xNewDocBroadcaster)
+ {
+ m_xNewDocBroadcaster.clear();
+ return;
+ }
+
+ if (aEvent.Source == m_xRecoveryCFG)
+ {
+ m_xRecoveryCFG.clear();
+ return;
+ }
+
+ // dispose from one of our cached documents ?
+ // Normally they should send a OnUnload message ...
+ // But some stacktraces shows another possible use case .-)
+ css::uno::Reference< css::frame::XModel > xDocument(aEvent.Source, css::uno::UNO_QUERY);
+ if (xDocument.is())
+ {
+ implts_deregisterDocument(xDocument, false); // sal_False => don't call removeEventListener() .. because it's not needed here
+ return;
+ }
+
+ /* SAFE */
+}
+
+void AutoRecovery::implts_openConfig()
+{
+ /* SAFE */ {
+ osl::MutexGuard g(cppu::WeakComponentImplHelperBase::rBHelper.rMutex);
+
+ if (m_xRecoveryCFG.is())
+ return;
+ } /* SAFE */
+
+ css::uno::Reference<css::lang::XMultiServiceFactory> xConfigProvider(
+ css::configuration::theDefaultProvider::get(m_xContext));
+
+ std::vector<css::uno::Any> lParams;
+ css::beans::PropertyValue aParam;
+
+ // set root path
+ aParam.Name = "nodepath";
+ aParam.Value <<= OUString(CFG_PACKAGE_RECOVERY);
+ lParams.push_back(css::uno::Any(aParam));
+
+ // throws a RuntimeException if an error occurs!
+ css::uno::Reference<css::container::XNameAccess> xCFG(
+ xConfigProvider->createInstanceWithArguments(
+ "com.sun.star.configuration.ConfigurationAccess",
+ comphelper::containerToSequence(lParams)),
+ css::uno::UNO_QUERY);
+
+ sal_Int32 nMinSpaceDocSave = MIN_DISCSPACE_DOCSAVE;
+ sal_Int32 nMinSpaceConfigSave = MIN_DISCSPACE_CONFIGSAVE;
+
+ try
+ {
+ nMinSpaceDocSave = officecfg::Office::Recovery::AutoSave::MinSpaceDocSave::get();
+ nMinSpaceConfigSave = officecfg::Office::Recovery::AutoSave::MinSpaceConfigSave::get();
+ }
+ catch(const css::uno::Exception&)
+ {
+ // These config keys are not sooooo important, that
+ // we are interested on errors here really .-)
+ nMinSpaceDocSave = MIN_DISCSPACE_DOCSAVE;
+ nMinSpaceConfigSave = MIN_DISCSPACE_CONFIGSAVE;
+ }
+
+ /* SAFE */ {
+ osl::MutexGuard g(cppu::WeakComponentImplHelperBase::rBHelper.rMutex);
+ m_xRecoveryCFG = xCFG;
+ m_nMinSpaceDocSave = nMinSpaceDocSave;
+ m_nMinSpaceConfigSave = nMinSpaceConfigSave;
+ } /* SAFE */
+}
+
+void AutoRecovery::implts_readAutoSaveConfig()
+{
+ implts_openConfig();
+
+ // AutoSave [bool]
+ bool bEnabled(officecfg::Office::Common::Save::Document::AutoSave::get());
+
+ /* SAFE */ {
+ osl::MutexGuard g(cppu::WeakComponentImplHelperBase::rBHelper.rMutex);
+ if (bEnabled)
+ {
+ bool bUserEnabled(officecfg::Office::Recovery::AutoSave::UserAutoSaveEnabled::get());
+
+ m_eJob |= Job::AutoSave;
+ m_eTimerType = AutoRecovery::E_NORMAL_AUTOSAVE_INTERVALL;
+
+ if (bUserEnabled)
+ {
+ m_eJob |= Job::UserAutoSave;
+ }
+ else
+ {
+ m_eJob &= ~Job::UserAutoSave;
+ }
+ }
+ else
+ {
+ m_eJob &= ~Job::AutoSave;
+ m_eTimerType = AutoRecovery::E_DONT_START_TIMER;
+ }
+ } /* SAFE */
+
+ // AutoSaveTimeIntervall [int] in min
+ sal_Int32 nTimeIntervall(
+ officecfg::Office::Common::Save::Document::AutoSaveTimeIntervall::get());
+
+ /* SAFE */ {
+ osl::MutexGuard g(cppu::WeakComponentImplHelperBase::rBHelper.rMutex);
+ m_nAutoSaveTimeIntervall = nTimeIntervall;
+ } /* SAFE */
+}
+
+void AutoRecovery::implts_readConfig()
+{
+ implts_readAutoSaveConfig();
+
+ // REENTRANT -> --------------------------------
+ CacheLockGuard aCacheLock(this, cppu::WeakComponentImplHelperBase::rBHelper.rMutex, m_nDocCacheLock, LOCK_FOR_CACHE_ADD_REMOVE);
+
+ /* SAFE */ {
+ osl::MutexGuard g(cppu::WeakComponentImplHelperBase::rBHelper.rMutex);
+ // reset current cache load cache
+ m_lDocCache.clear();
+ m_nIdPool = 0;
+ } /* SAFE */
+
+ aCacheLock.unlock();
+ // <- REENTRANT --------------------------------
+
+ css::uno::Reference<css::container::XNameAccess> xRecoveryList(
+ officecfg::Office::Recovery::RecoveryList::get());
+ const OUString sRECOVERY_ITEM_BASE_IDENTIFIER(RECOVERY_ITEM_BASE_IDENTIFIER);
+ const css::uno::Sequence< OUString > lItems = xRecoveryList->getElementNames();
+ const OUString* pItems = lItems.getConstArray();
+ sal_Int32 c = lItems.getLength();
+ sal_Int32 i = 0;
+
+ // REENTRANT -> --------------------------
+ aCacheLock.lock(LOCK_FOR_CACHE_ADD_REMOVE);
+
+ for (i=0; i<c; ++i)
+ {
+ css::uno::Reference< css::beans::XPropertySet > xItem;
+ xRecoveryList->getByName(pItems[i]) >>= xItem;
+ if (!xItem.is())
+ continue;
+
+ AutoRecovery::TDocumentInfo aInfo;
+ aInfo.NewTempURL.clear();
+ aInfo.Document.clear();
+ xItem->getPropertyValue(CFG_ENTRY_PROP_ORIGINALURL) >>= aInfo.OrgURL;
+ xItem->getPropertyValue(CFG_ENTRY_PROP_TEMPURL) >>= aInfo.OldTempURL;
+ xItem->getPropertyValue(CFG_ENTRY_PROP_TEMPLATEURL) >>= aInfo.TemplateURL;
+ xItem->getPropertyValue(CFG_ENTRY_PROP_FILTER) >>= aInfo.RealFilter;
+ sal_Int32 tmp = 0;
+ xItem->getPropertyValue(CFG_ENTRY_PROP_DOCUMENTSTATE) >>= tmp;
+ aInfo.DocumentState = DocState(tmp);
+ xItem->getPropertyValue(CFG_ENTRY_PROP_MODULE) >>= aInfo.AppModule;
+ xItem->getPropertyValue(CFG_ENTRY_PROP_TITLE) >>= aInfo.Title;
+ xItem->getPropertyValue(CFG_ENTRY_PROP_VIEWNAMES) >>= aInfo.ViewNames;
+ implts_specifyAppModuleAndFactory(aInfo);
+ implts_specifyDefaultFilterAndExtension(aInfo);
+
+ if (pItems[i].startsWith(sRECOVERY_ITEM_BASE_IDENTIFIER))
+ {
+ std::u16string_view sID = pItems[i].subView(sRECOVERY_ITEM_BASE_IDENTIFIER.getLength());
+ aInfo.ID = o3tl::toInt32(sID);
+ /* SAFE */ {
+ osl::MutexGuard g(cppu::WeakComponentImplHelperBase::rBHelper.rMutex);
+ if (aInfo.ID > m_nIdPool)
+ {
+ m_nIdPool = aInfo.ID+1;
+ SAL_WARN_IF(m_nIdPool<0, "fwk.autorecovery", "AutoRecovery::implts_readConfig(): Overflow of IDPool detected!");
+ }
+ } /* SAFE */
+ }
+ else
+ SAL_INFO("fwk.autorecovery", "AutoRecovery::implts_readConfig(): Who changed numbering of recovery items? Cache will be inconsistent then! I do not know, what will happen next time .-)");
+
+ /* SAFE */ {
+ osl::MutexGuard g(cppu::WeakComponentImplHelperBase::rBHelper.rMutex);
+ m_lDocCache.push_back(aInfo);
+ } /* SAFE */
+ }
+
+ aCacheLock.unlock();
+ // <- REENTRANT --------------------------
+
+ implts_updateTimer();
+}
+
+void AutoRecovery::implts_specifyDefaultFilterAndExtension(AutoRecovery::TDocumentInfo& rInfo)
+{
+ if (rInfo.AppModule.isEmpty())
+ {
+ throw css::uno::RuntimeException(
+ "Can not find out the default filter and its extension, if no application module is known!",
+ static_cast< css::frame::XDispatch* >(this));
+ }
+
+ css::uno::Reference< css::container::XNameAccess> xCFG;
+ /* SAFE */ {
+ osl::MutexGuard g(cppu::WeakComponentImplHelperBase::rBHelper.rMutex);
+ xCFG = m_xModuleCFG;
+ } /* SAFE */
+
+ try
+ {
+ if (! xCFG.is())
+ {
+ implts_openConfig();
+ // open module config on demand and cache the update access
+ xCFG.set(officecfg::Setup::Office::Factories::get(),
+ css::uno::UNO_SET_THROW);
+
+ /* SAFE */ {
+ osl::MutexGuard g2(cppu::WeakComponentImplHelperBase::rBHelper.rMutex);
+ m_xModuleCFG = xCFG;
+ } /* SAFE */
+ }
+
+ css::uno::Reference< css::container::XNameAccess > xModuleProps(
+ xCFG->getByName(rInfo.AppModule),
+ css::uno::UNO_QUERY_THROW);
+
+ xModuleProps->getByName(CFG_ENTRY_REALDEFAULTFILTER) >>= rInfo.DefaultFilter;
+
+ css::uno::Reference< css::container::XNameAccess > xFilterCFG(
+ m_xContext->getServiceManager()->createInstanceWithContext(
+ "com.sun.star.document.FilterFactory", m_xContext), css::uno::UNO_QUERY_THROW);
+ css::uno::Reference< css::container::XNameAccess > xTypeCFG(
+ m_xContext->getServiceManager()->createInstanceWithContext(
+ "com.sun.star.document.TypeDetection", m_xContext), css::uno::UNO_QUERY_THROW);
+
+ ::comphelper::SequenceAsHashMap lFilterProps (xFilterCFG->getByName(rInfo.DefaultFilter));
+ OUString sTypeRegistration = lFilterProps.getUnpackedValueOrDefault(FILTER_PROP_TYPE, OUString());
+ ::comphelper::SequenceAsHashMap lTypeProps (xTypeCFG->getByName(sTypeRegistration));
+ css::uno::Sequence< OUString > lExtensions = lTypeProps.getUnpackedValueOrDefault(TYPE_PROP_EXTENSIONS, css::uno::Sequence< OUString >());
+ if (lExtensions.hasElements())
+ {
+ rInfo.Extension = "." + lExtensions[0];
+ }
+ else
+ rInfo.Extension = ".unknown";
+ }
+ catch(const css::uno::Exception&)
+ {
+ rInfo.DefaultFilter.clear();
+ rInfo.Extension.clear();
+ }
+}
+
+void AutoRecovery::implts_specifyAppModuleAndFactory(AutoRecovery::TDocumentInfo& rInfo)
+{
+ ENSURE_OR_THROW2(
+ !rInfo.AppModule.isEmpty() || rInfo.Document.is(),
+ "Can not find out the application module nor its factory URL, if no application module (or a suitable) document is known!",
+ *this );
+
+ css::uno::Reference< css::frame::XModuleManager2 > xManager = ModuleManager::create(m_xContext);
+
+ if (rInfo.AppModule.isEmpty())
+ rInfo.AppModule = xManager->identify(rInfo.Document);
+
+ ::comphelper::SequenceAsHashMap lModuleDescription(xManager->getByName(rInfo.AppModule));
+ lModuleDescription[OUString(CFG_ENTRY_PROP_EMPTYDOCUMENTURL)] >>= rInfo.FactoryURL;
+ lModuleDescription[OUString(CFG_ENTRY_PROP_FACTORYSERVICE)] >>= rInfo.FactoryService;
+}
+
+void AutoRecovery::implts_collectActiveViewNames( AutoRecovery::TDocumentInfo& i_rInfo )
+{
+ ENSURE_OR_THROW2( i_rInfo.Document.is(), "need at document, at the very least", *this );
+
+ i_rInfo.ViewNames.realloc(0);
+
+ // obtain list of controllers of this document
+ ::std::vector< OUString > aViewNames;
+ const Reference< XModel2 > xModel( i_rInfo.Document, UNO_QUERY );
+ if ( xModel.is() )
+ {
+ const Reference< css::container::XEnumeration > xEnumControllers( xModel->getControllers() );
+ while ( xEnumControllers->hasMoreElements() )
+ {
+ const Reference< XController2 > xController( xEnumControllers->nextElement(), UNO_QUERY );
+ OUString sViewName;
+ if ( xController.is() )
+ sViewName = xController->getViewControllerName();
+ OSL_ENSURE( !sViewName.isEmpty(), "AutoRecovery::implts_collectActiveViewNames: (no XController2 ->) no view name -> no recovery of this view!" );
+
+ if ( !sViewName.isEmpty() )
+ aViewNames.push_back( sViewName );
+ }
+ }
+
+ i_rInfo.ViewNames.realloc( aViewNames.size() );
+ ::std::copy( aViewNames.begin(), aViewNames.end(), i_rInfo.ViewNames.getArray() );
+}
+
+void AutoRecovery::implts_persistAllActiveViewNames()
+{
+ osl::MutexGuard g(cppu::WeakComponentImplHelperBase::rBHelper.rMutex);
+
+ // This list will be filled with every document
+ for (auto & elem : m_lDocCache)
+ {
+ implts_collectActiveViewNames(elem);
+ implts_flushConfigItem(elem);
+ }
+}
+
+void AutoRecovery::implts_flushConfigItem(const AutoRecovery::TDocumentInfo& rInfo, bool bRemoveIt)
+{
+ std::shared_ptr<comphelper::ConfigurationChanges> batch(
+ comphelper::ConfigurationChanges::create());
+
+ try
+ {
+ implts_openConfig();
+
+ css::uno::Reference<css::container::XNameAccess> xCheck(
+ officecfg::Office::Recovery::RecoveryList::get(batch));
+
+ css::uno::Reference< css::container::XNameContainer > xModify(xCheck, css::uno::UNO_QUERY_THROW);
+ css::uno::Reference< css::lang::XSingleServiceFactory > xCreate(xCheck, css::uno::UNO_QUERY_THROW);
+
+ OUString sID = RECOVERY_ITEM_BASE_IDENTIFIER + OUString::number(rInfo.ID);
+
+ // remove
+ if (bRemoveIt)
+ {
+ // Catch NoSuchElementException.
+ // It's not a good idea inside multithreaded environments to call hasElement - removeElement.
+ // DO IT!
+ try
+ {
+ xModify->removeByName(sID);
+ }
+ catch(const css::container::NoSuchElementException&)
+ {
+ return;
+ }
+ }
+ else
+ {
+ // new/modify
+ css::uno::Reference< css::beans::XPropertySet > xSet;
+ bool bNew = !xCheck->hasByName(sID);
+ if (bNew)
+ xSet.set(xCreate->createInstance(), css::uno::UNO_QUERY_THROW);
+ else
+ xCheck->getByName(sID) >>= xSet;
+
+ xSet->setPropertyValue(CFG_ENTRY_PROP_ORIGINALURL, css::uno::Any(rInfo.OrgURL ));
+ xSet->setPropertyValue(CFG_ENTRY_PROP_TEMPURL, css::uno::Any(rInfo.OldTempURL ));
+ xSet->setPropertyValue(CFG_ENTRY_PROP_TEMPLATEURL, css::uno::Any(rInfo.TemplateURL ));
+ xSet->setPropertyValue(CFG_ENTRY_PROP_FILTER, css::uno::Any(rInfo.RealFilter));
+ xSet->setPropertyValue(CFG_ENTRY_PROP_DOCUMENTSTATE, css::uno::Any(sal_Int32(rInfo.DocumentState)));
+ xSet->setPropertyValue(CFG_ENTRY_PROP_MODULE, css::uno::Any(rInfo.AppModule));
+ xSet->setPropertyValue(CFG_ENTRY_PROP_TITLE, css::uno::Any(rInfo.Title));
+ xSet->setPropertyValue(CFG_ENTRY_PROP_VIEWNAMES, css::uno::Any(rInfo.ViewNames));
+
+ if (bNew)
+ xModify->insertByName(sID, css::uno::Any(xSet));
+ }
+ }
+ catch(const css::uno::RuntimeException&)
+ {
+ throw;
+ }
+ catch(const css::uno::Exception&)
+ {
+ // ??? can it happen that a full disc let these set of operations fail too ???
+ }
+
+ sal_Int32 nRetry = RETRY_STORE_ON_FULL_DISC_FOREVER;
+ do
+ {
+ try
+ {
+ batch->commit();
+
+#ifdef TRIGGER_FULL_DISC_CHECK
+ throw css::uno::Exception("trigger full disk check");
+#else // TRIGGER_FULL_DISC_CHECK
+ nRetry = 0;
+#endif // TRIGGER_FULL_DISC_CHECK
+ }
+ catch(const css::uno::Exception&)
+ {
+ // a) FULL DISC seems to be the problem behind => show error and retry it forever (e.g. retry=300)
+ // b) unknown problem (may be locking problem) => reset RETRY value to more useful value(!) (e.g. retry=3)
+ // c) unknown problem (may be locking problem) + 1..2 repeating operations => throw the original exception to force generation of a stacktrace !
+
+ sal_Int32 nMinSpaceConfigSave;
+ /* SAFE */ {
+ osl::MutexGuard g(cppu::WeakComponentImplHelperBase::rBHelper.rMutex);
+ nMinSpaceConfigSave = m_nMinSpaceConfigSave;
+ } /* SAFE */
+
+ if (! impl_enoughDiscSpace(nMinSpaceConfigSave))
+ AutoRecovery::impl_showFullDiscError();
+ else if (nRetry > RETRY_STORE_ON_MIGHT_FULL_DISC_USEFULL)
+ nRetry = RETRY_STORE_ON_MIGHT_FULL_DISC_USEFULL;
+ else if (nRetry <= GIVE_UP_RETRY)
+ throw; // force stacktrace to know if there exist might other reasons, why an AutoSave can fail !!!
+
+ --nRetry;
+ }
+ }
+ while(nRetry>0);
+}
+
+void AutoRecovery::implts_startListening()
+{
+ css::uno::Reference< css::util::XChangesNotifier > xCFG;
+ css::uno::Reference< css::frame::XGlobalEventBroadcaster > xBroadcaster;
+ bool bListenForDocEvents;
+ bool bListenForConfigChanges;
+ /* SAFE */ {
+ osl::MutexGuard g(cppu::WeakComponentImplHelperBase::rBHelper.rMutex);
+ xCFG.set (m_xRecoveryCFG, css::uno::UNO_QUERY);
+ xBroadcaster = m_xNewDocBroadcaster;
+ bListenForDocEvents = m_bListenForDocEvents;
+ bListenForConfigChanges = m_bListenForConfigChanges;
+ } /* SAFE */
+
+ if (
+ ( xCFG.is() ) &&
+ (! bListenForConfigChanges)
+ )
+ {
+ css::uno::Reference<css::util::XChangesListener> const xListener(
+ new WeakChangesListener(this));
+ xCFG->addChangesListener(xListener);
+ /* SAFE */ {
+ osl::MutexGuard g2(cppu::WeakComponentImplHelperBase::rBHelper.rMutex);
+ m_xRecoveryCFGListener = xListener;
+ m_bListenForConfigChanges = true;
+ } /* SAFE */
+ }
+
+ if (!xBroadcaster.is())
+ {
+ xBroadcaster = css::frame::theGlobalEventBroadcaster::get(m_xContext);
+ /* SAFE */ {
+ osl::MutexGuard g2(cppu::WeakComponentImplHelperBase::rBHelper.rMutex);
+ m_xNewDocBroadcaster = xBroadcaster;
+ } /* SAFE */
+ }
+
+ if (
+ ( xBroadcaster.is() ) &&
+ (! bListenForDocEvents)
+ )
+ {
+ css::uno::Reference<css::document::XDocumentEventListener> const
+ xListener(new WeakDocumentEventListener(this));
+ xBroadcaster->addDocumentEventListener(xListener);
+ /* SAFE */ {
+ osl::MutexGuard g2(cppu::WeakComponentImplHelperBase::rBHelper.rMutex);
+ m_xNewDocBroadcasterListener = xListener;
+ m_bListenForDocEvents = true;
+ } /* SAFE */
+ }
+}
+
+void AutoRecovery::implts_stopListening()
+{
+ css::uno::Reference< css::util::XChangesNotifier > xCFG;
+ css::uno::Reference< css::document::XDocumentEventBroadcaster > xGlobalEventBroadcaster;
+ /* SAFE */ {
+ osl::MutexGuard g(cppu::WeakComponentImplHelperBase::rBHelper.rMutex);
+ // Attention: Don't reset our internal members here too.
+ // May be we must work with our configuration, but don't wish to be informed
+ // about changes any longer. Needed e.g. during Job::EmergencySave!
+ xCFG.set (m_xRecoveryCFG , css::uno::UNO_QUERY);
+ xGlobalEventBroadcaster = m_xNewDocBroadcaster;
+ } /* SAFE */
+
+ if (xGlobalEventBroadcaster.is() && m_bListenForDocEvents)
+ {
+ xGlobalEventBroadcaster->removeDocumentEventListener(m_xNewDocBroadcasterListener);
+ m_bListenForDocEvents = false;
+ }
+
+ if (xCFG.is() && m_bListenForConfigChanges)
+ {
+ xCFG->removeChangesListener(m_xRecoveryCFGListener);
+ m_bListenForConfigChanges = false;
+ }
+}
+
+void AutoRecovery::implts_startModifyListeningOnDoc(AutoRecovery::TDocumentInfo& rInfo)
+{
+ if (rInfo.ListenForModify)
+ return;
+
+ css::uno::Reference< css::util::XModifyBroadcaster > xBroadcaster(rInfo.Document, css::uno::UNO_QUERY);
+ if (xBroadcaster.is())
+ {
+ css::uno::Reference< css::util::XModifyListener > xThis(this);
+ xBroadcaster->addModifyListener(xThis);
+ rInfo.ListenForModify = true;
+ }
+}
+
+void AutoRecovery::implts_stopModifyListeningOnDoc(AutoRecovery::TDocumentInfo& rInfo)
+{
+ if (! rInfo.ListenForModify)
+ return;
+
+ css::uno::Reference< css::util::XModifyBroadcaster > xBroadcaster(rInfo.Document, css::uno::UNO_QUERY);
+ if (xBroadcaster.is())
+ {
+ css::uno::Reference< css::util::XModifyListener > xThis(this);
+ xBroadcaster->removeModifyListener(xThis);
+ rInfo.ListenForModify = false;
+ }
+}
+
+void AutoRecovery::implts_updateTimer()
+{
+ implts_stopTimer();
+
+ sal_Int32 nMilliSeconds = 0;
+
+ /* SAFE */ {
+ osl::MutexGuard g(cppu::WeakComponentImplHelperBase::rBHelper.rMutex);
+
+ if (
+ (m_eJob == Job::NoJob ) || // TODO may be superfluous - E_DONT_START_TIMER should be used only
+ (m_eTimerType == AutoRecovery::E_DONT_START_TIMER)
+ )
+ return;
+
+ if (m_eTimerType == AutoRecovery::E_NORMAL_AUTOSAVE_INTERVALL)
+ {
+ nMilliSeconds = (m_nAutoSaveTimeIntervall*60000); // [min] => 60.000 ms
+ }
+ else if (m_eTimerType == AutoRecovery::E_POLL_FOR_USER_IDLE)
+ {
+ nMilliSeconds = MIN_TIME_FOR_USER_IDLE;
+ }
+ else if (m_eTimerType == AutoRecovery::E_POLL_TILL_AUTOSAVE_IS_ALLOWED)
+ nMilliSeconds = 300; // there is a minimum time frame, where the user can lose some key input data!
+
+
+ } /* SAFE */
+
+ SolarMutexGuard g;
+ m_aTimer.SetTimeout(nMilliSeconds);
+ m_aTimer.Start();
+}
+
+void AutoRecovery::implts_stopTimer()
+{
+ SolarMutexGuard g;
+
+ if (!m_aTimer.IsActive())
+ return;
+ m_aTimer.Stop();
+}
+
+IMPL_LINK_NOARG(AutoRecovery, implts_timerExpired, Timer *, void)
+{
+ try
+ {
+ // This method is called by using a pointer to us.
+ // But we must be aware that we can be destroyed hardly
+ // if our uno reference will be gone!
+ // => Hold this object alive till this method finish its work.
+ css::uno::Reference< css::uno::XInterface > xSelfHold(static_cast< css::lang::XTypeProvider* >(this));
+
+ // Needed! Otherwise every reschedule request allow a new triggered timer event :-(
+ implts_stopTimer();
+
+ // The timer must be ignored if AutoSave/Recovery was disabled for this
+ // office session. That can happen if e.g. the command line arguments "--norestore" or "--headless"
+ // was set. But normally the timer was disabled if recovery was disabled ...
+ // But so we are more "safe" .-)
+ /* SAFE */ {
+ osl::MutexGuard g(cppu::WeakComponentImplHelperBase::rBHelper.rMutex);
+ if ((m_eJob & Job::DisableAutorecovery) == Job::DisableAutorecovery)
+ return;
+ } /* SAFE */
+
+ // check some "states", where it's not allowed (better: not a good idea) to
+ // start an AutoSave. (e.g. if the user makes drag & drop ...)
+ // Then we poll till this "disallowed" state is gone.
+ bool bAutoSaveNotAllowed = Application::IsUICaptured();
+ if (bAutoSaveNotAllowed)
+ {
+ /* SAFE */ {
+ osl::MutexGuard g(cppu::WeakComponentImplHelperBase::rBHelper.rMutex);
+ m_eTimerType = AutoRecovery::E_POLL_TILL_AUTOSAVE_IS_ALLOWED;
+ } /* SAFE */
+ implts_updateTimer();
+ return;
+ }
+
+ // analyze timer type.
+ // If we poll for an user idle period, may be we must
+ // do nothing here and start the timer again.
+ /* SAFE */ {
+ osl::ClearableMutexGuard g(cppu::WeakComponentImplHelperBase::rBHelper.rMutex);
+
+ if (m_eTimerType == AutoRecovery::E_POLL_FOR_USER_IDLE)
+ {
+ bool bUserIdle = Application::GetLastInputInterval() > MIN_TIME_FOR_USER_IDLE;
+ if (!bUserIdle)
+ {
+ g.clear();
+ implts_updateTimer();
+ return;
+ }
+ }
+
+ } /* SAFE */
+
+ implts_informListener(Job::AutoSave,
+ AutoRecovery::implst_createFeatureStateEvent(Job::AutoSave, OPERATION_START, nullptr));
+
+ // force save of all currently open documents
+ // The called method returns an info, if and how this
+ // timer must be restarted.
+ AutoRecovery::ETimerType eSuggestedTimer = implts_saveDocs(true/*bAllowUserIdleLoop*/, false);
+
+ // If timer is not used for "short callbacks" (means polling
+ // for special states) ... reset the handle state of all
+ // cache items. Such handle state indicates, that a document
+ // was already saved during the THIS(!) AutoSave session.
+ // Of course NEXT AutoSave session must be started without
+ // any "handle" state ...
+ if (
+ (eSuggestedTimer == AutoRecovery::E_DONT_START_TIMER ) ||
+ (eSuggestedTimer == AutoRecovery::E_NORMAL_AUTOSAVE_INTERVALL)
+ )
+ {
+ implts_resetHandleStates();
+ }
+
+ implts_informListener(Job::AutoSave,
+ AutoRecovery::implst_createFeatureStateEvent(Job::AutoSave, OPERATION_STOP, nullptr));
+
+ // restart timer - because it was disabled before ...
+ /* SAFE */ {
+ osl::MutexGuard g(cppu::WeakComponentImplHelperBase::rBHelper.rMutex);
+ m_eTimerType = eSuggestedTimer;
+ } /* SAFE */
+
+ implts_updateTimer();
+ }
+ catch(const css::uno::Exception&)
+ {
+ }
+}
+
+IMPL_LINK_NOARG(AutoRecovery, implts_asyncDispatch, LinkParamNone*, void)
+{
+ DispatchParams aParams;
+ /* SAFE */ {
+ osl::MutexGuard g(cppu::WeakComponentImplHelperBase::rBHelper.rMutex);
+ aParams = m_aDispatchParams;
+ css::uno::Reference< css::uno::XInterface > xHoldRefForMethodAlive = aParams.m_xHoldRefForAsyncOpAlive;
+ m_aDispatchParams.forget(); // clears all members ... including the ref-hold object .-)
+ } /* SAFE */
+
+ try
+ {
+ implts_dispatch(aParams);
+ }
+ catch (...)
+ {
+ }
+}
+
+void AutoRecovery::implts_registerDocument(const css::uno::Reference< css::frame::XModel3 > & xDocument)
+{
+ // ignore corrupted events, where no document is given ... Runtime Error ?!
+ if (!xDocument.is())
+ return;
+
+ CacheLockGuard aCacheLock(this, cppu::WeakComponentImplHelperBase::rBHelper.rMutex, m_nDocCacheLock, LOCK_FOR_CACHE_USE);
+
+ // notification for already existing document !
+ // Can happen if events came in asynchronous on recovery time.
+ // Then our cache was filled from the configuration ... but now we get some
+ // asynchronous events from the global event broadcaster. We must be sure that
+ // we don't add the same document more than once.
+ AutoRecovery::TDocumentList::iterator pIt = AutoRecovery::impl_searchDocument(m_lDocCache, xDocument);
+ if (pIt != m_lDocCache.end())
+ {
+ // Normally nothing must be done for this "late" notification.
+ // But may be the modified state was changed inbetween.
+ // Check it...
+ implts_updateModifiedState(xDocument);
+ return;
+ }
+
+ aCacheLock.unlock();
+
+ utl::MediaDescriptor lDescriptor(xDocument->getArgs2( { utl::MediaDescriptor::PROP_FILTERNAME, utl::MediaDescriptor::PROP_NOAUTOSAVE } ));
+
+ // check if this document must be ignored for recovery !
+ // Some use cases don't wish support for AutoSave/Recovery ... as e.g. OLE-Server / ActiveX Control etcpp.
+ bool bNoAutoSave = lDescriptor.getUnpackedValueOrDefault(utl::MediaDescriptor::PROP_NOAUTOSAVE, false);
+ if (bNoAutoSave)
+ return;
+
+ // Check if doc is well known on the desktop. Otherwise ignore it!
+ // Other frames mostly are used from external programs - e.g. the bean ...
+ css::uno::Reference< css::frame::XController > xController = xDocument->getCurrentController();
+ if (!xController.is())
+ return;
+
+ css::uno::Reference< css::frame::XFrame > xFrame = xController->getFrame();
+ if (!xFrame.is())
+ return;
+ css::uno::Reference< css::frame::XDesktop > xDesktop (xFrame->getCreator(), css::uno::UNO_QUERY);
+ if (!xDesktop.is())
+ return;
+
+ // if the document doesn't support the XDocumentRecovery interface, we're not interested in it.
+ Reference< XDocumentRecovery > xDocRecovery( xDocument, UNO_QUERY );
+ if ( !xDocRecovery.is() )
+ return;
+
+ // get all needed information of this document
+ // We need it to update our cache or to locate already existing elements there!
+ AutoRecovery::TDocumentInfo aNew;
+ aNew.Document = xDocument;
+
+ // TODO replace getLocation() with getURL() ... it's a workaround currently only!
+ css::uno::Reference< css::frame::XStorable > xDoc(aNew.Document, css::uno::UNO_QUERY_THROW);
+ aNew.OrgURL = xDoc->getLocation();
+
+ css::uno::Reference< css::frame::XTitle > xTitle(aNew.Document, css::uno::UNO_QUERY_THROW);
+ aNew.Title = xTitle->getTitle ();
+
+ // classify the used application module, which is used by this document.
+ implts_specifyAppModuleAndFactory(aNew);
+
+ // Hack! Check for "illegal office documents"... as e.g. the Basic IDE
+ // It's not really a full featured office document. It doesn't provide a URL, any filter, a factory URL etcpp.
+ // TODO file bug to Basic IDE developers. They must remove the office document API from its service.
+ if (
+ (aNew.OrgURL.isEmpty()) &&
+ (aNew.FactoryURL.isEmpty())
+ )
+ {
+ OSL_FAIL( "AutoRecovery::implts_registerDocument: this should not happen anymore!" );
+ // nowadays, the Basic IDE should already die on the "supports XDocumentRecovery" check. And no other known
+ // document type fits in here ...
+ return;
+ }
+
+ // By the way - get some information about the default format for saving!
+ // and save an information about the real used filter by this document.
+ // We save this document with DefaultFilter ... and load it with the RealFilter.
+ implts_specifyDefaultFilterAndExtension(aNew);
+ aNew.RealFilter = lDescriptor.getUnpackedValueOrDefault(utl::MediaDescriptor::PROP_FILTERNAME, OUString());
+
+ // Further we must know, if this document base on a template.
+ // Then we must load it in a different way.
+ css::uno::Reference< css::document::XDocumentPropertiesSupplier > xSupplier(aNew.Document, css::uno::UNO_QUERY);
+ if (xSupplier.is()) // optional interface!
+ {
+ css::uno::Reference< css::document::XDocumentProperties > xDocProps(xSupplier->getDocumentProperties(), css::uno::UNO_SET_THROW);
+ aNew.TemplateURL = xDocProps->getTemplateURL();
+ }
+
+ css::uno::Reference< css::util::XModifiable > xModifyCheck(xDocument, css::uno::UNO_QUERY_THROW);
+ if (xModifyCheck->isModified())
+ {
+ aNew.DocumentState |= DocState::Modified;
+ }
+
+ aCacheLock.lock(LOCK_FOR_CACHE_ADD_REMOVE);
+
+ AutoRecovery::TDocumentInfo aInfo;
+ /* SAFE */ {
+ osl::MutexGuard g(cppu::WeakComponentImplHelperBase::rBHelper.rMutex);
+
+ // create a new cache entry ... this document is not known.
+ ++m_nIdPool;
+ aNew.ID = m_nIdPool;
+ SAL_WARN_IF(m_nIdPool<0, "fwk.autorecovery", "AutoRecovery::implts_registerDocument(): Overflow of ID pool detected.");
+ m_lDocCache.push_back(aNew);
+
+ AutoRecovery::TDocumentList::iterator pIt1 = AutoRecovery::impl_searchDocument(m_lDocCache, xDocument);
+ aInfo = *pIt1;
+
+ } /* SAFE */
+
+ implts_flushConfigItem(aInfo);
+ implts_startModifyListeningOnDoc(aInfo);
+
+ aCacheLock.unlock();
+}
+
+void AutoRecovery::implts_deregisterDocument(const css::uno::Reference< css::frame::XModel >& xDocument ,
+ bool bStopListening)
+{
+ AutoRecovery::TDocumentInfo aInfo;
+ /* SAFE */ {
+ osl::MutexGuard g(cppu::WeakComponentImplHelperBase::rBHelper.rMutex);
+
+ // Attention: Don't leave SAFE section, if you work with pIt!
+ // Because it points directly into the m_lDocCache list ...
+ CacheLockGuard aCacheLock(this, cppu::WeakComponentImplHelperBase::rBHelper.rMutex, m_nDocCacheLock, LOCK_FOR_CACHE_USE);
+
+ AutoRecovery::TDocumentList::iterator pIt = AutoRecovery::impl_searchDocument(m_lDocCache, xDocument);
+ if (pIt == m_lDocCache.end())
+ return; // unknown document => not a runtime error! Because we register only a few documents. see registration ...
+
+ aInfo = *pIt;
+
+ aCacheLock.unlock();
+
+ // Sometimes we close documents by ourself.
+ // And these documents can't be deregistered.
+ // Otherwise we lose our configuration data... but need it !
+ // see SessionSave !
+ if (aInfo.IgnoreClosing)
+ return;
+
+ CacheLockGuard aCacheLock2(this, cppu::WeakComponentImplHelperBase::rBHelper.rMutex, m_nDocCacheLock, LOCK_FOR_CACHE_ADD_REMOVE);
+ pIt = AutoRecovery::impl_searchDocument(m_lDocCache, xDocument);
+ if (pIt != m_lDocCache.end())
+ m_lDocCache.erase(pIt);
+ pIt = m_lDocCache.end(); // otherwise it's not specified what pIt means!
+ aCacheLock2.unlock();
+
+ } /* SAFE */
+
+ /* This method is called within disposing() of the document too. But there it's not a good idea to
+ deregister us as listener. Further it makes no sense - because the broadcaster dies.
+ So we suppress deregistration in such case...
+ */
+ if (bStopListening)
+ implts_stopModifyListeningOnDoc(aInfo);
+
+ AutoRecovery::st_impl_removeFile(aInfo.OldTempURL);
+ AutoRecovery::st_impl_removeFile(aInfo.NewTempURL);
+ implts_flushConfigItem(aInfo, true); // sal_True => remove it from config
+}
+
+void AutoRecovery::implts_markDocumentModifiedAgainstLastBackup(const css::uno::Reference< css::frame::XModel >& xDocument)
+{
+ CacheLockGuard aCacheLock(this, cppu::WeakComponentImplHelperBase::rBHelper.rMutex, m_nDocCacheLock, LOCK_FOR_CACHE_USE);
+
+ /* SAFE */ {
+ osl::MutexGuard g(cppu::WeakComponentImplHelperBase::rBHelper.rMutex);
+
+ AutoRecovery::TDocumentList::iterator pIt = AutoRecovery::impl_searchDocument(m_lDocCache, xDocument);
+ if (pIt != m_lDocCache.end())
+ {
+ /* Now we know, that this document was modified again and must be saved next time.
+ But we don't need this information for every e.g. key input of the user.
+ So we stop listening here.
+ But if the document was saved as temp. file we start listening for this event again.
+ */
+ implts_stopModifyListeningOnDoc(*pIt);
+ }
+
+ } /* SAFE */
+}
+
+void AutoRecovery::implts_updateModifiedState(const css::uno::Reference< css::frame::XModel >& xDocument)
+{
+ // use true as fallback to get every document on EmergencySave/AutoRecovery!
+ bool bModified = true;
+ css::uno::Reference< css::util::XModifiable > xModify(xDocument, css::uno::UNO_QUERY);
+ if (xModify.is())
+ bModified = xModify->isModified();
+
+ CacheLockGuard aCacheLock(this, cppu::WeakComponentImplHelperBase::rBHelper.rMutex, m_nDocCacheLock, LOCK_FOR_CACHE_USE);
+
+ /* SAFE */ {
+ osl::MutexGuard g(cppu::WeakComponentImplHelperBase::rBHelper.rMutex);
+
+ AutoRecovery::TDocumentList::iterator pIt = AutoRecovery::impl_searchDocument(m_lDocCache, xDocument);
+ if (pIt != m_lDocCache.end())
+ {
+ AutoRecovery::TDocumentInfo& rInfo = *pIt;
+
+ if (bModified)
+ {
+ rInfo.DocumentState |= DocState::Modified;
+ }
+ else
+ {
+ rInfo.DocumentState &= ~DocState::Modified;
+ }
+ }
+
+ } /* SAFE */
+}
+
+void AutoRecovery::implts_updateDocumentUsedForSavingState(const css::uno::Reference< css::frame::XModel >& xDocument ,
+ bool bSaveInProgress)
+{
+ CacheLockGuard aCacheLock(this, cppu::WeakComponentImplHelperBase::rBHelper.rMutex, m_nDocCacheLock, LOCK_FOR_CACHE_USE);
+
+ /* SAFE */ {
+ osl::MutexGuard g(cppu::WeakComponentImplHelperBase::rBHelper.rMutex);
+
+ AutoRecovery::TDocumentList::iterator pIt = AutoRecovery::impl_searchDocument(m_lDocCache, xDocument);
+ if (pIt == m_lDocCache.end())
+ return;
+ AutoRecovery::TDocumentInfo& rInfo = *pIt;
+ rInfo.UsedForSaving = bSaveInProgress;
+
+ } /* SAFE */
+}
+
+void AutoRecovery::implts_markDocumentAsSaved(const css::uno::Reference< css::frame::XModel >& xDocument)
+{
+ CacheLockGuard aCacheLock(this, cppu::WeakComponentImplHelperBase::rBHelper.rMutex, m_nDocCacheLock, LOCK_FOR_CACHE_USE);
+
+ AutoRecovery::TDocumentInfo aInfo;
+ OUString sRemoveURL1;
+ OUString sRemoveURL2;
+ /* SAFE */ {
+ osl::MutexGuard g(cppu::WeakComponentImplHelperBase::rBHelper.rMutex);
+
+ AutoRecovery::TDocumentList::iterator pIt = AutoRecovery::impl_searchDocument(m_lDocCache, xDocument);
+ if (pIt == m_lDocCache.end())
+ return;
+ aInfo = *pIt;
+
+ /* Since the document has been saved, update its entry in the document
+ * cache. We essentially reset the state of the document from an
+ * autorecovery perspective, updating things like the filename (which
+ * would change in the case of a 'Save as' operation) and the associated
+ * backup file URL. */
+
+ aInfo.DocumentState = DocState::Unknown;
+ // TODO replace getLocation() with getURL() ... it's a workaround currently only!
+ css::uno::Reference< css::frame::XStorable > xDoc(aInfo.Document, css::uno::UNO_QUERY);
+ aInfo.OrgURL = xDoc->getLocation();
+
+ /* Save off the backup file URLs and then clear them. NOTE - it is
+ * important that we clear them - otherwise, we could enter a state
+ * where pIt->OldTempURL == pIt->NewTempURL and our backup algorithm
+ * in implts_saveOneDoc will write to that URL and then delete the file
+ * at that URL (bug #96607) */
+ sRemoveURL1 = aInfo.OldTempURL;
+ sRemoveURL2 = aInfo.NewTempURL;
+ aInfo.OldTempURL.clear();
+ aInfo.NewTempURL.clear();
+
+ utl::MediaDescriptor lDescriptor(aInfo.Document->getArgs());
+ aInfo.RealFilter = lDescriptor.getUnpackedValueOrDefault(utl::MediaDescriptor::PROP_FILTERNAME, OUString());
+
+ css::uno::Reference< css::frame::XTitle > xDocTitle(xDocument, css::uno::UNO_QUERY);
+ if (xDocTitle.is ())
+ aInfo.Title = xDocTitle->getTitle ();
+ else
+ {
+ aInfo.Title = lDescriptor.getUnpackedValueOrDefault(utl::MediaDescriptor::PROP_TITLE, OUString());
+ if (aInfo.Title.isEmpty())
+ aInfo.Title = lDescriptor.getUnpackedValueOrDefault(utl::MediaDescriptor::PROP_DOCUMENTTITLE, OUString());
+ }
+
+ aInfo.UsedForSaving = false;
+
+ *pIt = aInfo;
+
+ } /* SAFE */
+
+ implts_flushConfigItem(aInfo);
+
+ aCacheLock.unlock();
+
+ AutoRecovery::st_impl_removeFile(sRemoveURL1);
+ AutoRecovery::st_impl_removeFile(sRemoveURL2);
+}
+
+AutoRecovery::TDocumentList::iterator AutoRecovery::impl_searchDocument( AutoRecovery::TDocumentList& rList ,
+ const css::uno::Reference< css::frame::XModel >& xDocument)
+{
+ return std::find_if(rList.begin(), rList.end(),
+ [&xDocument](const AutoRecovery::TDocumentInfo& rInfo) { return rInfo.Document == xDocument; });
+}
+
+void lcl_changeVisibility( const css::uno::Reference< css::frame::XFramesSupplier >& i_rFrames, bool i_bVisible )
+{
+ css::uno::Reference< css::container::XIndexAccess > xFramesContainer = i_rFrames->getFrames();
+ const sal_Int32 count = xFramesContainer->getCount();
+
+ Any aElement;
+ for ( sal_Int32 i=0; i < count; ++i )
+ {
+ aElement = xFramesContainer->getByIndex(i);
+ // check for sub frames
+ css::uno::Reference< css::frame::XFramesSupplier > xFramesSupp( aElement, css::uno::UNO_QUERY );
+ if ( xFramesSupp.is() )
+ lcl_changeVisibility( xFramesSupp, i_bVisible );
+
+ css::uno::Reference< css::frame::XFrame > xFrame( aElement, css::uno::UNO_QUERY );
+ if ( !xFrame.is() )
+ continue;
+
+ css::uno::Reference< css::awt::XWindow > xWindow( xFrame->getContainerWindow(), UNO_SET_THROW );
+ xWindow->setVisible( i_bVisible );
+ }
+}
+
+void AutoRecovery::implts_changeAllDocVisibility(bool bVisible)
+{
+ css::uno::Reference< css::frame::XFramesSupplier > xDesktop = css::frame::Desktop::create(m_xContext);
+ lcl_changeVisibility( xDesktop, bVisible );
+}
+
+/* Currently the document is not closed in case of crash,
+ so the lock file must be removed explicitly
+*/
+void lc_removeLockFile(AutoRecovery::TDocumentInfo const & rInfo)
+{
+#if !HAVE_FEATURE_MULTIUSER_ENVIRONMENT || HAVE_FEATURE_MACOSX_SANDBOX
+ (void) rInfo;
+#else
+ if ( !rInfo.Document.is() )
+ return;
+
+ try
+ {
+ css::uno::Reference< css::frame::XStorable > xStore(rInfo.Document, css::uno::UNO_QUERY_THROW);
+ OUString aURL = xStore->getLocation();
+ if ( !aURL.isEmpty() )
+ {
+ ::svt::DocumentLockFile aLockFile( aURL );
+ aLockFile.RemoveFile();
+ }
+ }
+ catch( const css::uno::Exception& )
+ {
+ }
+#endif
+}
+
+void AutoRecovery::implts_prepareSessionShutdown()
+{
+ SAL_INFO("fwk.autorecovery", "AutoRecovery::implts_prepareSessionShutdown() starts ...");
+
+ // a) reset modified documents (of course the must be saved before this method is called!)
+ // b) close it without showing any UI!
+
+ /* SAFE */ {
+ CacheLockGuard aCacheLock(this, cppu::WeakComponentImplHelperBase::rBHelper.rMutex, m_nDocCacheLock, LOCK_FOR_CACHE_USE);
+
+ for (auto & info : m_lDocCache)
+ {
+ // WORKAROUND... Since the documents are not closed the lock file must be removed explicitly
+ // it is not done on documents saving since shutdown can be cancelled
+ lc_removeLockFile( info );
+
+ // Prevent us from deregistration of these documents.
+ // Because we close these documents by ourself (see XClosable below) ...
+ // it's fact, that we reach our deregistration method. There we
+ // must not(!) update our configuration ... Otherwise all
+ // session data are lost !!!
+ info.IgnoreClosing = true;
+
+ // reset modified flag of these documents (ignoring the notification about it!)
+ // Otherwise a message box is shown on closing these models.
+ implts_stopModifyListeningOnDoc(info);
+
+ // if the session save is still running the documents should not be thrown away,
+ // actually that would be a bad sign, that means that the SessionManager tries
+ // to kill the session before the saving is ready
+ if ((m_eJob & Job::SessionSave) != Job::SessionSave)
+ {
+ css::uno::Reference< css::util::XModifiable > xModify(info.Document, css::uno::UNO_QUERY);
+ if (xModify.is())
+ xModify->setModified(false);
+
+ // close the model.
+ css::uno::Reference< css::util::XCloseable > xClose(info.Document, css::uno::UNO_QUERY);
+ if (xClose.is())
+ {
+ try
+ {
+ xClose->close(false);
+ }
+ catch(const css::uno::Exception&)
+ {
+ // At least it's only a try to close these documents before anybody else it does.
+ // So it seems to be possible to ignore any error here .-)
+ }
+
+ info.Document.clear();
+ }
+ }
+ }
+
+ aCacheLock.unlock();
+ } /* SAFE */
+}
+
+/* TODO WORKAROUND:
+
+ #i64599#
+
+ Normally the MediaDescriptor argument NoAutoSave indicates,
+ that a document must be ignored for AutoSave and Recovery.
+ But sometimes XModel->getArgs() does not contained this information
+ if implts_registerDocument() was called.
+ So we have to check a second time, if this property is set...
+ Best place doing so is to check it immediately before saving
+ and suppressing saving the document then.
+ Of course removing the corresponding cache entry is not an option.
+ Because it would disturb iteration over the cache!
+ So we ignore such documents only...
+ Hopefully next time they are not inserted in our cache.
+*/
+bool lc_checkIfSaveForbiddenByArguments(AutoRecovery::TDocumentInfo const & rInfo)
+{
+ if (! rInfo.Document.is())
+ return true;
+
+ utl::MediaDescriptor lDescriptor(rInfo.Document->getArgs());
+ bool bNoAutoSave = lDescriptor.getUnpackedValueOrDefault(utl::MediaDescriptor::PROP_NOAUTOSAVE, false);
+
+ return bNoAutoSave;
+}
+
+AutoRecovery::ETimerType AutoRecovery::implts_saveDocs( bool bAllowUserIdleLoop,
+ bool bRemoveLockFiles,
+ const DispatchParams* pParams )
+{
+ css::uno::Reference< css::task::XStatusIndicator > xExternalProgress;
+ if (pParams)
+ xExternalProgress = pParams->m_xProgress;
+
+ css::uno::Reference< css::frame::XDesktop2 > xDesktop = css::frame::Desktop::create(m_xContext);
+ OUString sBackupPath(SvtPathOptions().GetBackupPath());
+
+ css::uno::Reference< css::frame::XController > xActiveController;
+ css::uno::Reference< css::frame::XModel > xActiveModel;
+ css::uno::Reference< css::frame::XFrame > xActiveFrame = xDesktop->getActiveFrame();
+ if (xActiveFrame.is())
+ xActiveController = xActiveFrame->getController();
+ if (xActiveController.is())
+ xActiveModel = xActiveController->getModel();
+
+ // Set the default timer action for our call.
+ // Default = NORMAL_AUTOSAVE
+ // We return a suggestion for an active timer only.
+ // It will be ignored if the timer was disabled by the user ...
+ // Further this state can be set to USER_IDLE only later in this method.
+ // It's not allowed to reset such state then. Because we must know, if
+ // there exists POSTPONED documents. see below ...
+ AutoRecovery::ETimerType eTimer = AutoRecovery::E_NORMAL_AUTOSAVE_INTERVALL;
+
+ Job eJob = m_eJob;
+
+ CacheLockGuard aCacheLock(this, cppu::WeakComponentImplHelperBase::rBHelper.rMutex, m_nDocCacheLock, LOCK_FOR_CACHE_USE);
+
+ /* SAFE */ {
+ osl::ResettableMutexGuard g(cppu::WeakComponentImplHelperBase::rBHelper.rMutex);
+
+ // This list will be filled with every document
+ // which should be saved as last one. E.g. if it was used
+ // already for a UI save operation => crashed ... and
+ // now we try to save it again ... which can fail again ( of course .-) ).
+ ::std::vector< AutoRecovery::TDocumentList::iterator > lDangerousDocs;
+
+ AutoRecovery::TDocumentList::iterator pIt;
+ for ( pIt = m_lDocCache.begin();
+ pIt != m_lDocCache.end();
+ ++pIt )
+ {
+ AutoRecovery::TDocumentInfo aInfo = *pIt;
+
+ // WORKAROUND... Since the documents are not closed the lock file must be removed explicitly
+ if ( bRemoveLockFiles )
+ lc_removeLockFile( aInfo );
+
+ // WORKAROUND ... see comment of this method
+ if (lc_checkIfSaveForbiddenByArguments(aInfo))
+ continue;
+
+ // already auto saved during this session :-)
+ // This state must be reset for all documents
+ // if timer is started with normal AutoSaveTimerIntervall!
+ if ((aInfo.DocumentState & DocState::Handled) == DocState::Handled)
+ continue;
+
+ // Not modified documents are not saved.
+ // We safe an information about the URL only!
+ Reference< XDocumentRecovery > xDocRecover( aInfo.Document, UNO_QUERY_THROW );
+ if ( !xDocRecover->wasModifiedSinceLastSave() )
+ {
+ aInfo.DocumentState |= DocState::Handled;
+ continue;
+ }
+
+ // check if this document is still used by a concurrent save operation
+ // e.g. if the user tried to save via UI.
+ // Handle it in the following way:
+ // i) For an AutoSave ... ignore this document! It will be saved and next time we will (hopefully)
+ // get a notification about the state of this operation.
+ // And if a document was saved by the user we can remove our temp. file. But that will be done inside
+ // our callback for SaveDone notification.
+ // ii) For a CrashSave ... add it to the list of dangerous documents and
+ // save it after all other documents was saved successfully. That decrease
+ // the chance for a crash inside a crash.
+ // On the other side it's not necessary for documents, which are not modified.
+ // They can be handled normally - means we patch the corresponding configuration entry only.
+ // iii) For a SessionSave ... ignore it! There is no time to wait for this save operation.
+ // Because the WindowManager will kill the process if it doesn't react immediately.
+ // On the other side we can't risk a concurrent save request ... because we know
+ // that it will produce a crash.
+
+ // Attention: Because eJob is used as a flag field, you have to check for the worst case first.
+ // E.g. a CrashSave can overwrite an AutoSave. So you have to check for a CrashSave before an AutoSave!
+ if (aInfo.UsedForSaving)
+ {
+ if ((eJob & Job::EmergencySave) == Job::EmergencySave)
+ {
+ lDangerousDocs.push_back(pIt);
+ continue;
+ }
+ else
+ if ((eJob & Job::SessionSave) == Job::SessionSave)
+ {
+ continue;
+ }
+ else
+ if ((eJob & Job::AutoSave) == Job::AutoSave)
+ {
+ eTimer = AutoRecovery::E_POLL_TILL_AUTOSAVE_IS_ALLOWED;
+ aInfo.DocumentState |= DocState::Postponed;
+ continue;
+ }
+ }
+
+ // a) Document was not postponed - and is active now. => postpone it (restart timer, restart loop)
+ // b) Document was not postponed - and is not active now. => save it
+ // c) Document was postponed - and is not active now. => save it
+ // d) Document was postponed - and is active now. => save it (because user idle was checked already)
+ bool bActive = (xActiveModel == aInfo.Document);
+ bool bWasPostponed = ((aInfo.DocumentState & DocState::Postponed) == DocState::Postponed);
+
+ if (
+ ! bWasPostponed &&
+ bActive
+ )
+ {
+ aInfo.DocumentState |= DocState::Postponed;
+ *pIt = aInfo;
+ // postponed documents will be saved if this method is called again!
+ // That can be done by an outside started timer => E_POLL_FOR_USER_IDLE (if normal AutoSave is active)
+ // or it must be done directly without starting any timer => E_CALL_ME_BACK (if Emergency- or SessionSave is active and must be finished ASAP!)
+ eTimer = AutoRecovery::E_POLL_FOR_USER_IDLE;
+ if (!bAllowUserIdleLoop)
+ eTimer = AutoRecovery::E_CALL_ME_BACK;
+ continue;
+ }
+
+ // b, c, d)
+ // } /* SAFE */
+ g.clear();
+ // changing of aInfo and flushing it is done inside implts_saveOneDoc!
+ implts_saveOneDoc(sBackupPath, aInfo, xExternalProgress);
+ implts_informListener(eJob, AutoRecovery::implst_createFeatureStateEvent(eJob, OPERATION_UPDATE, &aInfo));
+ g.reset();
+ // /* SAFE */ {
+
+ *pIt = aInfo;
+ }
+
+ // Did we have some "dangerous candidates" ?
+ // Try to save it ... but may be it will fail !
+ for (auto const& dangerousDoc : lDangerousDocs)
+ {
+ pIt = dangerousDoc;
+ AutoRecovery::TDocumentInfo aInfo = *pIt;
+
+ // } /* SAFE */
+ g.clear();
+ // changing of aInfo and flushing it is done inside implts_saveOneDoc!
+ implts_saveOneDoc(sBackupPath, aInfo, xExternalProgress);
+ implts_informListener(eJob, AutoRecovery::implst_createFeatureStateEvent(eJob, OPERATION_UPDATE, &aInfo));
+ g.reset();
+ // /* SAFE */ {
+
+ *pIt = aInfo;
+ }
+
+ } /* SAFE */
+
+ return eTimer;
+}
+
+void AutoRecovery::implts_saveOneDoc(const OUString& sBackupPath ,
+ AutoRecovery::TDocumentInfo& rInfo ,
+ const css::uno::Reference< css::task::XStatusIndicator >& xExternalProgress)
+{
+ // no document? => can occur if we loaded our configuration with files,
+ // which couldn't be recovered successfully. In such case we have all needed information
+ // excepting the real document instance!
+
+ // TODO: search right place, where such "dead files" can be removed from the configuration!
+ if (!rInfo.Document.is())
+ return;
+
+ utl::MediaDescriptor lOldArgs(rInfo.Document->getArgs());
+ implts_generateNewTempURL(sBackupPath, lOldArgs, rInfo);
+
+ // if the document was loaded with a password, it should be
+ // stored with password
+ utl::MediaDescriptor lNewArgs;
+ css::uno::Sequence< css::beans::NamedValue > aEncryptionData =
+ lOldArgs.getUnpackedValueOrDefault(utl::MediaDescriptor::PROP_ENCRYPTIONDATA,
+ css::uno::Sequence< css::beans::NamedValue >());
+ if (aEncryptionData.hasElements())
+ lNewArgs[utl::MediaDescriptor::PROP_ENCRYPTIONDATA] <<= aEncryptionData;
+
+ // Further it must be saved using the default file format of that application.
+ // Otherwise we will some data lost.
+ if (!rInfo.DefaultFilter.isEmpty())
+ lNewArgs[utl::MediaDescriptor::PROP_FILTERNAME] <<= rInfo.DefaultFilter;
+
+ // prepare frame/document/mediadescriptor in a way, that it uses OUR progress .-)
+ if (xExternalProgress.is())
+ lNewArgs[utl::MediaDescriptor::PROP_STATUSINDICATOR] <<= xExternalProgress;
+ impl_establishProgress(rInfo, lNewArgs, css::uno::Reference< css::frame::XFrame >());
+
+ // #i66598# use special handling of property "DocumentBaseURL" (it must be an empty string!)
+ // for make hyperlinks working
+ lNewArgs[utl::MediaDescriptor::PROP_DOCUMENTBASEURL] <<= OUString();
+
+ lNewArgs[utl::MediaDescriptor::PROP_AUTOSAVEEVENT] <<= true;
+
+ // try to save this document as a new temp file every time.
+ // Mark AutoSave state as "INCOMPLETE" if it failed.
+ // Because the last temp file is too old and does not include all changes.
+ Reference< XDocumentRecovery > xDocRecover(rInfo.Document, css::uno::UNO_QUERY_THROW);
+
+ // safe the state about "trying to save"
+ // ... we need it for recovery if e.g. a crash occurs inside next line!
+ rInfo.DocumentState |= DocState::TrySave;
+ implts_flushConfigItem(rInfo);
+
+ // If userautosave is enabled, first try to save the original file.
+ // Note that we must do it *before* calling storeToRecoveryFile, so in case of failure here
+ // we won't remain with the modified flag set to true, even though the autorecovery save succeeded.
+ try
+ {
+ // We must check here for an empty URL to avoid a "This operation is not supported on this operating system."
+ // message during autosave.
+ if ((m_eJob & Job::UserAutoSave) == Job::UserAutoSave && !rInfo.OrgURL.isEmpty())
+ {
+ Reference< XStorable > xDocSave(rInfo.Document, css::uno::UNO_QUERY_THROW);
+ xDocSave->store();
+ }
+ }
+ catch(const css::uno::Exception&)
+ {
+ }
+
+ sal_Int32 nRetry = RETRY_STORE_ON_FULL_DISC_FOREVER;
+ bool bError = false;
+ do
+ {
+ try
+ {
+ xDocRecover->storeToRecoveryFile( rInfo.NewTempURL, lNewArgs.getAsConstPropertyValueList() );
+
+#ifdef TRIGGER_FULL_DISC_CHECK
+ throw css::uno::Exception("trigger full disk check");
+#else // TRIGGER_FULL_DISC_CHECK
+
+ bError = false;
+ nRetry = 0;
+#endif // TRIGGER_FULL_DISC_CHECK
+ }
+ catch(const css::uno::Exception& rException)
+ {
+ bError = true;
+
+ // skip saving XLSX with protected sheets, if their passwords haven't supported yet
+ if ( rException.Message.startsWith("SfxBaseModel::impl_store") )
+ {
+ const css::task::ErrorCodeIOException& pErrorCodeIOException =
+ static_cast<const css::task::ErrorCodeIOException&>(rException);
+ if ( static_cast<ErrCode>(pErrorCodeIOException.ErrCode) == ERRCODE_SFX_WRONGPASSWORD )
+ {
+ // stop and remove the bad temporary file, instead of filling the disk with them
+ bError = false;
+ break;
+ }
+ }
+
+ // a) FULL DISC seems to be the problem behind => show error and retry it forever (e.g. retry=300)
+ // b) unknown problem (may be locking problem) => reset RETRY value to more useful value(!) (e.g. retry=3)
+ // c) unknown problem (may be locking problem) + 1..2 repeating operations => throw the original exception to force generation of a stacktrace !
+
+ sal_Int32 nMinSpaceDocSave;
+ /* SAFE */ {
+ osl::MutexGuard g(cppu::WeakComponentImplHelperBase::rBHelper.rMutex);
+ nMinSpaceDocSave = m_nMinSpaceDocSave;
+ } /* SAFE */
+
+ if (! impl_enoughDiscSpace(nMinSpaceDocSave))
+ AutoRecovery::impl_showFullDiscError();
+ else if (nRetry > RETRY_STORE_ON_MIGHT_FULL_DISC_USEFULL)
+ nRetry = RETRY_STORE_ON_MIGHT_FULL_DISC_USEFULL;
+ else if (nRetry <= GIVE_UP_RETRY)
+ throw; // force stacktrace to know if there exist might other reasons, why an AutoSave can fail !!!
+
+ --nRetry;
+ }
+ }
+ while(nRetry>0);
+
+ if (! bError)
+ {
+ // safe the state about success
+ // ... you know the reason: to know it on recovery time if next line crash .-)
+ rInfo.DocumentState &= ~DocState::TrySave;
+ rInfo.DocumentState |= DocState::Handled;
+ rInfo.DocumentState |= DocState::Succeeded;
+ }
+ else
+ {
+ // safe the state about error ...
+ rInfo.NewTempURL.clear();
+ rInfo.DocumentState &= ~DocState::TrySave;
+ rInfo.DocumentState |= DocState::Handled;
+ rInfo.DocumentState |= DocState::Incomplete;
+ }
+
+ // make sure the progress is not referred any longer
+ impl_forgetProgress(rInfo, lNewArgs, css::uno::Reference< css::frame::XFrame >());
+
+ // try to remove the old temp file.
+ // Ignore any error here. We have a new temp file, which is up to date.
+ // The only thing is: we fill the disk with temp files, if we can't remove old ones :-)
+ OUString sRemoveFile = rInfo.OldTempURL;
+ rInfo.OldTempURL = rInfo.NewTempURL;
+ rInfo.NewTempURL.clear();
+
+ implts_flushConfigItem(rInfo);
+
+ // We must know if the user modifies the document again ...
+ implts_startModifyListeningOnDoc(rInfo);
+
+ AutoRecovery::st_impl_removeFile(sRemoveFile);
+}
+
+AutoRecovery::ETimerType AutoRecovery::implts_openDocs(const DispatchParams& aParams)
+{
+ AutoRecovery::ETimerType eTimer = AutoRecovery::E_DONT_START_TIMER;
+
+ CacheLockGuard aCacheLock(this, cppu::WeakComponentImplHelperBase::rBHelper.rMutex, m_nDocCacheLock, LOCK_FOR_CACHE_USE);
+
+ /* SAFE */ {
+ osl::ResettableMutexGuard g(cppu::WeakComponentImplHelperBase::rBHelper.rMutex);
+
+ Job eJob = m_eJob;
+ for (auto & info : m_lDocCache)
+ {
+ // Such documents are already loaded by the last loop.
+ // Don't check DocState::Succeeded here! It may be the final state of an AutoSave
+ // operation before!!!
+ if ((info.DocumentState & DocState::Handled) == DocState::Handled)
+ continue;
+
+ // a1,b1,c1,d2,e2,f2)
+ if ((info.DocumentState & DocState::Damaged) == DocState::Damaged)
+ {
+ // don't forget to inform listener! May be this document was
+ // damaged on last saving time ...
+ // Then our listener need this notification.
+ // If it was damaged during last "try to open" ...
+ // it will be notified more than once. SH.. HAPPENS ...
+ // } /* SAFE */
+ g.clear();
+ implts_informListener(eJob,
+ AutoRecovery::implst_createFeatureStateEvent(eJob, OPERATION_UPDATE, &info));
+ g.reset();
+ // /* SAFE */ {
+ continue;
+ }
+
+ utl::MediaDescriptor lDescriptor;
+
+ // it's a UI feature - so the "USER" itself must be set as referrer
+ lDescriptor[utl::MediaDescriptor::PROP_REFERRER] <<= OUString(REFERRER_USER);
+ lDescriptor[utl::MediaDescriptor::PROP_SALVAGEDFILE] <<= OUString();
+
+ // recovered documents are loaded hidden, and shown all at once, later
+ lDescriptor[utl::MediaDescriptor::PROP_HIDDEN] <<= true;
+
+ if (aParams.m_xProgress.is())
+ lDescriptor[utl::MediaDescriptor::PROP_STATUSINDICATOR] <<= aParams.m_xProgress;
+
+ bool bBackupWasTried = (
+ ((info.DocumentState & DocState::TryLoadBackup ) == DocState::TryLoadBackup) || // temp. state!
+ ((info.DocumentState & DocState::Incomplete ) == DocState::Incomplete ) // transport DocState::TryLoadBackup from last loop to this new one!
+ );
+ bool bOriginalWasTried = ((info.DocumentState & DocState::TryLoadOriginal) == DocState::TryLoadOriginal);
+
+ if (bBackupWasTried)
+ {
+ if (!bOriginalWasTried)
+ {
+ info.DocumentState |= DocState::Incomplete;
+ // try original URL ... ! don't continue with next item here ...
+ }
+ else
+ {
+ info.DocumentState |= DocState::Damaged;
+ continue;
+ }
+ }
+
+ OUString sLoadOriginalURL;
+ OUString sLoadBackupURL;
+
+ if (!bBackupWasTried)
+ sLoadBackupURL = info.OldTempURL;
+
+ if (!info.OrgURL.isEmpty())
+ {
+ sLoadOriginalURL = info.OrgURL;
+ }
+ else if (!info.TemplateURL.isEmpty())
+ {
+ sLoadOriginalURL = info.TemplateURL;
+ lDescriptor[utl::MediaDescriptor::PROP_ASTEMPLATE] <<= true;
+ lDescriptor[utl::MediaDescriptor::PROP_TEMPLATENAME] <<= info.TemplateURL;
+ }
+ else if (!info.FactoryURL.isEmpty())
+ {
+ sLoadOriginalURL = info.FactoryURL;
+ lDescriptor[utl::MediaDescriptor::PROP_ASTEMPLATE] <<= true;
+ }
+
+ // A "Salvaged" item must exists every time. The core can make something special then for recovery.
+ // Of course it should be the real file name of the original file, in case we load the temp. backup here.
+ OUString sURL;
+ if (!sLoadBackupURL.isEmpty())
+ {
+ sURL = sLoadBackupURL;
+ info.DocumentState |= DocState::TryLoadBackup;
+ lDescriptor[utl::MediaDescriptor::PROP_SALVAGEDFILE] <<= sLoadOriginalURL;
+ }
+ else if (!sLoadOriginalURL.isEmpty())
+ {
+ sURL = sLoadOriginalURL;
+ info.DocumentState |= DocState::TryLoadOriginal;
+ }
+ else
+ continue; // TODO ERROR!
+
+ LoadEnv::initializeUIDefaults( m_xContext, lDescriptor, true, nullptr );
+
+ // } /* SAFE */
+ g.clear();
+
+ implts_flushConfigItem(info);
+ implts_informListener(eJob,
+ AutoRecovery::implst_createFeatureStateEvent(eJob, OPERATION_UPDATE, &info));
+
+ try
+ {
+ implts_openOneDoc(sURL, lDescriptor, info);
+ }
+ catch(const css::uno::Exception&)
+ {
+ info.DocumentState &= ~DocState::TryLoadBackup;
+ info.DocumentState &= ~DocState::TryLoadOriginal;
+ if (!sLoadBackupURL.isEmpty())
+ {
+ info.DocumentState |= DocState::Incomplete;
+ eTimer = AutoRecovery::E_CALL_ME_BACK;
+ }
+ else
+ {
+ info.DocumentState |= DocState::Handled;
+ info.DocumentState |= DocState::Damaged;
+ }
+
+ implts_flushConfigItem(info, true);
+ implts_informListener(eJob,
+ AutoRecovery::implst_createFeatureStateEvent(eJob, OPERATION_UPDATE, &info));
+
+ // /* SAFE */ {
+ // Needed for next loop!
+ g.reset();
+ continue;
+ }
+
+ if (!info.RealFilter.isEmpty())
+ {
+ utl::MediaDescriptor lPatchDescriptor(info.Document->getArgs());
+ lPatchDescriptor[utl::MediaDescriptor::PROP_FILTERNAME] <<= info.RealFilter;
+ info.Document->attachResource(info.Document->getURL(), lPatchDescriptor.getAsConstPropertyValueList());
+ // do *not* use sURL here. In case this points to the recovery file, it has already been passed
+ // to recoverFromFile. Also, passing it here is logically wrong, as attachResource is intended
+ // to take the logical file URL.
+ }
+
+ css::uno::Reference< css::util::XModifiable > xModify(info.Document, css::uno::UNO_QUERY);
+ if ( xModify.is() )
+ {
+ bool bModified = ((info.DocumentState & DocState::Modified) == DocState::Modified);
+ xModify->setModified(bModified);
+ }
+
+ info.DocumentState &= ~DocState::TryLoadBackup;
+ info.DocumentState &= ~DocState::TryLoadOriginal;
+ info.DocumentState |= DocState::Handled;
+ info.DocumentState |= DocState::Succeeded;
+
+ implts_flushConfigItem(info);
+ implts_informListener(eJob,
+ AutoRecovery::implst_createFeatureStateEvent(eJob, OPERATION_UPDATE, &info));
+
+ /* Normally we listen as XModifyListener on a document to know if a document was changed
+ since our last AutoSave. And we deregister us in case we know this state.
+ But directly after one document as recovered ... we must start listening.
+ Otherwise the first "modify" doesn't reach us. Because we ourself called setModified()
+ on the document via API. And currently we don't listen for any events (not at theGlobalEventBroadcaster
+ nor at any document!).
+ */
+ implts_startModifyListeningOnDoc(info);
+
+ // /* SAFE */ {
+ // Needed for next loop. Don't unlock it again!
+ g.reset();
+ }
+
+ } /* SAFE */
+
+ return eTimer;
+}
+
+void AutoRecovery::implts_openOneDoc(const OUString& sURL ,
+ utl::MediaDescriptor& lDescriptor,
+ AutoRecovery::TDocumentInfo& rInfo )
+{
+ css::uno::Reference< css::frame::XDesktop2 > xDesktop = css::frame::Desktop::create(m_xContext);
+
+ ::std::vector< Reference< XComponent > > aCleanup;
+ try
+ {
+ // create a new document of the desired type
+ Reference< XModel2 > xModel(m_xContext->getServiceManager()->createInstanceWithContext(
+ rInfo.FactoryService, m_xContext), UNO_QUERY_THROW);
+ aCleanup.emplace_back(xModel.get() );
+
+ // put the filter name into the descriptor - we're not going to involve any type detection, so
+ // the document might be lost without the FilterName property
+ if ( (rInfo.DocumentState & DocState::TryLoadOriginal) == DocState::TryLoadOriginal)
+ lDescriptor[ utl::MediaDescriptor::PROP_FILTERNAME ] <<= rInfo.RealFilter;
+ else
+ lDescriptor[ utl::MediaDescriptor::PROP_FILTERNAME ] <<= rInfo.DefaultFilter;
+
+ if ( sURL == rInfo.FactoryURL )
+ {
+ // if the document was a new, unmodified document, then there's nothing to recover, just to init
+ ENSURE_OR_THROW( ( rInfo.DocumentState & DocState::Modified ) == DocState(0),
+ "unexpected document state" );
+ Reference< XLoadable > xModelLoad( xModel, UNO_QUERY_THROW );
+ xModelLoad->initNew();
+
+ // TODO: remove load-process specific arguments from the descriptor, e.g. the status indicator
+ xModel->attachResource( sURL, lDescriptor.getAsConstPropertyValueList() );
+ }
+ else
+ {
+ OUString sFilterName;
+ lDescriptor[utl::MediaDescriptor::PROP_FILTERNAME] >>= sFilterName;
+ if (!sFilterName.isEmpty()
+ && ( sFilterName == "Calc MS Excel 2007 XML"
+ || sFilterName == "Impress MS PowerPoint 2007 XML"
+ || sFilterName == "MS Word 2007 XML"))
+ // TODO: Probably need to check other affected formats + templates?
+ {
+ // tdf#129096: in case of recovery of password protected OOXML document it is done not
+ // the same way as ordinal loading. Inside XDocumentRecovery::recoverFromFile
+ // there is a call to XFilter::filter which has constant media descriptor and thus
+ // all encryption data used in document is lost. To avoid this try to walkaround
+ // with explicit call to FormatDetector. It will try to load document, prompt for password
+ // and store this info in media descriptor we will use for recoverFromFile call.
+ Reference< css::document::XExtendedFilterDetection > xDetection(
+ m_xContext->getServiceManager()->createInstanceWithContext(
+ "com.sun.star.comp.oox.FormatDetector", m_xContext),
+ UNO_QUERY_THROW);
+ lDescriptor[utl::MediaDescriptor::PROP_URL] <<= sURL;
+ Sequence< css::beans::PropertyValue > aDescriptorSeq = lDescriptor.getAsConstPropertyValueList();
+ OUString sType = xDetection->detect(aDescriptorSeq);
+
+ OUString sNewFilterName;
+ lDescriptor[utl::MediaDescriptor::PROP_FILTERNAME] >>= sNewFilterName;
+ if (!sType.isEmpty() && sNewFilterName == sFilterName)
+ {
+ // Filter detection was okay, update media descriptor with one received from FilterDetect
+ lDescriptor = aDescriptorSeq;
+ }
+ }
+
+ // let it recover itself
+ Reference< XDocumentRecovery > xDocRecover( xModel, UNO_QUERY_THROW );
+ xDocRecover->recoverFromFile(
+ sURL,
+ lDescriptor.getUnpackedValueOrDefault( utl::MediaDescriptor::PROP_SALVAGEDFILE, OUString() ),
+ lDescriptor.getAsConstPropertyValueList()
+ );
+
+ // No attachResource needed here. By definition (of XDocumentRecovery), the implementation is responsible
+ // for completely initializing the model, which includes attachResource (or equivalent), if required.
+ }
+
+ // re-create all the views
+ ::std::vector< OUString > aViewsToRestore( std::cbegin(rInfo.ViewNames), std::cend(rInfo.ViewNames) );
+ // if we don't have views for whatever reason, then create a default-view, at least
+ if ( aViewsToRestore.empty() )
+ aViewsToRestore.emplace_back( );
+
+ for (auto const& viewToRestore : aViewsToRestore)
+ {
+ // create a frame
+ Reference< XFrame > xTargetFrame = xDesktop->findFrame( SPECIALTARGET_BLANK, 0 );
+ aCleanup.emplace_back(xTargetFrame.get() );
+
+ // create a view to the document
+ Reference< XController2 > xController;
+ if ( viewToRestore.getLength() )
+ {
+ xController.set( xModel->createViewController( viewToRestore, Sequence< css::beans::PropertyValue >(), xTargetFrame ), UNO_SET_THROW );
+ }
+ else
+ {
+ xController.set( xModel->createDefaultViewController( xTargetFrame ), UNO_SET_THROW );
+ }
+
+ // introduce model/view/controller to each other
+ xController->attachModel( xModel );
+ xModel->connectController( xController );
+ xTargetFrame->setComponent( xController->getComponentWindow(), xController );
+ xController->attachFrame( xTargetFrame );
+ xModel->setCurrentController( xController );
+ }
+
+ rInfo.Document = xModel.get();
+ }
+ catch(const css::uno::RuntimeException&)
+ {
+ throw;
+ }
+ catch(const css::uno::Exception&)
+ {
+ Any aCaughtException( ::cppu::getCaughtException() );
+
+ // clean up
+ for (auto const& component : aCleanup)
+ {
+ css::uno::Reference< css::util::XCloseable > xClose(component, css::uno::UNO_QUERY);
+ if ( xClose.is() )
+ xClose->close( true );
+ else
+ component->dispose();
+ }
+
+ // re-throw
+ throw css::lang::WrappedTargetException(
+ "Recovery of \"" + sURL + "\" failed.",
+ static_cast< css::frame::XDispatch* >(this),
+ aCaughtException
+ );
+ }
+}
+
+void AutoRecovery::implts_generateNewTempURL(const OUString& sBackupPath ,
+ utl::MediaDescriptor& /*rMediaDescriptor*/,
+ AutoRecovery::TDocumentInfo& rInfo )
+{
+ // specify URL for saving (which points to a temp file inside backup directory)
+ // and define a unique name, so we can locate it later.
+ // This unique name must solve an optimization problem too!
+ // In case we are asked to save unmodified documents too - and one of them
+ // is an empty one (because it was new created using e.g. a URL private:factory/...)
+ // we should not save it really. Then we put the information about such "empty document"
+ // into the configuration and don't create any recovery file on disk.
+ // We use the title of the document to make it unique.
+ OUStringBuffer sUniqueName;
+ if (!rInfo.OrgURL.isEmpty())
+ {
+ css::uno::Reference< css::util::XURLTransformer > xParser(css::util::URLTransformer::create(m_xContext));
+ css::util::URL aURL;
+ aURL.Complete = rInfo.OrgURL;
+ xParser->parseStrict(aURL);
+ sUniqueName.append(aURL.Name);
+ }
+ else if (!rInfo.FactoryURL.isEmpty())
+ sUniqueName.append("untitled");
+ sUniqueName.append("_");
+
+ // TODO: Must we strip some illegal signes - if we use the title?
+
+ OUString sName(sUniqueName.makeStringAndClear());
+ OUString sExtension(rInfo.Extension);
+ OUString sPath(sBackupPath);
+ ::utl::TempFile aTempFile(sName, true, &sExtension, &sPath, true);
+
+ rInfo.NewTempURL = aTempFile.GetURL();
+}
+
+void AutoRecovery::implts_informListener( Job eJob ,
+ const css::frame::FeatureStateEvent& aEvent)
+{
+ // Helper shares mutex with us -> threadsafe!
+ ::comphelper::OInterfaceContainerHelper3<css::frame::XStatusListener>* pListenerForURL = nullptr;
+ OUString sJob = AutoRecovery::implst_getJobDescription(eJob);
+
+ // inform listener, which are registered for any URLs(!)
+ pListenerForURL = m_lListener.getContainer(sJob);
+ if(pListenerForURL == nullptr)
+ return;
+
+ ::comphelper::OInterfaceIteratorHelper3 pIt(*pListenerForURL);
+ while(pIt.hasMoreElements())
+ {
+ try
+ {
+ pIt.next()->statusChanged(aEvent);
+ }
+ catch(const css::uno::RuntimeException&)
+ {
+ pIt.remove();
+ }
+ }
+}
+
+OUString AutoRecovery::implst_getJobDescription(Job eJob)
+{
+ // describe the current running operation
+ OUStringBuffer sFeature(256);
+ sFeature.append(CMD_PROTOCOL);
+
+ // Attention: Because "eJob" is used as a flag field the order of checking these
+ // flags is important. We must prefer job with higher priorities!
+ // E.g. EmergencySave has an higher prio then AutoSave ...
+ // On the other side there exist a well defined order between two different jobs.
+ // e.g. PrepareEmergencySave must be done before EmergencySave is started of course.
+
+ if ((eJob & Job::PrepareEmergencySave) == Job::PrepareEmergencySave)
+ sFeature.append(CMD_DO_PREPARE_EMERGENCY_SAVE);
+ else if ((eJob & Job::EmergencySave) == Job::EmergencySave)
+ sFeature.append(CMD_DO_EMERGENCY_SAVE);
+ else if ((eJob & Job::Recovery) == Job::Recovery)
+ sFeature.append(CMD_DO_RECOVERY);
+ else if ((eJob & Job::SessionSave) == Job::SessionSave)
+ sFeature.append(CMD_DO_SESSION_SAVE);
+ else if ((eJob & Job::SessionQuietQuit) == Job::SessionQuietQuit)
+ sFeature.append(CMD_DO_SESSION_QUIET_QUIT);
+ else if ((eJob & Job::SessionRestore) == Job::SessionRestore)
+ sFeature.append(CMD_DO_SESSION_RESTORE);
+ else if ((eJob & Job::EntryBackup) == Job::EntryBackup)
+ sFeature.append(CMD_DO_ENTRY_BACKUP);
+ else if ((eJob & Job::EntryCleanup) == Job::EntryCleanup)
+ sFeature.append(CMD_DO_ENTRY_CLEANUP);
+ else if ((eJob & Job::AutoSave) == Job::AutoSave)
+ sFeature.append(CMD_DO_AUTO_SAVE);
+ else if ( eJob != Job::NoJob )
+ SAL_INFO("fwk.autorecovery", "AutoRecovery::implst_getJobDescription(): Invalid job identifier detected.");
+
+ return sFeature.makeStringAndClear();
+}
+
+Job AutoRecovery::implst_classifyJob(const css::util::URL& aURL)
+{
+ if ( aURL.Protocol == CMD_PROTOCOL )
+ {
+ if ( aURL.Path == CMD_DO_PREPARE_EMERGENCY_SAVE )
+ return Job::PrepareEmergencySave;
+ else if ( aURL.Path == CMD_DO_EMERGENCY_SAVE )
+ return Job::EmergencySave;
+ else if ( aURL.Path == CMD_DO_RECOVERY )
+ return Job::Recovery;
+ else if ( aURL.Path == CMD_DO_ENTRY_BACKUP )
+ return Job::EntryBackup;
+ else if ( aURL.Path == CMD_DO_ENTRY_CLEANUP )
+ return Job::EntryCleanup;
+ else if ( aURL.Path == CMD_DO_SESSION_SAVE )
+ return Job::SessionSave;
+ else if ( aURL.Path == CMD_DO_SESSION_QUIET_QUIT )
+ return Job::SessionQuietQuit;
+ else if ( aURL.Path == CMD_DO_SESSION_RESTORE )
+ return Job::SessionRestore;
+ else if ( aURL.Path == CMD_DO_DISABLE_RECOVERY )
+ return Job::DisableAutorecovery;
+ else if ( aURL.Path == CMD_DO_SET_AUTOSAVE_STATE )
+ return Job::SetAutosaveState;
+ }
+
+ SAL_INFO("fwk.autorecovery", "AutoRecovery::implts_classifyJob(): Invalid URL (protocol).");
+ return Job::NoJob;
+}
+
+css::frame::FeatureStateEvent AutoRecovery::implst_createFeatureStateEvent( Job eJob ,
+ const OUString& sEventType,
+ AutoRecovery::TDocumentInfo const * pInfo )
+{
+ css::frame::FeatureStateEvent aEvent;
+ aEvent.FeatureURL.Complete = AutoRecovery::implst_getJobDescription(eJob);
+ aEvent.FeatureDescriptor = sEventType;
+
+ if (pInfo && sEventType == OPERATION_UPDATE)
+ {
+ // pack rInfo for transport via UNO
+ ::comphelper::NamedValueCollection aInfo;
+ aInfo.put( OUString(CFG_ENTRY_PROP_ID), pInfo->ID );
+ aInfo.put( OUString(CFG_ENTRY_PROP_ORIGINALURL), pInfo->OrgURL );
+ aInfo.put( OUString(CFG_ENTRY_PROP_FACTORYURL), pInfo->FactoryURL );
+ aInfo.put( OUString(CFG_ENTRY_PROP_TEMPLATEURL), pInfo->TemplateURL );
+ aInfo.put( OUString(CFG_ENTRY_PROP_TEMPURL), pInfo->OldTempURL.isEmpty() ? pInfo->NewTempURL : pInfo->OldTempURL );
+ aInfo.put( OUString(CFG_ENTRY_PROP_MODULE), pInfo->AppModule);
+ aInfo.put( OUString(CFG_ENTRY_PROP_TITLE), pInfo->Title);
+ aInfo.put( OUString(CFG_ENTRY_PROP_VIEWNAMES), pInfo->ViewNames);
+ aInfo.put( OUString(CFG_ENTRY_PROP_DOCUMENTSTATE), sal_Int32(pInfo->DocumentState));
+
+ aEvent.State <<= aInfo.getPropertyValues();
+ }
+
+ return aEvent;
+}
+
+void AutoRecovery::implts_resetHandleStates()
+{
+ CacheLockGuard aCacheLock(this, cppu::WeakComponentImplHelperBase::rBHelper.rMutex, m_nDocCacheLock, LOCK_FOR_CACHE_USE);
+
+ /* SAFE */ {
+ osl::ResettableMutexGuard g(cppu::WeakComponentImplHelperBase::rBHelper.rMutex);
+
+ for (auto & info : m_lDocCache)
+ {
+ info.DocumentState &= ~DocState::Handled;
+ info.DocumentState &= ~DocState::Postponed;
+
+ // } /* SAFE */
+ g.clear();
+ implts_flushConfigItem(info);
+ g.reset();
+ // /* SAFE */ {
+ }
+ } /* SAFE */
+}
+
+void AutoRecovery::implts_prepareEmergencySave()
+{
+ // Be sure to know all open documents really .-)
+ implts_verifyCacheAgainstDesktopDocumentList();
+
+ // hide all docs, so the user can't disturb our emergency save .-)
+ implts_changeAllDocVisibility(false);
+}
+
+void AutoRecovery::implts_doEmergencySave(const DispatchParams& aParams)
+{
+ // Write a hint "we crashed" into the configuration, so
+ // the error report tool is started too in case no recovery
+ // documents exists and was saved.
+
+ std::shared_ptr<comphelper::ConfigurationChanges> batch(
+ comphelper::ConfigurationChanges::create());
+ officecfg::Office::Recovery::RecoveryInfo::Crashed::set(true, batch);
+ batch->commit();
+
+ // for all docs, store their current view/names in the configuration
+ implts_persistAllActiveViewNames();
+
+ // The called method for saving documents runs
+ // during normal AutoSave more than once. Because
+ // it postpone active documents and save it later.
+ // That is normally done by recalling it from a timer.
+ // Here we must do it immediately!
+ // Of course this method returns the right state -
+ // because it knows, that we are running in EMERGENCY SAVE mode .-)
+
+ bool const bAllowUserIdleLoop = false; // not allowed to change that .-)
+ AutoRecovery::ETimerType eSuggestedTimer = AutoRecovery::E_DONT_START_TIMER;
+ do
+ {
+ eSuggestedTimer = implts_saveDocs(bAllowUserIdleLoop, true, &aParams);
+ }
+ while(eSuggestedTimer == AutoRecovery::E_CALL_ME_BACK);
+
+ // reset the handle state of all
+ // cache items. Such handle state indicates, that a document
+ // was already saved during the THIS(!) EmergencySave session.
+ // Of course following recovery session must be started without
+ // any "handle" state ...
+ implts_resetHandleStates();
+
+ // flush config cached back to disc.
+ impl_flushALLConfigChanges();
+
+ // try to make sure next time office will be started user won't be
+ // notified about any other might be running office instance
+ // remove ".lock" file from disc !
+ AutoRecovery::st_impl_removeLockFile();
+}
+
+void AutoRecovery::implts_doRecovery(const DispatchParams& aParams)
+{
+ AutoRecovery::ETimerType eSuggestedTimer = AutoRecovery::E_DONT_START_TIMER;
+ do
+ {
+ eSuggestedTimer = implts_openDocs(aParams);
+ }
+ while(eSuggestedTimer == AutoRecovery::E_CALL_ME_BACK);
+
+ // reset the handle state of all
+ // cache items. Such handle state indicates, that a document
+ // was already saved during the THIS(!) Recovery session.
+ // Of course a may be following EmergencySave session must be started without
+ // any "handle" state...
+ implts_resetHandleStates();
+
+ // Reset the configuration hint "we were crashed"!
+ std::shared_ptr<comphelper::ConfigurationChanges> batch(
+ comphelper::ConfigurationChanges::create());
+ officecfg::Office::Recovery::RecoveryInfo::Crashed::set(false, batch);
+ batch->commit();
+}
+
+void AutoRecovery::implts_doSessionSave(const DispatchParams& aParams)
+{
+ SAL_INFO("fwk.autorecovery", "AutoRecovery::implts_doSessionSave()");
+
+ // Be sure to know all open documents really .-)
+ implts_verifyCacheAgainstDesktopDocumentList();
+
+ // for all docs, store their current view/names in the configuration
+ implts_persistAllActiveViewNames();
+
+ // The called method for saving documents runs
+ // during normal AutoSave more than once. Because
+ // it postpone active documents and save it later.
+ // That is normally done by recalling it from a timer.
+ // Here we must do it immediately!
+ // Of course this method returns the right state -
+ // because it knows, that we are running in SESSION SAVE mode .-)
+
+ bool const bAllowUserIdleLoop = false; // not allowed to change that .-)
+ AutoRecovery::ETimerType eSuggestedTimer = AutoRecovery::E_DONT_START_TIMER;
+ do
+ {
+ // do not remove lock files of the documents, it will be done on session quit
+ eSuggestedTimer = implts_saveDocs(bAllowUserIdleLoop, false, &aParams);
+ }
+ while(eSuggestedTimer == AutoRecovery::E_CALL_ME_BACK);
+
+ // reset the handle state of all
+ // cache items. Such handle state indicates, that a document
+ // was already saved during the THIS(!) save session.
+ // Of course following restore session must be started without
+ // any "handle" state ...
+ implts_resetHandleStates();
+
+ // flush config cached back to disc.
+ impl_flushALLConfigChanges();
+}
+
+void AutoRecovery::implts_doSessionQuietQuit()
+{
+ SAL_INFO("fwk.autorecovery", "AutoRecovery::implts_doSessionQuietQuit()");
+
+ // try to make sure next time office will be started user won't be
+ // notified about any other might be running office instance
+ // remove ".lock" file from disc!
+ // it is done as a first action for session save since Gnome sessions
+ // do not provide enough time for shutdown, and the dialog looks to be
+ // confusing for the user
+ AutoRecovery::st_impl_removeLockFile();
+
+ // reset all modified documents, so the don't show any UI on closing ...
+ // and close all documents, so we can shutdown the OS!
+ implts_prepareSessionShutdown();
+
+ // Write a hint for "stored session data" into the configuration, so
+ // the on next startup we know what's happen last time
+ std::shared_ptr<comphelper::ConfigurationChanges> batch(
+ comphelper::ConfigurationChanges::create());
+ officecfg::Office::Recovery::RecoveryInfo::SessionData::set(true, batch);
+ batch->commit();
+
+ // flush config cached back to disc.
+ impl_flushALLConfigChanges();
+}
+
+void AutoRecovery::implts_doSessionRestore(const DispatchParams& aParams)
+{
+ SAL_INFO("fwk.autorecovery", "AutoRecovery::implts_doSessionRestore() ...");
+
+ AutoRecovery::ETimerType eSuggestedTimer = AutoRecovery::E_DONT_START_TIMER;
+ do
+ {
+ eSuggestedTimer = implts_openDocs(aParams);
+ }
+ while(eSuggestedTimer == AutoRecovery::E_CALL_ME_BACK);
+
+ // reset the handle state of all
+ // cache items. Such handle state indicates, that a document
+ // was already saved during the THIS(!) Restore session.
+ // Of course a may be following save session must be started without
+ // any "handle" state ...
+ implts_resetHandleStates();
+
+ // make all opened documents visible
+ implts_changeAllDocVisibility(true);
+
+ // Reset the configuration hint for "session save"!
+ SAL_INFO("fwk.autorecovery", "... reset config key 'SessionData'");
+ std::shared_ptr<comphelper::ConfigurationChanges> batch(
+ comphelper::ConfigurationChanges::create());
+ officecfg::Office::Recovery::RecoveryInfo::SessionData::set(false, batch);
+ batch->commit();
+
+ SAL_INFO("fwk.autorecovery", "... AutoRecovery::implts_doSessionRestore()");
+}
+
+void AutoRecovery::implts_backupWorkingEntry(const DispatchParams& aParams)
+{
+ CacheLockGuard aCacheLock(this, cppu::WeakComponentImplHelperBase::rBHelper.rMutex, m_nDocCacheLock, LOCK_FOR_CACHE_USE);
+
+ for (auto const& info : m_lDocCache)
+ {
+ if (info.ID != aParams.m_nWorkingEntryID)
+ continue;
+
+ OUString sSourceURL;
+ // Prefer temp file. It contains the changes against the original document!
+ if (!info.OldTempURL.isEmpty())
+ sSourceURL = info.OldTempURL;
+ else if (!info.NewTempURL.isEmpty())
+ sSourceURL = info.NewTempURL;
+ else if (!info.OrgURL.isEmpty())
+ sSourceURL = info.OrgURL;
+ else
+ continue; // nothing real to save! An unmodified but new created document.
+
+ INetURLObject aParser(sSourceURL);
+ // AutoRecovery::EFailureSafeResult eResult =
+ implts_copyFile(sSourceURL, aParams.m_sSavePath, aParser.getName());
+
+ // TODO: Check eResult and react for errors (InteractionHandler!?)
+ // Currently we ignore it ...
+ // DON'T UPDATE THE CACHE OR REMOVE ANY TEMP. FILES FROM DISK.
+ // That has to be forced from outside explicitly.
+ // See implts_cleanUpWorkingEntry() for further details.
+ }
+}
+
+void AutoRecovery::implts_cleanUpWorkingEntry(const DispatchParams& aParams)
+{
+ CacheLockGuard aCacheLock(this, cppu::WeakComponentImplHelperBase::rBHelper.rMutex, m_nDocCacheLock, LOCK_FOR_CACHE_ADD_REMOVE);
+
+ AutoRecovery::TDocumentList::iterator pIt = std::find_if(m_lDocCache.begin(), m_lDocCache.end(),
+ [&aParams](const AutoRecovery::TDocumentInfo& rInfo) { return rInfo.ID == aParams.m_nWorkingEntryID; });
+ if (pIt != m_lDocCache.end())
+ {
+ AutoRecovery::TDocumentInfo& rInfo = *pIt;
+ AutoRecovery::st_impl_removeFile(rInfo.OldTempURL);
+ AutoRecovery::st_impl_removeFile(rInfo.NewTempURL);
+ implts_flushConfigItem(rInfo, true); // sal_True => remove it from xml config!
+
+ m_lDocCache.erase(pIt);
+ }
+}
+
+AutoRecovery::EFailureSafeResult AutoRecovery::implts_copyFile(const OUString& sSource ,
+ const OUString& sTargetPath,
+ const OUString& sTargetName)
+{
+ // create content for the parent folder and call transfer on that content with the source content
+ // and the destination file name as parameters
+
+ css::uno::Reference< css::ucb::XCommandEnvironment > xEnvironment;
+
+ ::ucbhelper::Content aSourceContent;
+ ::ucbhelper::Content aTargetContent;
+
+ try
+ {
+ aTargetContent = ::ucbhelper::Content(sTargetPath, xEnvironment, m_xContext);
+ }
+ catch(const css::uno::Exception&)
+ {
+ return AutoRecovery::E_WRONG_TARGET_PATH;
+ }
+
+ sal_Int32 nNameClash;
+ nNameClash = css::ucb::NameClash::RENAME;
+
+ try
+ {
+ bool bSuccess = ::ucbhelper::Content::create(sSource, xEnvironment, m_xContext, aSourceContent);
+ if (!bSuccess)
+ return AutoRecovery::E_ORIGINAL_FILE_MISSING;
+ aTargetContent.transferContent(aSourceContent, ::ucbhelper::InsertOperation::Copy, sTargetName, nNameClash);
+ }
+ catch(const css::uno::Exception&)
+ {
+ return AutoRecovery::E_ORIGINAL_FILE_MISSING;
+ }
+
+ return AutoRecovery::E_COPIED;
+}
+
+sal_Bool SAL_CALL AutoRecovery::convertFastPropertyValue( css::uno::Any& /*aConvertedValue*/,
+ css::uno::Any& /*aOldValue*/ ,
+ sal_Int32 /*nHandle*/ ,
+ const css::uno::Any& /*aValue*/ )
+{
+ // not needed currently
+ return false;
+}
+
+void SAL_CALL AutoRecovery::setFastPropertyValue_NoBroadcast( sal_Int32 /*nHandle*/,
+ const css::uno::Any& /*aValue*/ )
+{
+ // not needed currently
+}
+
+void SAL_CALL AutoRecovery::getFastPropertyValue(css::uno::Any& aValue ,
+ sal_Int32 nHandle) const
+{
+ switch(nHandle)
+ {
+ case AUTORECOVERY_PROPHANDLE_EXISTS_RECOVERYDATA :
+ {
+ bool bSessionData = officecfg::Office::Recovery::RecoveryInfo::SessionData::get();
+ bool bRecoveryData = !m_lDocCache.empty();
+
+ // exists session data ... => then we can't say, that these
+ // data are valid for recovery. So we have to return sal_False then!
+ if (bSessionData)
+ bRecoveryData = false;
+
+ aValue <<= bRecoveryData;
+ }
+ break;
+
+ case AUTORECOVERY_PROPHANDLE_CRASHED :
+ aValue <<= officecfg::Office::Recovery::RecoveryInfo::Crashed::get();
+ break;
+
+ case AUTORECOVERY_PROPHANDLE_EXISTS_SESSIONDATA :
+ aValue <<= officecfg::Office::Recovery::RecoveryInfo::SessionData::get();
+ break;
+ }
+}
+
+css::uno::Sequence< css::beans::Property > impl_getStaticPropertyDescriptor()
+{
+ return
+ {
+ css::beans::Property( AUTORECOVERY_PROPNAME_CRASHED , AUTORECOVERY_PROPHANDLE_CRASHED , cppu::UnoType<bool>::get() , css::beans::PropertyAttribute::TRANSIENT | css::beans::PropertyAttribute::READONLY ),
+ css::beans::Property( AUTORECOVERY_PROPNAME_EXISTS_RECOVERYDATA, AUTORECOVERY_PROPHANDLE_EXISTS_RECOVERYDATA, cppu::UnoType<bool>::get() , css::beans::PropertyAttribute::TRANSIENT | css::beans::PropertyAttribute::READONLY ),
+ css::beans::Property( AUTORECOVERY_PROPNAME_EXISTS_SESSIONDATA , AUTORECOVERY_PROPHANDLE_EXISTS_SESSIONDATA , cppu::UnoType<bool>::get() , css::beans::PropertyAttribute::TRANSIENT | css::beans::PropertyAttribute::READONLY ),
+ };
+}
+
+::cppu::IPropertyArrayHelper& SAL_CALL AutoRecovery::getInfoHelper()
+{
+ static ::cppu::OPropertyArrayHelper ourInfoHelper(impl_getStaticPropertyDescriptor(), true);
+
+ return ourInfoHelper;
+}
+
+css::uno::Reference< css::beans::XPropertySetInfo > SAL_CALL AutoRecovery::getPropertySetInfo()
+{
+ static css::uno::Reference< css::beans::XPropertySetInfo > xInfo(
+ ::cppu::OPropertySetHelper::createPropertySetInfo(getInfoHelper()));
+
+ return xInfo;
+}
+
+void AutoRecovery::implts_verifyCacheAgainstDesktopDocumentList()
+{
+ SAL_INFO("fwk.autorecovery", "AutoRecovery::implts_verifyCacheAgainstDesktopDocumentList() ...");
+ try
+ {
+ css::uno::Reference< css::frame::XDesktop2 > xDesktop = css::frame::Desktop::create(m_xContext);
+
+ css::uno::Reference< css::container::XIndexAccess > xContainer(
+ xDesktop->getFrames(),
+ css::uno::UNO_QUERY_THROW);
+
+ sal_Int32 i = 0;
+ sal_Int32 c = xContainer->getCount();
+
+ for (i=0; i<c; ++i)
+ {
+ css::uno::Reference< css::frame::XFrame > xFrame;
+ try
+ {
+ xContainer->getByIndex(i) >>= xFrame;
+ if (!xFrame.is())
+ continue;
+ }
+ // can happen in multithreaded environments, that frames was removed from the container during this loop runs!
+ // Ignore it.
+ catch(const css::lang::IndexOutOfBoundsException&)
+ {
+ continue;
+ }
+
+ // We are interested on visible documents only.
+ // Note: It's n optional interface .-(
+ css::uno::Reference< css::awt::XWindow2 > xVisibleCheck(
+ xFrame->getContainerWindow(),
+ css::uno::UNO_QUERY);
+ if (
+ (!xVisibleCheck.is() ) ||
+ (!xVisibleCheck->isVisible())
+ )
+ {
+ continue;
+ }
+
+ // extract the model from the frame.
+ // Ignore "view only" frames, which does not have a model.
+ css::uno::Reference< css::frame::XController > xController;
+ css::uno::Reference< css::frame::XModel3 > xModel;
+
+ xController = xFrame->getController();
+ if (xController.is())
+ xModel.set( xController->getModel(), UNO_QUERY_THROW );
+ if (!xModel.is())
+ continue;
+
+ // insert model into cache ...
+ // If the model is already well known inside cache
+ // it's information set will be updated by asking the
+ // model again for its new states.
+ implts_registerDocument(xModel);
+ }
+ }
+ catch(const css::uno::RuntimeException&)
+ {
+ throw;
+ }
+ catch(const css::uno::Exception&)
+ {
+ }
+
+ SAL_INFO("fwk.autorecovery", "... AutoRecovery::implts_verifyCacheAgainstDesktopDocumentList()");
+}
+
+bool AutoRecovery::impl_enoughDiscSpace(sal_Int32 nRequiredSpace)
+{
+#ifdef SIMULATE_FULL_DISC
+ return sal_False;
+#else // SIMULATE_FULL_DISC
+ // In case an error occurs and we are not able to retrieve the needed information
+ // it's better to "disable" the feature ShowErrorOnFullDisc !
+ // Otherwise we start a confusing process of error handling ...
+
+ sal_uInt64 nFreeSpace = SAL_MAX_UINT64;
+
+ OUString sBackupPath(SvtPathOptions().GetBackupPath());
+ ::osl::VolumeInfo aInfo (osl_VolumeInfo_Mask_FreeSpace);
+ ::osl::FileBase::RC aRC = ::osl::Directory::getVolumeInfo(sBackupPath, aInfo);
+
+ if (
+ (aInfo.isValid(osl_VolumeInfo_Mask_FreeSpace)) &&
+ (aRC == ::osl::FileBase::E_None )
+ )
+ {
+ nFreeSpace = aInfo.getFreeSpace();
+ }
+
+ sal_uInt64 nFreeMB = nFreeSpace/1048576;
+ return (nFreeMB >= o3tl::make_unsigned(nRequiredSpace));
+#endif // SIMULATE_FULL_DISC
+}
+
+void AutoRecovery::impl_showFullDiscError()
+{
+ OUString sBtn(FwkResId(STR_FULL_DISC_RETRY_BUTTON));
+ OUString sMsg(FwkResId(STR_FULL_DISC_MSG));
+
+ OUString sBackupURL(SvtPathOptions().GetBackupPath());
+ INetURLObject aConverter(sBackupURL);
+ sal_Unicode aDelimiter;
+ OUString sBackupPath = aConverter.getFSysPath(FSysStyle::Detect, &aDelimiter);
+ if (sBackupPath.getLength() < 1)
+ sBackupPath = sBackupURL;
+
+ std::unique_ptr<weld::MessageDialog> xBox(Application::CreateMessageDialog(nullptr,
+ VclMessageType::Error, VclButtonsType::NONE,
+ sMsg.replaceAll("%PATH", sBackupPath)));
+ xBox->add_button(sBtn, RET_OK);
+ xBox->run();
+}
+
+void AutoRecovery::impl_establishProgress(const AutoRecovery::TDocumentInfo& rInfo ,
+ utl::MediaDescriptor& rArgs ,
+ const css::uno::Reference< css::frame::XFrame >& xNewFrame)
+{
+ // external well known frame must be preferred (because it was created by ourself
+ // for loading documents into this frame)!
+ // But if no frame exists... we can try to locate it using any frame bound to the provided
+ // document. Of course we must live without any frame in case the document does not exists at this
+ // point. But this state should not occur. In such case xNewFrame should be valid ... hopefully .-)
+ css::uno::Reference< css::frame::XFrame > xFrame = xNewFrame;
+ if (
+ (!xFrame.is() ) &&
+ (rInfo.Document.is())
+ )
+ {
+ css::uno::Reference< css::frame::XController > xController = rInfo.Document->getCurrentController();
+ if (xController.is())
+ xFrame = xController->getFrame();
+ }
+
+ // Any outside progress must be used ...
+ // Only if there is no progress, we can create our own one.
+ css::uno::Reference< css::task::XStatusIndicator > xInternalProgress;
+ css::uno::Reference< css::task::XStatusIndicator > xExternalProgress = rArgs.getUnpackedValueOrDefault(
+ utl::MediaDescriptor::PROP_STATUSINDICATOR,
+ css::uno::Reference< css::task::XStatusIndicator >() );
+
+ // Normally a progress is set from outside (e.g. by the CrashSave/Recovery dialog, which uses our dispatch API).
+ // But for a normal auto save we don't have such "external progress"... because this function is triggered by our own timer then.
+ // In such case we must create our own progress !
+ if (
+ (! xExternalProgress.is()) &&
+ (xFrame.is() )
+ )
+ {
+ css::uno::Reference< css::task::XStatusIndicatorFactory > xProgressFactory(xFrame, css::uno::UNO_QUERY);
+ if (xProgressFactory.is())
+ xInternalProgress = xProgressFactory->createStatusIndicator();
+ }
+
+ // HACK
+ // An external provided progress (most given by the CrashSave/Recovery dialog)
+ // must be preferred. But we know that some application filters query its own progress instance
+ // at the frame method Frame::createStatusIndicator().
+ // So we use a two step mechanism:
+ // 1) we set the progress inside the MediaDescriptor, which will be provided to the filter
+ // 2) and we set a special Frame property, which overwrites the normal behaviour of Frame::createStatusIndicator .-)
+ // But we suppress 2) in case we uses an internal progress. Because then it doesn't matter
+ // if our applications make it wrong. In such case the internal progress resists at the same frame
+ // and there is no need to forward progress activities to e.g. an outside dialog .-)
+ if (
+ (xExternalProgress.is()) &&
+ (xFrame.is() )
+ )
+ {
+ css::uno::Reference< css::beans::XPropertySet > xFrameProps(xFrame, css::uno::UNO_QUERY);
+ if (xFrameProps.is())
+ xFrameProps->setPropertyValue(FRAME_PROPNAME_ASCII_INDICATORINTERCEPTION, css::uno::Any(xExternalProgress));
+ }
+
+ // But inside the MediaDescriptor we must set our own create progress ...
+ // in case there is not already another progress set.
+ rArgs.createItemIfMissing(utl::MediaDescriptor::PROP_STATUSINDICATOR, xInternalProgress);
+}
+
+void AutoRecovery::impl_forgetProgress(const AutoRecovery::TDocumentInfo& rInfo ,
+ utl::MediaDescriptor& rArgs ,
+ const css::uno::Reference< css::frame::XFrame >& xNewFrame)
+{
+ // external well known frame must be preferred (because it was created by ourself
+ // for loading documents into this frame)!
+ // But if no frame exists... we can try to locate it using any frame bound to the provided
+ // document. Of course we must live without any frame in case the document does not exists at this
+ // point. But this state should not occur. In such case xNewFrame should be valid ... hopefully .-)
+ css::uno::Reference< css::frame::XFrame > xFrame = xNewFrame;
+ if (
+ (!xFrame.is() ) &&
+ (rInfo.Document.is())
+ )
+ {
+ css::uno::Reference< css::frame::XController > xController = rInfo.Document->getCurrentController();
+ if (xController.is())
+ xFrame = xController->getFrame();
+ }
+
+ // stop progress interception on corresponding frame.
+ css::uno::Reference< css::beans::XPropertySet > xFrameProps(xFrame, css::uno::UNO_QUERY);
+ if (xFrameProps.is())
+ xFrameProps->setPropertyValue(FRAME_PROPNAME_ASCII_INDICATORINTERCEPTION, css::uno::Any(css::uno::Reference< css::task::XStatusIndicator >()));
+
+ // forget progress inside list of arguments.
+ utl::MediaDescriptor::iterator pArg = rArgs.find(utl::MediaDescriptor::PROP_STATUSINDICATOR);
+ if (pArg != rArgs.end())
+ {
+ rArgs.erase(pArg);
+ pArg = rArgs.end();
+ }
+}
+
+void AutoRecovery::impl_flushALLConfigChanges()
+{
+ try
+ {
+ // SOLAR SAFE ->
+ SolarMutexGuard aGuard;
+ ::utl::ConfigManager::storeConfigItems();
+ }
+ catch(const css::uno::Exception&)
+ {
+ }
+}
+
+void AutoRecovery::st_impl_removeFile(const OUString& sURL)
+{
+ if ( sURL.isEmpty())
+ return;
+
+ try
+ {
+ ::ucbhelper::Content aContent(sURL, css::uno::Reference< css::ucb::XCommandEnvironment >(), m_xContext);
+ aContent.executeCommand("delete", css::uno::Any(true));
+ }
+ catch(const css::uno::Exception&)
+ {
+ }
+}
+
+void AutoRecovery::st_impl_removeLockFile()
+{
+ try
+ {
+ OUString sUserURL;
+ ::utl::Bootstrap::locateUserInstallation( sUserURL );
+
+ OUString sLockURL = sUserURL + "/.lock";
+ AutoRecovery::st_impl_removeFile(sLockURL);
+ }
+ catch(const css::uno::Exception&)
+ {
+ }
+}
+
+}
+
+extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface *
+com_sun_star_comp_framework_AutoRecovery_get_implementation(
+ css::uno::XComponentContext *context,
+ css::uno::Sequence<css::uno::Any> const &)
+{
+ rtl::Reference<AutoRecovery> xAutoRecovery = new AutoRecovery(context);
+ // 2nd phase initialization needed
+ xAutoRecovery->initListeners();
+
+ return cppu::acquire(xAutoRecovery.get());
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/framework/source/services/desktop.cxx b/framework/source/services/desktop.cxx
new file mode 100644
index 000000000..cbc3ce6eb
--- /dev/null
+++ b/framework/source/services/desktop.cxx
@@ -0,0 +1,1775 @@
+/* -*- 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 <framework/desktop.hxx>
+
+#include <loadenv/loadenv.hxx>
+
+#include <helper/ocomponentaccess.hxx>
+#include <helper/oframes.hxx>
+#include <dispatch/dispatchprovider.hxx>
+
+#include <dispatch/interceptionhelper.hxx>
+#include <classes/taskcreator.hxx>
+#include <threadhelp/transactionguard.hxx>
+#include <properties.h>
+#include <targets.h>
+
+#include <strings.hrc>
+#include <classes/fwkresid.hxx>
+
+#include <com/sun/star/beans/PropertyAttribute.hpp>
+#include <com/sun/star/frame/FrameSearchFlag.hpp>
+#include <com/sun/star/frame/TerminationVetoException.hpp>
+#include <com/sun/star/task/XInteractionAbort.hpp>
+#include <com/sun/star/task/XInteractionApprove.hpp>
+#include <com/sun/star/document/XInteractionFilterSelect.hpp>
+#include <com/sun/star/task/ErrorCodeRequest.hpp>
+#include <com/sun/star/frame/DispatchResultState.hpp>
+#include <com/sun/star/lang/DisposedException.hpp>
+#include <com/sun/star/util/CloseVetoException.hpp>
+#include <com/sun/star/util/XCloseable.hpp>
+#include <com/sun/star/frame/XTerminateListener2.hpp>
+
+#include <comphelper/numberedcollection.hxx>
+#include <comphelper/sequence.hxx>
+#include <comphelper/lok.hxx>
+#include <cppuhelper/supportsservice.hxx>
+#include <utility>
+#include <vcl/svapp.hxx>
+#include <desktop/crashreport.hxx>
+#include <vcl/scheduler.hxx>
+#include <sal/log.hxx>
+#include <vcl/errcode.hxx>
+#include <vcl/threadex.hxx>
+#include <unotools/configmgr.hxx>
+
+namespace framework{
+
+namespace {
+
+enum PropHandle {
+ ActiveFrame, DispatchRecorderSupplier, IsPlugged, SuspendQuickstartVeto,
+ Title };
+
+}
+
+OUString SAL_CALL Desktop::getImplementationName()
+{
+ return "com.sun.star.comp.framework.Desktop";
+}
+
+sal_Bool SAL_CALL Desktop::supportsService(OUString const & ServiceName)
+{
+ return cppu::supportsService(this, ServiceName);
+}
+
+css::uno::Sequence<OUString> SAL_CALL Desktop::getSupportedServiceNames()
+{
+ return { "com.sun.star.frame.Desktop" };
+}
+
+void Desktop::constructorInit()
+{
+ // Initialize a new XFrames-helper-object to handle XIndexAccess and XElementAccess.
+ // We hold member as reference ... not as pointer too!
+ // Attention: We share our frame container with this helper. Container is threadsafe himself ... So I think we can do that.
+ // But look on dispose() for right order of deinitialization.
+ m_xFramesHelper = new OFrames( this, &m_aChildTaskContainer );
+
+ // Initialize a new dispatchhelper-object to handle dispatches.
+ // We use these helper as slave for our interceptor helper ... not directly!
+ // But he is event listener on THIS instance!
+ rtl::Reference<DispatchProvider> xDispatchProvider = new DispatchProvider( m_xContext, this );
+
+ // Initialize a new interception helper object to handle dispatches and implement an interceptor mechanism.
+ // Set created dispatch provider as slowest slave of it.
+ // Hold interception helper by reference only - not by pointer!
+ // So it's easier to destroy it.
+ m_xDispatchHelper = new InterceptionHelper( this, xDispatchProvider );
+
+ OUString sUntitledPrefix = FwkResId(STR_UNTITLED_DOCUMENT) + " ";
+
+ rtl::Reference<::comphelper::NumberedCollection> pNumbers = new ::comphelper::NumberedCollection ();
+ m_xTitleNumberGenerator = pNumbers;
+ pNumbers->setOwner ( static_cast< ::cppu::OWeakObject* >(this) );
+ pNumbers->setUntitledPrefix ( sUntitledPrefix );
+
+ // Safe impossible cases
+ // We can't work without this helper!
+ SAL_WARN_IF( !m_xFramesHelper.is(), "fwk.desktop", "Desktop::Desktop(): Frames helper is not valid. XFrames, XIndexAccess and XElementAccess are not supported!");
+ SAL_WARN_IF( !m_xDispatchHelper.is(), "fwk.desktop", "Desktop::Desktop(): Dispatch helper is not valid. XDispatch will not work correctly!" );
+
+ // Enable object for real working!
+ // Otherwise all calls will be rejected ...
+ m_aTransactionManager.setWorkingMode( E_WORK );
+}
+
+/*-************************************************************************************************************
+ @short standard constructor to create instance by factory
+ @descr This constructor initialize a new instance of this class by valid factory,
+ and will be set valid values on his member and baseclasses.
+
+ @attention a) Don't use your own reference during a UNO-Service-ctor! There is no guarantee, that you
+ will get over this. (e.g. using of your reference as parameter to initialize some member)
+ Do such things in DEFINE_INIT_SERVICE() method, which is called automatically after your ctor!!!
+ b) Baseclass OBroadcastHelper is a typedef in namespace cppu!
+ The microsoft compiler has some problems to handle it right BY using namespace explicitly ::cppu::OBroadcastHelper.
+ If we write it without a namespace or expand the typedef to OBroadcastHelperVar<...> -> it will be OK!?
+ I don't know why! (other compiler not tested .. but it works!)
+
+ @seealso method DEFINE_INIT_SERVICE()
+
+ @param "xFactory" is the multi service manager, which create this instance.
+ The value must be different from NULL!
+ @onerror We throw an ASSERT in debug version or do nothing in release version.
+*//*-*************************************************************************************************************/
+Desktop::Desktop( css::uno::Reference< css::uno::XComponentContext > xContext )
+ : Desktop_BASE ( m_aMutex )
+ , cppu::OPropertySetHelper( cppu::WeakComponentImplHelperBase::rBHelper )
+ // Init member
+ , m_bIsTerminated(false)
+ , m_bIsShutdown(false) // see dispose() for further information!
+ , m_bSession ( false )
+ , m_xContext (std::move( xContext ))
+ , m_aListenerContainer ( m_aMutex )
+ , m_eLoadState ( E_NOTSET )
+ , m_bSuspendQuickstartVeto( false )
+{
+}
+
+/*-************************************************************************************************************
+ @short standard destructor
+ @descr This one do NOTHING! Use dispose() instead of this.
+
+ @seealso method dispose()
+*//*-*************************************************************************************************************/
+Desktop::~Desktop()
+{
+ SAL_WARN_IF(!m_bIsShutdown, "fwk.desktop", "Desktop not terminated before being destructed");
+ SAL_WARN_IF( m_aTransactionManager.getWorkingMode()!=E_CLOSE, "fwk.desktop", "Desktop::~Desktop(): Who forgot to dispose this service?" );
+}
+
+css::uno::Any SAL_CALL Desktop::queryInterface( const css::uno::Type& _rType )
+{
+ css::uno::Any aRet = Desktop_BASE::queryInterface( _rType );
+ if ( !aRet.hasValue() )
+ aRet = OPropertySetHelper::queryInterface( _rType );
+ return aRet;
+}
+
+css::uno::Sequence< css::uno::Type > SAL_CALL Desktop::getTypes( )
+{
+ return comphelper::concatSequences(
+ Desktop_BASE::getTypes(),
+ ::cppu::OPropertySetHelper::getTypes()
+ );
+}
+
+sal_Bool SAL_CALL Desktop::terminate()
+{
+ TransactionGuard aTransaction( m_aTransactionManager, E_HARDEXCEPTIONS );
+ SolarMutexResettableGuard aGuard;
+
+ if (m_bIsTerminated)
+ return true;
+
+ css::uno::Reference< css::frame::XTerminateListener > xPipeTerminator = m_xPipeTerminator;
+ css::uno::Reference< css::frame::XTerminateListener > xQuickLauncher = m_xQuickLauncher;
+ css::uno::Reference< css::frame::XTerminateListener > xSWThreadManager = m_xSWThreadManager;
+ css::uno::Reference< css::frame::XTerminateListener > xSfxTerminator = m_xSfxTerminator;
+
+ css::lang::EventObject aEvent ( static_cast< ::cppu::OWeakObject* >(this) );
+ bool bAskQuickStart = !m_bSuspendQuickstartVeto;
+ const bool bRestartableMainLoop = Application::IsEventTestingModeEnabled() ||
+ comphelper::LibreOfficeKit::isActive();
+ aGuard.clear();
+
+ // Allow using of any UI ... because Desktop.terminate() was designed as UI functionality in the past.
+
+ // Ask normal terminate listener. They could veto terminating the process.
+ Desktop::TTerminateListenerList lCalledTerminationListener;
+ if (!impl_sendQueryTerminationEvent(lCalledTerminationListener))
+ {
+ impl_sendCancelTerminationEvent(lCalledTerminationListener);
+ return false;
+ }
+
+ // try to close all open frames
+ if (!impl_closeFrames(!bRestartableMainLoop))
+ {
+ impl_sendCancelTerminationEvent(lCalledTerminationListener);
+ return false;
+ }
+
+ // Normal listener had no problem ...
+ // all frames was closed ...
+ // now it's time to ask our specialized listener.
+ // They are handled these way because they wish to hinder the office on termination
+ // but they wish also closing of all frames.
+
+ // Note further:
+ // We shouldn't ask quicklauncher in case it was allowed from outside only.
+ // This is special trick to "ignore existing quick starter" for debug purposes.
+
+ // Attention:
+ // Order of called listener is important!
+ // Some of them are harmless,-)
+ // but some can be dangerous. E.g. it would be dangerous if we close our pipe
+ // and don't terminate in real because another listener throws a veto exception .-)
+
+ try
+ {
+ if( bAskQuickStart && xQuickLauncher.is() )
+ {
+ xQuickLauncher->queryTermination( aEvent );
+ lCalledTerminationListener.push_back( xQuickLauncher );
+ }
+
+ if ( xSWThreadManager.is() )
+ {
+ xSWThreadManager->queryTermination( aEvent );
+ lCalledTerminationListener.push_back( xSWThreadManager );
+ }
+
+ if ( xPipeTerminator.is() )
+ {
+ xPipeTerminator->queryTermination( aEvent );
+ lCalledTerminationListener.push_back( xPipeTerminator );
+ }
+
+ if ( xSfxTerminator.is() )
+ {
+ xSfxTerminator->queryTermination( aEvent );
+ lCalledTerminationListener.push_back( xSfxTerminator );
+ }
+ }
+ catch(const css::frame::TerminationVetoException&)
+ {
+ impl_sendCancelTerminationEvent(lCalledTerminationListener);
+ return false;
+ }
+
+ aGuard.reset();
+ if (m_bIsTerminated)
+ return true;
+ m_bIsTerminated = true;
+
+ if (!bRestartableMainLoop)
+ {
+ CrashReporter::addKeyValue("ShutDown", OUString::boolean(true), CrashReporter::Write);
+
+ // The clipboard listener needs to be the first. It can create copies of the
+ // existing document which needs basically all the available infrastructure.
+ impl_sendTerminateToClipboard();
+ {
+ SolarMutexReleaser aReleaser;
+ impl_sendNotifyTerminationEvent();
+ }
+ Scheduler::ProcessEventsToIdle();
+
+ if( bAskQuickStart && xQuickLauncher.is() )
+ xQuickLauncher->notifyTermination( aEvent );
+
+ if ( xSWThreadManager.is() )
+ xSWThreadManager->notifyTermination( aEvent );
+
+ if ( xPipeTerminator.is() )
+ xPipeTerminator->notifyTermination( aEvent );
+
+ // further termination is postponed to shutdown, if LO already runs the main loop
+ if (!Application::IsInExecute())
+ shutdown();
+ }
+ else
+ m_bIsShutdown = true;
+
+#ifndef IOS // or ANDROID?
+ aGuard.clear();
+ // In the iOS app, posting the ImplQuitMsg user event will be too late, it will not be handled during the
+ // lifetime of the current document, but handled for the next document opened, which thus will break horribly.
+ Application::Quit();
+#endif
+
+ return true;
+}
+
+void Desktop::shutdown()
+{
+ TransactionGuard aTransaction(m_aTransactionManager, E_HARDEXCEPTIONS);
+ SolarMutexGuard aGuard;
+
+ if (m_bIsShutdown)
+ return;
+ m_bIsShutdown = true;
+
+ css::uno::Reference<css::frame::XTerminateListener> xSfxTerminator = m_xSfxTerminator;
+ css::lang::EventObject aEvent(static_cast<::cppu::OWeakObject* >(this));
+
+ // we need a copy here as the notifyTermination call might cause a removeTerminateListener call
+ std::vector< css::uno::Reference<css::frame::XTerminateListener> > xComponentDllListeners;
+ xComponentDllListeners.swap(m_xComponentDllListeners);
+ for (auto& xListener : xComponentDllListeners)
+ xListener->notifyTermination(aEvent);
+ xComponentDllListeners.clear();
+
+ // Must be really the last listener to be called.
+ // Because it shuts down the whole process asynchronous!
+ if (xSfxTerminator.is())
+ xSfxTerminator->notifyTermination(aEvent);
+}
+
+namespace
+{
+ class QuickstartSuppressor
+ {
+ Desktop* const m_pDesktop;
+ css::uno::Reference< css::frame::XTerminateListener > m_xQuickLauncher;
+ public:
+ QuickstartSuppressor(Desktop* const pDesktop, css::uno::Reference< css::frame::XTerminateListener > xQuickLauncher)
+ : m_pDesktop(pDesktop)
+ , m_xQuickLauncher(std::move(xQuickLauncher))
+ {
+ SAL_INFO("fwk.desktop", "temporary removing Quickstarter");
+ if(m_xQuickLauncher.is())
+ m_pDesktop->removeTerminateListener(m_xQuickLauncher);
+ }
+ ~QuickstartSuppressor()
+ {
+ SAL_INFO("fwk.desktop", "readding Quickstarter");
+ if(m_xQuickLauncher.is())
+ m_pDesktop->addTerminateListener(m_xQuickLauncher);
+ }
+ };
+}
+
+bool Desktop::terminateQuickstarterToo()
+{
+ QuickstartSuppressor aQuickstartSuppressor(this, m_xQuickLauncher);
+ m_bSession = true;
+ return terminate();
+}
+
+void SAL_CALL Desktop::addTerminateListener( const css::uno::Reference< css::frame::XTerminateListener >& xListener )
+{
+ TransactionGuard aTransaction( m_aTransactionManager, E_HARDEXCEPTIONS );
+
+ css::uno::Reference< css::lang::XServiceInfo > xInfo( xListener, css::uno::UNO_QUERY );
+ if ( xInfo.is() )
+ {
+ OUString sImplementationName = xInfo->getImplementationName();
+
+ SolarMutexGuard g;
+
+ if( sImplementationName == "com.sun.star.comp.sfx2.SfxTerminateListener" )
+ {
+ m_xSfxTerminator = xListener;
+ return;
+ }
+ if( sImplementationName == "com.sun.star.comp.RequestHandlerController" )
+ {
+ m_xPipeTerminator = xListener;
+ return;
+ }
+ if( sImplementationName == "com.sun.star.comp.desktop.QuickstartWrapper" )
+ {
+ m_xQuickLauncher = xListener;
+ return;
+ }
+ if( sImplementationName == "com.sun.star.util.comp.FinalThreadManager" )
+ {
+ m_xSWThreadManager = xListener;
+ return;
+ }
+ else if ( sImplementationName == "com.sun.star.comp.ComponentDLLListener" )
+ {
+ m_xComponentDllListeners.push_back(xListener);
+ return;
+ }
+ }
+
+ // No lock required... container is threadsafe by itself.
+ m_aListenerContainer.addInterface( cppu::UnoType<css::frame::XTerminateListener>::get(), xListener );
+}
+
+void SAL_CALL Desktop::removeTerminateListener( const css::uno::Reference< css::frame::XTerminateListener >& xListener )
+{
+ TransactionGuard aTransaction( m_aTransactionManager, E_SOFTEXCEPTIONS );
+
+ css::uno::Reference< css::lang::XServiceInfo > xInfo( xListener, css::uno::UNO_QUERY );
+ if ( xInfo.is() )
+ {
+ OUString sImplementationName = xInfo->getImplementationName();
+
+ SolarMutexGuard g;
+
+ if( sImplementationName == "com.sun.star.comp.sfx2.SfxTerminateListener" )
+ {
+ m_xSfxTerminator.clear();
+ return;
+ }
+
+ if( sImplementationName == "com.sun.star.comp.RequestHandlerController" )
+ {
+ m_xPipeTerminator.clear();
+ return;
+ }
+
+ if( sImplementationName == "com.sun.star.comp.desktop.QuickstartWrapper" )
+ {
+ m_xQuickLauncher.clear();
+ return;
+ }
+
+ if( sImplementationName == "com.sun.star.util.comp.FinalThreadManager" )
+ {
+ m_xSWThreadManager.clear();
+ return;
+ }
+ else if (sImplementationName == "com.sun.star.comp.ComponentDLLListener")
+ {
+ m_xComponentDllListeners.erase(
+ std::remove(m_xComponentDllListeners.begin(), m_xComponentDllListeners.end(), xListener),
+ m_xComponentDllListeners.end());
+ return;
+ }
+ }
+
+ // No lock required ... container is threadsafe by itself.
+ m_aListenerContainer.removeInterface( cppu::UnoType<css::frame::XTerminateListener>::get(), xListener );
+}
+
+/*-************************************************************************************************************
+ @interface XDesktop
+ @short get access to create enumerations of all current components
+ @descr You will be the owner of the returned object and must delete it if you don't use it again.
+
+ @seealso class TasksAccess
+ @seealso class TasksEnumeration
+ @return A reference to an XEnumerationAccess-object.
+
+ @onerror We return a null-reference.
+ @threadsafe yes
+*//*-*************************************************************************************************************/
+css::uno::Reference< css::container::XEnumerationAccess > SAL_CALL Desktop::getComponents()
+{
+ /* UNSAFE AREA --------------------------------------------------------------------------------------------- */
+ // Register transaction and reject wrong calls.
+ TransactionGuard aTransaction( m_aTransactionManager, E_HARDEXCEPTIONS );
+
+ // We use a helper class OComponentAccess to have access on all child components.
+ // Create it on demand and return it as a reference.
+ return new OComponentAccess( this );
+}
+
+/*-************************************************************************************************************
+ @interface XDesktop
+ @short return the current active component
+ @descr The most current component is the window, model or the controller of the current active frame.
+
+ @seealso method getCurrentFrame()
+ @seealso method impl_getFrameComponent()
+ @return A reference to the component.
+
+ @onerror We return a null-reference.
+ @threadsafe yes
+*//*-*************************************************************************************************************/
+css::uno::Reference< css::lang::XComponent > SAL_CALL Desktop::getCurrentComponent()
+{
+ /* UNSAFE AREA --------------------------------------------------------------------------------------------- */
+ // Register transaction and reject wrong calls.
+ TransactionGuard aTransaction( m_aTransactionManager, E_HARDEXCEPTIONS );
+
+ // Set return value if method failed.
+ css::uno::Reference< css::lang::XComponent > xComponent;
+
+ // Get reference to current frame ...
+ // ... get component of this frame ... (It can be the window, the model or the controller.)
+ // ... and return the result.
+ css::uno::Reference< css::frame::XFrame > xCurrentFrame = getCurrentFrame();
+ if( xCurrentFrame.is() )
+ {
+ xComponent = impl_getFrameComponent( xCurrentFrame );
+ }
+ return xComponent;
+}
+
+/*-************************************************************************************************************
+ @interface XDesktop
+ @short return the current active frame in hierarchy
+ @descr There can be more than one different active paths in our frame hierarchy. But only one of them
+ could be the most active frame (normal he has the focus).
+ Don't mix it with getActiveFrame()! That will return our current active frame, which must be
+ a direct child of us and should be a part(!) of an active path.
+
+ @seealso method getActiveFrame()
+ @return A valid reference, if there is an active frame.
+ A null reference , otherwise.
+
+ @onerror We return a null reference.
+ @threadsafe yes
+*//*-*************************************************************************************************************/
+css::uno::Reference< css::frame::XFrame > SAL_CALL Desktop::getCurrentFrame()
+{
+ /* UNSAFE AREA --------------------------------------------------------------------------------------------- */
+ // Register transaction and reject wrong calls.
+ TransactionGuard aTransaction( m_aTransactionManager, E_HARDEXCEPTIONS );
+
+ // Start search with our direct active frame (if it exist!).
+ // Search on his children for other active frames too.
+ // Stop if no one could be found and return last of found ones.
+ css::uno::Reference< css::frame::XFramesSupplier > xLast( getActiveFrame(), css::uno::UNO_QUERY );
+ if( xLast.is() )
+ {
+ css::uno::Reference< css::frame::XFramesSupplier > xNext( xLast->getActiveFrame(), css::uno::UNO_QUERY );
+ while( xNext.is() )
+ {
+ xLast = xNext;
+ xNext.set( xNext->getActiveFrame(), css::uno::UNO_QUERY );
+ }
+ }
+ return xLast;
+}
+
+/*-************************************************************************************************************
+ @interface XComponentLoader
+ @short try to load given URL into a task
+ @descr You can give us some information about the content, which you will load into a frame.
+ We search or create this target for you, make a type detection of given URL and try to load it.
+ As result of this operation we return the new created component or nothing, if loading failed.
+ @param "sURL" , URL, which represent the content
+ @param "sTargetFrameName" , name of target frame or special value like "_self", "_blank" ...
+ @param "nSearchFlags" , optional arguments for frame search, if target isn't a special one
+ @param "lArguments" , optional arguments for loading
+ @return A valid component reference, if loading was successful.
+ A null reference otherwise.
+
+ @onerror We return a null reference.
+ @threadsafe yes
+*//*-*************************************************************************************************************/
+css::uno::Reference< css::lang::XComponent > SAL_CALL Desktop::loadComponentFromURL( const OUString& sURL ,
+ const OUString& sTargetFrameName,
+ sal_Int32 nSearchFlags ,
+ const css::uno::Sequence< css::beans::PropertyValue >& lArguments )
+{
+ /* UNSAFE AREA --------------------------------------------------------------------------------------------- */
+ // Register transaction and reject wrong calls.
+ TransactionGuard aTransaction( m_aTransactionManager, E_HARDEXCEPTIONS );
+ SAL_INFO( "fwk.desktop", "loadComponentFromURL" );
+
+ css::uno::Reference< css::frame::XComponentLoader > xThis(this);
+
+ utl::MediaDescriptor aDescriptor(lArguments);
+ bool bOnMainThread = aDescriptor.getUnpackedValueOrDefault("OnMainThread", false);
+
+ if (bOnMainThread)
+ {
+ // Make sure that we own the solar mutex, otherwise later
+ // vcl::SolarThreadExecutor::execute() will release the solar mutex, even if it's owned by
+ // another thread, leading to an std::abort() at the end.
+ SolarMutexGuard g;
+
+ return vcl::solarthread::syncExecute(std::bind(&LoadEnv::loadComponentFromURL, xThis,
+ m_xContext, sURL, sTargetFrameName,
+ nSearchFlags, lArguments));
+ }
+ else
+ {
+ return LoadEnv::loadComponentFromURL(xThis, m_xContext, sURL, sTargetFrameName,
+ nSearchFlags, lArguments);
+ }
+}
+
+/*-************************************************************************************************************
+ @interface XTasksSupplier
+ @short get access to create enumerations of our taskchildren
+ @descr Direct children of desktop are tasks every time.
+ Call these method to could create enumerations of it.
+
+But; Don't forget - you will be the owner of returned object and must release it!
+ We use a helper class to implement the access interface. They hold a weakreference to us.
+ It can be, that the desktop is dead - but not your tasksaccess-object! Then they will do nothing!
+ You can't create enumerations then.
+
+ @attention Normally we don't need any lock here. We don't work on internal member!
+
+ @seealso class TasksAccess
+ @return A reference to an accessobject, which can create enumerations of our childtasks.
+
+ @onerror A null reference is returned.
+ @threadsafe yes
+*//*-*************************************************************************************************************/
+css::uno::Reference< css::container::XEnumerationAccess > SAL_CALL Desktop::getTasks()
+{
+ SAL_INFO("fwk.desktop", "Desktop::getTasks(): Use of obsolete interface XTaskSupplier");
+ return nullptr;
+}
+
+/*-************************************************************************************************************
+ @interface XTasksSupplier
+ @short return current active task of our direct children
+ @descr Desktop children are tasks only ! If we have an active path from desktop
+ as top to any frame on bottom, we must have an active direct child. His reference is returned here.
+
+ @attention a) Do not confuse it with getCurrentFrame()! The current frame don't must one of our direct children.
+ It can be every frame in subtree and must have the focus (Is the last one of an active path!).
+ b) We don't need any lock here. Our container is threadsafe himself and live, if we live!
+
+ @seealso method getCurrentFrame()
+ @return A reference to our current active taskchild.
+
+ @onerror A null reference is returned.
+ @threadsafe yes
+*//*-*************************************************************************************************************/
+css::uno::Reference< css::frame::XTask > SAL_CALL Desktop::getActiveTask()
+{
+ SAL_INFO("fwk.desktop", "Desktop::getActiveTask(): Use of obsolete interface XTaskSupplier");
+ return nullptr;
+}
+
+/*-************************************************************************************************************
+ @interface XDispatchProvider
+ @short search a dispatcher for given URL
+ @descr We use a helper implementation (class DispatchProvider) to do so.
+ So we don't must implement this algorithm twice!
+
+ @attention We don't need any lock here. Our helper is threadsafe himself and live, if we live!
+
+ @seealso class DispatchProvider
+
+ @param "aURL" , URL to dispatch
+ @param "sTargetFrameName" , name of target frame, who should dispatch these URL
+ @param "nSearchFlags" , flags to regulate the search
+ @param "lQueries" , list of queryDispatch() calls!
+ @return A reference or list of founded dispatch objects for these URL.
+
+ @onerror A null reference is returned.
+ @threadsafe yes
+*//*-*************************************************************************************************************/
+css::uno::Reference< css::frame::XDispatch > SAL_CALL Desktop::queryDispatch( const css::util::URL& aURL ,
+ const OUString& sTargetFrameName ,
+ sal_Int32 nSearchFlags )
+{
+ /* UNSAFE AREA --------------------------------------------------------------------------------------------- */
+ // Register transaction and reject wrong calls.
+ TransactionGuard aTransaction( m_aTransactionManager, E_HARDEXCEPTIONS );
+
+ // Remove uno and cmd protocol part as we want to support both of them. We store only the command part
+ // in our hash map. All other protocols are stored with the protocol part.
+ OUString aCommand( aURL.Main );
+ if ( aURL.Protocol.equalsIgnoreAsciiCase(".uno:") )
+ aCommand = aURL.Path;
+
+ if (!m_xCommandOptions && !utl::ConfigManager::IsFuzzing())
+ m_xCommandOptions.reset(new SvtCommandOptions);
+
+ // Make std::unordered_map lookup if the current URL is in the disabled list
+ if (m_xCommandOptions && m_xCommandOptions->Lookup(SvtCommandOptions::CMDOPTION_DISABLED, aCommand))
+ return css::uno::Reference< css::frame::XDispatch >();
+ else
+ {
+ // We use a helper to support these interface and an interceptor mechanism.
+ // Our helper is threadsafe by himself!
+ return m_xDispatchHelper->queryDispatch( aURL, sTargetFrameName, nSearchFlags );
+ }
+}
+
+css::uno::Sequence< css::uno::Reference< css::frame::XDispatch > > SAL_CALL Desktop::queryDispatches( const css::uno::Sequence< css::frame::DispatchDescriptor >& lQueries )
+{
+ /* UNSAFE AREA --------------------------------------------------------------------------------------------- */
+ // Register transaction and reject wrong calls.
+ TransactionGuard aTransaction( m_aTransactionManager, E_HARDEXCEPTIONS );
+
+ return m_xDispatchHelper->queryDispatches( lQueries );
+}
+
+/*-************************************************************************************************************
+ @interface XDispatchProviderInterception
+ @short supports registration/deregistration of interception objects, which
+ are interested on special dispatches.
+
+ @descr It's really provided by an internal helper, which is used inside the dispatch API too.
+ @param xInterceptor
+ the interceptor object, which wishes to be (de)registered.
+
+ @threadsafe yes
+*//*-*************************************************************************************************************/
+void SAL_CALL Desktop::registerDispatchProviderInterceptor( const css::uno::Reference< css::frame::XDispatchProviderInterceptor >& xInterceptor)
+{
+ TransactionGuard aTransaction( m_aTransactionManager, E_HARDEXCEPTIONS );
+
+ css::uno::Reference< css::frame::XDispatchProviderInterception > xInterceptionHelper( m_xDispatchHelper, css::uno::UNO_QUERY );
+ xInterceptionHelper->registerDispatchProviderInterceptor( xInterceptor );
+}
+
+void SAL_CALL Desktop::releaseDispatchProviderInterceptor ( const css::uno::Reference< css::frame::XDispatchProviderInterceptor >& xInterceptor)
+{
+ TransactionGuard aTransaction( m_aTransactionManager, E_SOFTEXCEPTIONS );
+
+ css::uno::Reference< css::frame::XDispatchProviderInterception > xInterceptionHelper( m_xDispatchHelper, css::uno::UNO_QUERY );
+ xInterceptionHelper->releaseDispatchProviderInterceptor( xInterceptor );
+}
+
+/*-************************************************************************************************************
+ @interface XFramesSupplier
+ @short return access to append or remove children on desktop
+ @descr We don't implement these interface directly. We use a helper class to do this.
+ If you wish to add or delete children to/from the container, call these method to get
+ a reference to the helper.
+
+ @attention Helper is threadsafe himself. So we don't need any lock here.
+
+ @seealso class OFrames
+ @return A reference to the helper.
+
+ @onerror A null reference is returned.
+ @threadsafe yes
+*//*-*************************************************************************************************************/
+css::uno::Reference< css::frame::XFrames > SAL_CALL Desktop::getFrames()
+{
+ /* UNSAFE AREA --------------------------------------------------------------------------------------------- */
+ // Register transaction and reject wrong calls.
+ TransactionGuard aTransaction( m_aTransactionManager, E_HARDEXCEPTIONS );
+
+ return m_xFramesHelper;
+}
+
+/*-************************************************************************************************************
+ @interface XFramesSupplier
+ @short set/get the current active child frame
+ @descr It must be a task. Direct children of desktop are tasks only! No frames are accepted.
+ We don't save this information directly in this class. We use our container-helper
+ to do that.
+
+ @attention Helper is threadsafe himself. So we don't need any lock here.
+
+ @seealso class OFrameContainer
+
+ @param "xFrame", new active frame (must be valid!)
+ @return A reference to our current active childtask, if anyone exist.
+
+ @onerror A null reference is returned.
+ @threadsafe yes
+*//*-*************************************************************************************************************/
+void SAL_CALL Desktop::setActiveFrame( const css::uno::Reference< css::frame::XFrame >& xFrame )
+{
+ /* UNSAFE AREA --------------------------------------------------------------------------------------------- */
+ // Register transaction and reject wrong calls.
+ TransactionGuard aTransaction( m_aTransactionManager, E_HARDEXCEPTIONS );
+
+ // Get old active frame first.
+ // If nothing will change - do nothing!
+ // Otherwise set new active frame ...
+ // and deactivate last frame.
+ // It's necessary for our FrameActionEvent listener on a frame!
+ css::uno::Reference< css::frame::XFrame > xLastActiveChild = m_aChildTaskContainer.getActive();
+ if( xLastActiveChild != xFrame )
+ {
+ m_aChildTaskContainer.setActive( xFrame );
+ if( xLastActiveChild.is() )
+ {
+ xLastActiveChild->deactivate();
+ }
+ }
+}
+
+css::uno::Reference< css::frame::XFrame > SAL_CALL Desktop::getActiveFrame()
+{
+ /* UNSAFE AREA --------------------------------------------------------------------------------------------- */
+ // Register transaction and reject wrong calls.
+ TransactionGuard aTransaction( m_aTransactionManager, E_HARDEXCEPTIONS );
+
+ return m_aChildTaskContainer.getActive();
+}
+
+/*
+ @interface XFrame
+ @short non implemented methods!
+ @descr Some method make no sense for our desktop! He has no window or parent or ...
+ So we should implement it empty and warn programmer, if he use it!
+*/
+void SAL_CALL Desktop::initialize( const css::uno::Reference< css::awt::XWindow >& )
+{
+}
+
+css::uno::Reference< css::awt::XWindow > SAL_CALL Desktop::getContainerWindow()
+{
+ return css::uno::Reference< css::awt::XWindow >();
+}
+
+void SAL_CALL Desktop::setCreator( const css::uno::Reference< css::frame::XFramesSupplier >& /*xCreator*/ )
+{
+}
+
+css::uno::Reference< css::frame::XFramesSupplier > SAL_CALL Desktop::getCreator()
+{
+ return css::uno::Reference< css::frame::XFramesSupplier >();
+}
+
+OUString SAL_CALL Desktop::getName()
+{
+ SolarMutexGuard g;
+ return m_sName;
+}
+
+void SAL_CALL Desktop::setName( const OUString& sName )
+{
+ SolarMutexGuard g;
+ m_sName = sName;
+}
+
+sal_Bool SAL_CALL Desktop::isTop()
+{
+ return true;
+}
+
+void SAL_CALL Desktop::activate()
+{
+ // Desktop is active always... but sometimes our frames try to activate
+ // the complete path from bottom to top... And our desktop is the topest frame :-(
+ // So - please don't show any assertions here. Do nothing!
+}
+
+void SAL_CALL Desktop::deactivate()
+{
+ // Desktop is active always... but sometimes our frames try to deactivate
+ // the complete path from bottom to top... And our desktop is the topest frame :-(
+ // So - please don't show any assertions here. Do nothing!
+}
+
+sal_Bool SAL_CALL Desktop::isActive()
+{
+ return true;
+}
+
+sal_Bool SAL_CALL Desktop::setComponent( const css::uno::Reference< css::awt::XWindow >& /*xComponentWindow*/ ,
+ const css::uno::Reference< css::frame::XController >& /*xController*/ )
+{
+ return false;
+}
+
+css::uno::Reference< css::awt::XWindow > SAL_CALL Desktop::getComponentWindow()
+{
+ return css::uno::Reference< css::awt::XWindow >();
+}
+
+css::uno::Reference< css::frame::XController > SAL_CALL Desktop::getController()
+{
+ return css::uno::Reference< css::frame::XController >();
+}
+
+void SAL_CALL Desktop::contextChanged()
+{
+}
+
+void SAL_CALL Desktop::addFrameActionListener( const css::uno::Reference< css::frame::XFrameActionListener >& )
+{
+}
+
+// css::frame::XFrame
+void SAL_CALL Desktop::removeFrameActionListener( const css::uno::Reference< css::frame::XFrameActionListener >& )
+{
+}
+
+/*-************************************************************************************************************
+ @interface XFrame
+ @short try to find a frame with special parameters
+ @descr This method searches for a frame with the specified name.
+ Frames may contain other frames (e.g. a frameset) and may
+ be contained in other frames. This hierarchy is searched by
+ this method.
+ First some special names are taken into account, i.e. "",
+ "_self", "_top", "_parent" etc. The FrameSearchFlags are ignored
+ when comparing these names with aTargetFrameName, further steps are
+ controlled by the FrameSearchFlags. If allowed, the name of the frame
+ itself is compared with the desired one, then ( again if allowed )
+ the method findFrame is called for all children of the frame.
+ If no Frame with the given name is found until the top frames container,
+ a new top Frame is created, if this is allowed by a special
+ FrameSearchFlag. The new Frame also gets the desired name.
+ We use a helper to get right search direction and react in a right manner.
+
+ @seealso class TargetFinder
+
+ @param "sTargetFrameName" , name of searched frame
+ @param "nSearchFlags" , flags to regulate search
+ @return A reference to an existing frame in hierarchy, if it exist.
+
+ @onerror A null reference is returned.
+ @threadsafe yes
+*//*-*************************************************************************************************************/
+css::uno::Reference< css::frame::XFrame > SAL_CALL Desktop::findFrame( const OUString& sTargetFrameName ,
+ sal_Int32 nSearchFlags )
+{
+ css::uno::Reference< css::frame::XFrame > xTarget;
+
+ // 0) Ignore wrong parameter!
+ // We don't support search for following special targets.
+ // If we reject these requests, we must not check for such names
+ // in following code again and again. If we do not, so wrong
+ // search results can occur!
+
+ if (
+ (sTargetFrameName==SPECIALTARGET_DEFAULT ) || // valid for dispatches - not for findFrame()!
+ (sTargetFrameName==SPECIALTARGET_PARENT ) || // we have no parent by definition
+ (sTargetFrameName==SPECIALTARGET_BEAMER ) // beamer frames are allowed as child of tasks only -
+ // and they exist more than ones. We have no idea which our sub tasks is the right one
+ )
+ {
+ return nullptr;
+ }
+
+ // I) check for special defined targets first which must be handled exclusive.
+ // force using of "if() else if() ..."
+
+ // I.I) "_blank"
+ // create a new task as child of this desktop instance
+ // Note: Used helper TaskCreator use us automatically ...
+
+ if ( sTargetFrameName==SPECIALTARGET_BLANK )
+ {
+ TaskCreator aCreator( m_xContext );
+ xTarget = aCreator.createTask(sTargetFrameName, utl::MediaDescriptor());
+ }
+
+ // I.II) "_top"
+ // We are top by definition
+
+ else if ( sTargetFrameName==SPECIALTARGET_TOP )
+ {
+ xTarget = this;
+ }
+
+ // I.III) "_self", ""
+ // This mean this "frame" in every case.
+
+ else if (
+ ( sTargetFrameName==SPECIALTARGET_SELF ) ||
+ ( sTargetFrameName.isEmpty() )
+ )
+ {
+ xTarget = this;
+ }
+
+ else
+ {
+
+ // II) otherwise use optional given search flags
+ // force using of combinations of such flags. means no "else" part of use if() statements.
+ // But we ust break further searches if target was already found.
+ // Order of using flags is fix: SELF - CHILDREN - SIBLINGS - PARENT
+ // TASK and CREATE are handled special.
+ // But note: Such flags are not valid for the desktop - especially SIBLINGS or PARENT.
+
+ // II.I) SELF
+ // Check for right name. If it's the searched one return ourself - otherwise
+ // ignore this flag.
+
+ if (
+ (nSearchFlags & css::frame::FrameSearchFlag::SELF) &&
+ (m_sName == sTargetFrameName)
+ )
+ {
+ xTarget = this;
+ }
+
+ // II.II) TASKS
+ // This is a special flag. Normally it regulate search inside tasks and forbid access to parent trees.
+ // But the desktop exists outside such task trees. They are our sub trees. So the desktop implement
+ // a special feature: We use it to start search on our direct children only. That means we suppress
+ // search on ALL child frames. May that can be useful to get access on opened document tasks
+ // only without filter out all non really required sub frames ...
+ // Used helper method on our container doesn't create any frame - it's a search only.
+
+ if (
+ ( ! xTarget.is() ) &&
+ (nSearchFlags & css::frame::FrameSearchFlag::TASKS)
+ )
+ {
+ xTarget = m_aChildTaskContainer.searchOnDirectChildrens(sTargetFrameName);
+ }
+
+ // II.III) CHILDREN
+ // Search on all children for the given target name.
+ // An empty name value can't occur here - because it must be already handled as "_self"
+ // before. Used helper function of container doesn't create any frame.
+ // It makes a deep search only.
+
+ if (
+ ( ! xTarget.is() ) &&
+ (nSearchFlags & css::frame::FrameSearchFlag::CHILDREN)
+ )
+ {
+ xTarget = m_aChildTaskContainer.searchOnAllChildrens(sTargetFrameName);
+ }
+
+ // II.IV) CREATE
+ // If we haven't found any valid target frame by using normal flags - but user allowed us to create
+ // a new one ... we should do that. Used TaskCreator use us automatically as parent!
+
+ if (
+ ( ! xTarget.is() ) &&
+ (nSearchFlags & css::frame::FrameSearchFlag::CREATE)
+ )
+ {
+ TaskCreator aCreator( m_xContext );
+ xTarget = aCreator.createTask(sTargetFrameName, utl::MediaDescriptor());
+ }
+ }
+
+ return xTarget;
+}
+
+void SAL_CALL Desktop::disposing()
+{
+ // Safe impossible cases
+ // It's a programming error if dispose is called before terminate!
+
+ // But if you just ignore the assertion (which happens in unit
+ // tests for instance in sc/qa/unit) nothing bad happens.
+ assert(m_bIsShutdown && "Desktop disposed before terminating it");
+
+ {
+ SolarMutexGuard aWriteLock;
+
+ {
+ TransactionGuard aTransaction(m_aTransactionManager, E_HARDEXCEPTIONS);
+ }
+
+ // Disable this instance for further work.
+ // This will wait for all current running transactions ...
+ // and reject all new incoming requests!
+ m_aTransactionManager.setWorkingMode(E_BEFORECLOSE);
+ }
+
+ // Following lines of code can be called outside a synchronized block ...
+ // Because our transaction manager will block all new requests to this object.
+ // So nobody can use us any longer.
+ // Exception: Only removing of listener will work ... and this code can't be dangerous.
+
+ // First we have to kill all listener connections.
+ // They might rely on our member and can hinder us on releasing them.
+ css::uno::Reference< css::uno::XInterface > xThis ( static_cast< ::cppu::OWeakObject* >(this), css::uno::UNO_QUERY );
+ css::lang::EventObject aEvent( xThis );
+ m_aListenerContainer.disposeAndClear( aEvent );
+
+ // Clear our child task container and forget all task references hardly.
+ // Normally all open document was already closed by our terminate() function before ...
+ // New opened frames will have a problem now .-)
+ m_aChildTaskContainer.clear();
+
+ // Dispose our helper too.
+ css::uno::Reference< css::lang::XEventListener > xFramesHelper( m_xFramesHelper, css::uno::UNO_QUERY );
+ if( xFramesHelper.is() )
+ xFramesHelper->disposing( aEvent );
+
+ // At least clean up other member references.
+ m_xDispatchHelper.clear();
+ m_xFramesHelper.clear();
+ m_xContext.clear();
+
+ m_xPipeTerminator.clear();
+ m_xQuickLauncher.clear();
+ m_xSWThreadManager.clear();
+
+ // we need a copy because the disposing might call the removeEventListener method
+ std::vector< css::uno::Reference<css::frame::XTerminateListener> > xComponentDllListeners;
+ xComponentDllListeners.swap(m_xComponentDllListeners);
+ for (auto& xListener: xComponentDllListeners)
+ {
+ xListener->disposing(aEvent);
+ }
+ xComponentDllListeners.clear();
+ m_xSfxTerminator.clear();
+ m_xCommandOptions.reset();
+
+ // From this point nothing will work further on this object ...
+ // excepting our dtor() .-)
+ m_aTransactionManager.setWorkingMode( E_CLOSE );
+}
+
+/*
+ @interface XComponent
+ @short add/remove listener for dispose events
+ @descr Add an event listener to this object, if you wish to get information
+ about our dying!
+ You must release this listener reference during your own disposing() method.
+
+ @attention Our container is threadsafe himself. So we don't need any lock here.
+ @param "xListener", reference to valid listener. We don't accept invalid values!
+ @threadsafe yes
+*/
+void SAL_CALL Desktop::addEventListener( const css::uno::Reference< css::lang::XEventListener >& xListener )
+{
+ /* UNSAFE AREA --------------------------------------------------------------------------------------------- */
+ // Safe impossible cases
+ // Method not defined for all incoming parameter.
+ SAL_WARN_IF( !xListener.is(), "fwk.desktop", "Desktop::addEventListener(): Invalid parameter detected!" );
+ // Register transaction and reject wrong calls.
+ TransactionGuard aTransaction( m_aTransactionManager, E_HARDEXCEPTIONS );
+
+ m_aListenerContainer.addInterface( cppu::UnoType<css::lang::XEventListener>::get(), xListener );
+}
+
+void SAL_CALL Desktop::removeEventListener( const css::uno::Reference< css::lang::XEventListener >& xListener )
+{
+ /* UNSAFE AREA --------------------------------------------------------------------------------------------- */
+ // Safe impossible cases
+ // Method not defined for all incoming parameter.
+ SAL_WARN_IF( !xListener.is(), "fwk.desktop", "Desktop::removeEventListener(): Invalid parameter detected!" );
+ // Register transaction and reject wrong calls.
+ TransactionGuard aTransaction( m_aTransactionManager, E_SOFTEXCEPTIONS );
+
+ m_aListenerContainer.removeInterface( cppu::UnoType<css::lang::XEventListener>::get(), xListener );
+}
+
+/*-************************************************************************************************************
+ @interface XDispatchResultListener
+ @short callback for dispatches
+ @descr To support our method "loadComponentFromURL()" we are listener on temp. created dispatcher.
+ They call us back in this method "statusChanged()". As source of given state event, they give us a
+ reference to the target frame, in which dispatch was loaded! So we can use it to return his component
+ to caller! If no target exist ... ??!!
+
+ @seealso method loadComponentFromURL()
+
+ @param "aEvent", state event which (hopefully) valid information
+ @threadsafe yes
+*//*-*************************************************************************************************************/
+void SAL_CALL Desktop::dispatchFinished( const css::frame::DispatchResultEvent& aEvent )
+{
+ /* UNSAFE AREA --------------------------------------------------------------------------------------------- */
+ // Register transaction and reject wrong calls.
+ TransactionGuard aTransaction( m_aTransactionManager, E_HARDEXCEPTIONS );
+
+ SolarMutexGuard g;
+ if( m_eLoadState != E_INTERACTION )
+ {
+ m_eLoadState = E_FAILED;
+ if( aEvent.State == css::frame::DispatchResultState::SUCCESS )
+ {
+ css::uno::Reference< css::frame::XFrame > xLastFrame; /// last target of "loadComponentFromURL()"!
+ if ( aEvent.Result >>= xLastFrame )
+ m_eLoadState = E_SUCCESSFUL;
+ }
+ }
+}
+
+/*-************************************************************************************************************
+ @interface XEventListener
+ @short not implemented!
+ @descr We are a status listener ... and so we must be an event listener too ... But we don't need it really!
+ We are a temp. listener only and our lifetime isn't smaller then of our temp. used dispatcher.
+
+ @seealso method loadComponentFromURL()
+*//*-*************************************************************************************************************/
+void SAL_CALL Desktop::disposing( const css::lang::EventObject& )
+{
+ SAL_WARN( "fwk.desktop", "Desktop::disposing(): Algorithm error! Normally desktop is temp. listener ... not all the time. So this method shouldn't be called." );
+}
+
+/*-************************************************************************************************************
+ @interface XInteractionHandler
+ @short callback for loadComponentFromURL for detected exceptions during load process
+ @descr In this case we must cancel loading and throw these detected exception again as result
+ of our own called method.
+
+ @attention a)
+ Normal loop in loadComponentFromURL() breaks on set member m_eLoadState during callback statusChanged().
+ But these interaction feature implements second way to do so! So we must look on different callbacks
+ for same operation ... and live with it.
+ b)
+ Search for given continuations too. If any XInteractionAbort exist ... use it to abort further operations
+ for currently running operation!
+
+ @seealso method loadComponentFromURL()
+ @seealso member m_eLoadState
+
+ @param "xRequest", request for interaction - normal a wrapped target exception from bottom services
+ @threadsafe yes
+*//*-*************************************************************************************************************/
+void SAL_CALL Desktop::handle( const css::uno::Reference< css::task::XInteractionRequest >& xRequest )
+{
+ /* UNSAFE AREA --------------------------------------------------------------------------------------------- */
+ // Register transaction and reject wrong calls.
+ TransactionGuard aTransaction( m_aTransactionManager, E_HARDEXCEPTIONS );
+
+ // Don't check incoming request!
+ // If somewhere starts interaction without right parameter - he made something wrong.
+ // loadComponentFromURL() waits for these event - otherwise it yield for ever!
+
+ // get packed request and work on it first
+ // Attention: Don't set it on internal member BEFORE interaction is finished - because
+ // "loadComponentFromURL()" yield tills this member is changed. If we do it before
+ // interaction finish we can't guarantee right functionality. May be we cancel load process to earlier...
+ css::uno::Any aRequest = xRequest->getRequest();
+
+ // extract continuations from request
+ css::uno::Sequence< css::uno::Reference< css::task::XInteractionContinuation > > lContinuations = xRequest->getContinuations();
+ css::uno::Reference< css::task::XInteractionAbort > xAbort;
+ css::uno::Reference< css::task::XInteractionApprove > xApprove;
+ css::uno::Reference< css::document::XInteractionFilterSelect > xFilterSelect;
+ bool bAbort = false;
+
+ sal_Int32 nCount=lContinuations.getLength();
+ for( sal_Int32 nStep=0; nStep<nCount; ++nStep )
+ {
+ if( ! xAbort.is() )
+ xAbort.set( lContinuations[nStep], css::uno::UNO_QUERY );
+
+ if( ! xApprove.is() )
+ xApprove.set( lContinuations[nStep], css::uno::UNO_QUERY );
+
+ if( ! xFilterSelect.is() )
+ xFilterSelect.set( lContinuations[nStep], css::uno::UNO_QUERY );
+ }
+
+ // differ between abortable interactions (error, unknown filter...)
+ // and other ones (ambiguous but not unknown filter...)
+ css::task::ErrorCodeRequest aErrorCodeRequest;
+ if( aRequest >>= aErrorCodeRequest )
+ {
+ bool bWarning = ErrCode(aErrorCodeRequest.ErrCode).IsWarning();
+ if (xApprove.is() && bWarning)
+ xApprove->select();
+ else
+ if (xAbort.is())
+ {
+ xAbort->select();
+ bAbort = true;
+ }
+ }
+ else if( xAbort.is() )
+ {
+ xAbort->select();
+ bAbort = true;
+ }
+
+ // Ok now it's time to break yield loop of loadComponentFromURL().
+ // But only for really aborted requests!
+ // For example warnings will be approved and we wait for any success story ...
+ if (bAbort)
+ {
+ SolarMutexGuard g;
+ m_eLoadState = E_INTERACTION;
+ }
+}
+
+::sal_Int32 SAL_CALL Desktop::leaseNumber( const css::uno::Reference< css::uno::XInterface >& xComponent )
+{
+ TransactionGuard aTransaction( m_aTransactionManager, E_HARDEXCEPTIONS );
+ return m_xTitleNumberGenerator->leaseNumber (xComponent);
+}
+
+void SAL_CALL Desktop::releaseNumber( ::sal_Int32 nNumber )
+{
+ TransactionGuard aTransaction( m_aTransactionManager, E_HARDEXCEPTIONS );
+ m_xTitleNumberGenerator->releaseNumber (nNumber);
+}
+
+void SAL_CALL Desktop::releaseNumberForComponent( const css::uno::Reference< css::uno::XInterface >& xComponent )
+{
+ TransactionGuard aTransaction( m_aTransactionManager, E_HARDEXCEPTIONS );
+ m_xTitleNumberGenerator->releaseNumberForComponent (xComponent);
+}
+
+OUString SAL_CALL Desktop::getUntitledPrefix()
+{
+ TransactionGuard aTransaction( m_aTransactionManager, E_HARDEXCEPTIONS );
+ return m_xTitleNumberGenerator->getUntitledPrefix ();
+}
+
+/*-************************************************************************************************************
+ @short try to convert a property value
+ @descr This method is called from helperclass "OPropertySetHelper".
+ Don't use this directly!
+ You must try to convert the value of given PropHandle and
+ return results of this operation. This will be used to ask vetoable
+ listener. If no listener has a veto, we will change value really!
+ ( in method setFastPropertyValue_NoBroadcast(...) )
+
+ @attention Methods of OPropertySethelper are safed by using our shared osl mutex! (see ctor!)
+ So we must use different locks to make our implementation threadsafe.
+
+ @seealso class OPropertySetHelper
+ @seealso method setFastPropertyValue_NoBroadcast()
+
+ @param "aConvertedValue" new converted value of property
+ @param "aOldValue" old value of property
+ @param "nHandle" handle of property
+ @param "aValue" new value of property
+ @return sal_True if value will be changed, sal_FALSE otherway
+
+ @onerror IllegalArgumentException, if you call this with an invalid argument
+ @threadsafe yes
+*//*-*************************************************************************************************************/
+sal_Bool SAL_CALL Desktop::convertFastPropertyValue( css::uno::Any& aConvertedValue ,
+ css::uno::Any& aOldValue ,
+ sal_Int32 nHandle ,
+ const css::uno::Any& aValue )
+{
+ /* UNSAFE AREA --------------------------------------------------------------------------------------------- */
+ // Register transaction and reject wrong calls.
+ TransactionGuard aTransaction( m_aTransactionManager, E_HARDEXCEPTIONS );
+
+ // Initialize state with sal_False !!!
+ // (Handle can be invalid)
+ bool bReturn = false;
+
+ switch( nHandle )
+ {
+ case PropHandle::SuspendQuickstartVeto:
+ bReturn = PropHelper::willPropertyBeChanged(
+ css::uno::Any(m_bSuspendQuickstartVeto),
+ aValue,
+ aOldValue,
+ aConvertedValue);
+ break;
+ case PropHandle::DispatchRecorderSupplier :
+ bReturn = PropHelper::willPropertyBeChanged(
+ css::uno::Any(m_xDispatchRecorderSupplier),
+ aValue,
+ aOldValue,
+ aConvertedValue);
+ break;
+ case PropHandle::Title :
+ bReturn = PropHelper::willPropertyBeChanged(
+ css::uno::Any(m_sTitle),
+ aValue,
+ aOldValue,
+ aConvertedValue);
+ break;
+ }
+
+ // Return state of operation.
+ return bReturn;
+}
+
+/*-************************************************************************************************************
+ @short set value of a transient property
+ @descr This method is calling from helperclass "OPropertySetHelper".
+ Don't use this directly!
+ Handle and value are valid everyway! You must set the new value only.
+ After this, baseclass send messages to all listener automatically.
+
+ @seealso class OPropertySetHelper
+
+ @param "nHandle" handle of property to change
+ @param "aValue" new value of property
+ @onerror An exception is thrown.
+ @threadsafe yes
+*//*-*************************************************************************************************************/
+void SAL_CALL Desktop::setFastPropertyValue_NoBroadcast( sal_Int32 nHandle ,
+ const css::uno::Any& aValue )
+{
+ /* UNSAFE AREA --------------------------------------------------------------------------------------------- */
+ // Register transaction and reject wrong calls.
+ TransactionGuard aTransaction( m_aTransactionManager, E_HARDEXCEPTIONS );
+
+ switch( nHandle )
+ {
+ case PropHandle::SuspendQuickstartVeto: aValue >>= m_bSuspendQuickstartVeto;
+ break;
+ case PropHandle::DispatchRecorderSupplier: aValue >>= m_xDispatchRecorderSupplier;
+ break;
+ case PropHandle::Title: aValue >>= m_sTitle;
+ break;
+ }
+}
+
+/*-************************************************************************************************************
+ @short get value of a transient property
+ @descr This method is calling from helperclass "OPropertySetHelper".
+ Don't use this directly!
+
+ @attention We don't need any mutex or lock here ... We use threadsafe container or methods here only!
+
+ @seealso class OPropertySetHelper
+
+ @param "nHandle" handle of property to change
+ @param "aValue" current value of property
+ @threadsafe yes
+*//*-*************************************************************************************************************/
+void SAL_CALL Desktop::getFastPropertyValue( css::uno::Any& aValue ,
+ sal_Int32 nHandle ) const
+{
+ /* UNSAFE AREA --------------------------------------------------------------------------------------------- */
+ // Register transaction and reject wrong calls.
+ TransactionGuard aTransaction( m_aTransactionManager, E_HARDEXCEPTIONS );
+
+ switch( nHandle )
+ {
+ case PropHandle::ActiveFrame : aValue <<= m_aChildTaskContainer.getActive();
+ break;
+ case PropHandle::IsPlugged : aValue <<= false;
+ break;
+ case PropHandle::SuspendQuickstartVeto: aValue <<= m_bSuspendQuickstartVeto;
+ break;
+ case PropHandle::DispatchRecorderSupplier: aValue <<= m_xDispatchRecorderSupplier;
+ break;
+ case PropHandle::Title: aValue <<= m_sTitle;
+ break;
+ }
+}
+
+::cppu::IPropertyArrayHelper& SAL_CALL Desktop::getInfoHelper()
+{
+ static cppu::OPropertyArrayHelper HELPER =
+ [] () {
+ return cppu::OPropertyArrayHelper {
+ {{"ActiveFrame", PropHandle::ActiveFrame,
+ cppu::UnoType<css::lang::XComponent>::get(),
+ (css::beans::PropertyAttribute::TRANSIENT
+ | css::beans::PropertyAttribute::READONLY)},
+ {"DispatchRecorderSupplier",
+ PropHandle::DispatchRecorderSupplier,
+ cppu::UnoType<css::frame::XDispatchRecorderSupplier>::get(),
+ css::beans::PropertyAttribute::TRANSIENT},
+ {"IsPlugged",
+ PropHandle::IsPlugged, cppu::UnoType<bool>::get(),
+ (css::beans::PropertyAttribute::TRANSIENT
+ | css::beans::PropertyAttribute::READONLY)},
+ {"SuspendQuickstartVeto", PropHandle::SuspendQuickstartVeto,
+ cppu::UnoType<bool>::get(),
+ css::beans::PropertyAttribute::TRANSIENT},
+ {"Title", PropHandle::Title, cppu::UnoType<OUString>::get(),
+ css::beans::PropertyAttribute::TRANSIENT}},
+ true};
+ }();
+ return HELPER;
+}
+
+/*-************************************************************************************************************
+ @short return propertysetinfo
+ @descr You can call this method to get information about transient properties
+ of this object.
+
+ @attention You must use global lock (method use static variable) ... and it must be the shareable osl mutex of it.
+ Because; our baseclass use this mutex to make his code threadsafe. We use our lock!
+ So we could have two different mutex/lock mechanism at the same object.
+
+ @seealso class OPropertySetHelper
+ @seealso interface XPropertySet
+ @seealso interface XMultiPropertySet
+ @return reference to object with information [XPropertySetInfo]
+ @threadsafe yes
+*//*-*************************************************************************************************************/
+css::uno::Reference< css::beans::XPropertySetInfo > SAL_CALL Desktop::getPropertySetInfo()
+{
+ /* UNSAFE AREA --------------------------------------------------------------------------------------------- */
+ // Register transaction and reject wrong calls.
+ TransactionGuard aTransaction( m_aTransactionManager, E_HARDEXCEPTIONS );
+
+ // Create structure of propertysetinfo for baseclass "OPropertySetHelper".
+ // (Use method "getInfoHelper()".)
+ static css::uno::Reference< css::beans::XPropertySetInfo > xInfo(
+ cppu::OPropertySetHelper::createPropertySetInfo( getInfoHelper() ) );
+
+ return xInfo;
+}
+
+/*-************************************************************************************************************
+ @short return current component of current frame
+ @descr The desktop himself has no component. But every frame in subtree.
+ If somewhere call getCurrentComponent() at this class, we try to find the right frame and
+ then we try to become his component. It can be a VCL-component, the model or the controller
+ of founded frame.
+
+ @attention We don't work on internal member ... so we don't need any lock here.
+
+ @seealso method getCurrentComponent();
+
+ @param "xFrame", reference to valid frame in hierarchy. Method is not defined for invalid values.
+ But we don't check these. It's an IMPL-method and caller must use it right!
+ @return A reference to found component.
+
+ @onerror A null reference is returned.
+ @threadsafe yes
+*//*-*************************************************************************************************************/
+css::uno::Reference< css::lang::XComponent > Desktop::impl_getFrameComponent( const css::uno::Reference< css::frame::XFrame >& xFrame ) const
+{
+ /* UNSAFE AREA --------------------------------------------------------------------------------------------- */
+ // Register transaction and reject wrong calls.
+ TransactionGuard aTransaction( m_aTransactionManager, E_HARDEXCEPTIONS );
+
+ // Set default return value, if method failed.
+ css::uno::Reference< css::lang::XComponent > xComponent;
+ // Does no controller exists?
+ css::uno::Reference< css::frame::XController > xController = xFrame->getController();
+ if( !xController.is() )
+ {
+ // Controller not exist - use the VCL-component.
+ xComponent = xFrame->getComponentWindow();
+ }
+ else
+ {
+ // Does no model exists?
+ css::uno::Reference< css::frame::XModel > xModel = xController->getModel();
+ if( xModel.is() )
+ {
+ // Model exist - use the model as component.
+ xComponent = xModel;
+ }
+ else
+ {
+ // Model not exist - use the controller as component.
+ xComponent = xController;
+ }
+ }
+
+ return xComponent;
+}
+
+bool Desktop::impl_sendQueryTerminationEvent(Desktop::TTerminateListenerList& lCalledListener)
+{
+ TransactionGuard aTransaction( m_aTransactionManager, E_HARDEXCEPTIONS );
+
+ comphelper::OInterfaceContainerHelper2* pContainer = m_aListenerContainer.getContainer( cppu::UnoType<css::frame::XTerminateListener>::get());
+ if ( ! pContainer )
+ return true;
+
+ css::lang::EventObject aEvent( static_cast< ::cppu::OWeakObject* >(this) );
+
+ comphelper::OInterfaceIteratorHelper2 aIterator( *pContainer );
+ while ( aIterator.hasMoreElements() )
+ {
+ try
+ {
+ css::uno::Reference< css::frame::XTerminateListener > xListener(aIterator.next(), css::uno::UNO_QUERY);
+ if ( ! xListener.is() )
+ continue;
+ xListener->queryTermination( aEvent );
+ lCalledListener.push_back(xListener);
+ }
+ catch( const css::frame::TerminationVetoException& )
+ {
+ // first veto will stop the query loop.
+ return false;
+ }
+ catch( const css::uno::Exception& )
+ {
+ // clean up container.
+ // E.g. dead remote listener objects can make trouble otherwise.
+ // Iterator implementation allows removing objects during it's used !
+ aIterator.remove();
+ }
+ }
+
+ return true;
+}
+
+void Desktop::impl_sendCancelTerminationEvent(const Desktop::TTerminateListenerList& lCalledListener)
+{
+ TransactionGuard aTransaction( m_aTransactionManager, E_HARDEXCEPTIONS );
+
+ css::lang::EventObject aEvent( static_cast< ::cppu::OWeakObject* >(this) );
+ for (const css::uno::Reference<css::frame::XTerminateListener>& xListener : lCalledListener)
+ {
+ try
+ {
+ // Note: cancelTermination() is a new and optional interface method !
+ css::uno::Reference< css::frame::XTerminateListener2 > xListenerGeneration2(xListener, css::uno::UNO_QUERY);
+ if ( ! xListenerGeneration2.is() )
+ continue;
+ xListenerGeneration2->cancelTermination( aEvent );
+ }
+ catch( const css::uno::Exception& )
+ {}
+ }
+}
+
+void Desktop::impl_sendTerminateToClipboard()
+{
+ TransactionGuard aTransaction( m_aTransactionManager, E_HARDEXCEPTIONS );
+
+ comphelper::OInterfaceContainerHelper2* pContainer = m_aListenerContainer.getContainer( cppu::UnoType<css::frame::XTerminateListener>::get());
+ if ( ! pContainer )
+ return;
+
+ comphelper::OInterfaceIteratorHelper2 aIterator( *pContainer );
+ while ( aIterator.hasMoreElements() )
+ {
+ try
+ {
+ css::frame::XTerminateListener* pTerminateListener =
+ static_cast< css::frame::XTerminateListener* >(aIterator.next());
+ css::uno::Reference< css::lang::XServiceInfo > xInfo( pTerminateListener, css::uno::UNO_QUERY );
+ if ( !xInfo.is() )
+ continue;
+
+ if ( xInfo->getImplementationName() != "com.sun.star.comp.svt.TransferableHelperTerminateListener" )
+ continue;
+
+ css::lang::EventObject aEvent( static_cast< ::cppu::OWeakObject* >(this) );
+ pTerminateListener->notifyTermination( aEvent );
+
+ // don't notify twice
+ aIterator.remove();
+ }
+ catch( const css::uno::Exception& )
+ {
+ // clean up container.
+ // E.g. dead remote listener objects can make trouble otherwise.
+ // Iterator implementation allows removing objects during it's used !
+ aIterator.remove();
+ }
+ }
+}
+
+void Desktop::impl_sendNotifyTerminationEvent()
+{
+ TransactionGuard aTransaction( m_aTransactionManager, E_HARDEXCEPTIONS );
+
+ comphelper::OInterfaceContainerHelper2* pContainer = m_aListenerContainer.getContainer( cppu::UnoType<css::frame::XTerminateListener>::get());
+ if ( ! pContainer )
+ return;
+
+ css::lang::EventObject aEvent( static_cast< ::cppu::OWeakObject* >(this) );
+
+ comphelper::OInterfaceIteratorHelper2 aIterator( *pContainer );
+ while ( aIterator.hasMoreElements() )
+ {
+ try
+ {
+ static_cast< css::frame::XTerminateListener* >(aIterator.next())->notifyTermination( aEvent );
+ }
+ catch( const css::uno::Exception& )
+ {
+ // clean up container.
+ // E.g. dead remote listener objects can make trouble otherwise.
+ // Iterator implementation allows removing objects during it's used !
+ aIterator.remove();
+ }
+ }
+}
+
+bool Desktop::impl_closeFrames(bool bAllowUI)
+{
+ SolarMutexClearableGuard aReadLock;
+ css::uno::Sequence< css::uno::Reference< css::frame::XFrame > > lFrames = m_aChildTaskContainer.getAllElements();
+ aReadLock.clear();
+
+ ::sal_Int32 c = lFrames.getLength();
+ ::sal_Int32 i = 0;
+ ::sal_Int32 nNonClosedFrames = 0;
+
+ for( i=0; i<c; ++i )
+ {
+ try
+ {
+ css::uno::Reference< css::frame::XFrame > xFrame = lFrames[i];
+
+ // XController.suspend() will show a UI ...
+ // Use it in case it was allowed from outside only.
+ bool bSuspended = false;
+ css::uno::Reference< css::frame::XController > xController = xFrame->getController();
+ if ( bAllowUI && xController.is() )
+ {
+ bSuspended = xController->suspend( true );
+ if ( ! bSuspended )
+ {
+ ++nNonClosedFrames;
+ if(m_bSession)
+ break;
+ else
+ continue;
+ }
+ }
+
+ // Try to close frame (in case no UI was allowed without calling XController->suspend() before!)
+ // But don't deliver ownership to any other one!
+ // This method can be called again.
+ css::uno::Reference< css::util::XCloseable > xClose( xFrame, css::uno::UNO_QUERY );
+ if ( xClose.is() )
+ {
+ try
+ {
+ xClose->close(false);
+ }
+ catch(const css::util::CloseVetoException&)
+ {
+ // Any internal process of this frame disagree with our request.
+ // Safe this state but don't break these loop. Other frames has to be closed!
+ ++nNonClosedFrames;
+
+ // Reactivate controller.
+ // It can happen that XController.suspend() returned true... but a registered close listener
+ // threw these veto exception. Then the controller has to be reactivated. Otherwise
+ // these document doesn't work any more.
+ if ( bSuspended && xController.is())
+ xController->suspend(false);
+ }
+
+ // If interface XClosable interface exists and was used...
+ // it's not allowed to use XComponent->dispose() also!
+ continue;
+ }
+
+ // XClosable not supported ?
+ // Then we have to dispose these frame hardly.
+ if ( xFrame.is() )
+ xFrame->dispose();
+
+ // Don't remove these frame from our child container!
+ // A frame do it by itself inside close()/dispose() method.
+ }
+ catch(const css::lang::DisposedException&)
+ {
+ // Dispose frames are closed frames.
+ // So we can count it here .-)
+ }
+ }
+
+ // reset the session
+ m_bSession = false;
+
+ return (nNonClosedFrames < 1);
+}
+
+} // namespace framework
+
+namespace {
+
+rtl::Reference<framework::Desktop> createDesktop(
+ css::uno::Reference<css::uno::XComponentContext> const & context)
+{
+ SolarMutexGuard g; // tdf#114025 init with SolarMutex to avoid deadlock
+ rtl::Reference<framework::Desktop> desktop(new framework::Desktop(context));
+ desktop->constructorInit();
+ return desktop;
+}
+
+}
+
+const rtl::Reference<framework::Desktop> & framework::getDesktop(
+ css::uno::Reference<css::uno::XComponentContext> const & context)
+{
+ static auto const instance = createDesktop(context);
+ return instance;
+}
+
+extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface *
+com_sun_star_comp_framework_Desktop_get_implementation(
+ css::uno::XComponentContext *context,
+ css::uno::Sequence<css::uno::Any> const &)
+{
+ return cppu::acquire(framework::getDesktop(context).get());
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/framework/source/services/dispatchhelper.cxx b/framework/source/services/dispatchhelper.cxx
new file mode 100644
index 000000000..8f3d77d32
--- /dev/null
+++ b/framework/source/services/dispatchhelper.cxx
@@ -0,0 +1,218 @@
+/* -*- 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 <framework/dispatchhelper.hxx>
+
+#include <com/sun/star/frame/XNotifyingDispatch.hpp>
+#include <com/sun/star/util/URLTransformer.hpp>
+#include <com/sun/star/util/XURLTransformer.hpp>
+
+#include <comphelper/profilezone.hxx>
+#include <unotools/mediadescriptor.hxx>
+#include <utility>
+#include <vcl/threadex.hxx>
+#include <cppuhelper/supportsservice.hxx>
+
+namespace framework
+{
+// XInterface, XTypeProvider, XServiceInfo
+
+OUString SAL_CALL DispatchHelper::getImplementationName()
+{
+ return "com.sun.star.comp.framework.services.DispatchHelper";
+}
+
+sal_Bool SAL_CALL DispatchHelper::supportsService(const OUString& sServiceName)
+{
+ return cppu::supportsService(this, sServiceName);
+}
+
+css::uno::Sequence<OUString> SAL_CALL DispatchHelper::getSupportedServiceNames()
+{
+ return { "com.sun.star.frame.DispatchHelper" };
+}
+
+/** ctor.
+
+ @param xSMGR the global uno service manager, which can be used to create own needed services.
+*/
+DispatchHelper::DispatchHelper(css::uno::Reference<css::uno::XComponentContext> xContext)
+ : m_xContext(std::move(xContext))
+ , m_aBlockFlag(false)
+{
+}
+
+/** dtor.
+*/
+DispatchHelper::~DispatchHelper() {}
+
+/** capsulate all steps of a dispatch request and provide so an easy way for dispatches.
+
+ @param xDispatchProvider
+ identifies the object, which provides may be valid dispatch objects for this execute.
+
+ @param sURL
+ describes the requested feature.
+
+ @param sTargetFrameName
+ points to the frame, which must be used (or may be created) for this dispatch.
+
+ @param nSearchFlags
+ in case the <var>sTargetFrameName</var> isn't unique, these flags regulate further searches.
+
+ @param lArguments
+ optional arguments for this request.
+
+ @return An Any which capsulate a possible result of the internal wrapped dispatch.
+ */
+css::uno::Any SAL_CALL DispatchHelper::executeDispatch(
+ const css::uno::Reference<css::frame::XDispatchProvider>& xDispatchProvider,
+ const OUString& sURL, const OUString& sTargetFrameName, sal_Int32 nSearchFlags,
+ const css::uno::Sequence<css::beans::PropertyValue>& lArguments)
+{
+ // check for valid parameters
+ if ((!xDispatchProvider.is()) || (!m_xContext.is()) || (sURL.isEmpty()))
+ {
+ return css::uno::Any();
+ }
+
+ // parse given URL
+ css::uno::Reference<css::util::XURLTransformer> xParser;
+ /* SAFE { */
+ {
+ std::scoped_lock aReadLock(m_mutex);
+ xParser = css::util::URLTransformer::create(m_xContext);
+ }
+ /* } SAFE */
+
+ css::util::URL aURL;
+ aURL.Complete = sURL;
+ xParser->parseStrict(aURL);
+
+ // search dispatcher
+ css::uno::Reference<css::frame::XDispatch> xDispatch
+ = xDispatchProvider->queryDispatch(aURL, sTargetFrameName, nSearchFlags);
+
+ utl::MediaDescriptor aDescriptor(lArguments);
+ bool bOnMainThread = aDescriptor.getUnpackedValueOrDefault("OnMainThread", false);
+
+ if (bOnMainThread)
+ return vcl::solarthread::syncExecute([this, &xDispatch, &aURL, &lArguments]() {
+ return executeDispatch(xDispatch, aURL, true, lArguments);
+ });
+ else
+ return executeDispatch(xDispatch, aURL, true, lArguments);
+}
+
+const css::uno::Any&
+DispatchHelper::executeDispatch(const css::uno::Reference<css::frame::XDispatch>& xDispatch,
+ const css::util::URL& aURL, bool SyncronFlag,
+ const css::uno::Sequence<css::beans::PropertyValue>& lArguments)
+{
+ comphelper::ProfileZone aZone("executeDispatch");
+ css::uno::Reference<css::uno::XInterface> xTHIS(static_cast<::cppu::OWeakObject*>(this),
+ css::uno::UNO_QUERY);
+ m_aResult.clear();
+
+ // check for valid parameters
+ if (xDispatch.is())
+ {
+ css::uno::Reference<css::frame::XNotifyingDispatch> xNotifyDispatch(xDispatch,
+ css::uno::UNO_QUERY);
+
+ // make sure that synchronous execution is used (if possible)
+ css::uno::Sequence<css::beans::PropertyValue> aArguments(lArguments);
+ sal_Int32 nLength = lArguments.getLength();
+ aArguments.realloc(nLength + 1);
+ auto pArguments = aArguments.getArray();
+ pArguments[nLength].Name = "SynchronMode";
+ pArguments[nLength].Value <<= SyncronFlag;
+
+ if (xNotifyDispatch.is())
+ {
+ // dispatch it with guaranteed notification
+ // Here we can hope for a result ... instead of the normal dispatch.
+ css::uno::Reference<css::frame::XDispatchResultListener> xListener(xTHIS,
+ css::uno::UNO_QUERY);
+ /* SAFE { */
+ {
+ std::scoped_lock aWriteLock(m_mutex);
+ m_xBroadcaster = xNotifyDispatch;
+ m_aBlockFlag = false;
+ }
+ /* } SAFE */
+
+ // dispatch it and wait for a notification
+ // TODO/MBA: waiting in main thread?!
+ xNotifyDispatch->dispatchWithNotification(aURL, aArguments, xListener);
+
+ std::unique_lock aWriteLock(m_mutex);
+ m_aBlock.wait(aWriteLock, [this] { return m_aBlockFlag; }); // wait for result
+ }
+ else
+ {
+ // dispatch it without any chance to get a result
+ xDispatch->dispatch(aURL, aArguments);
+ }
+ }
+
+ return m_aResult;
+}
+
+/** callback for started dispatch with guaranteed notifications.
+
+ We must save the result, so the method executeDispatch() can return it.
+ Further we must release the broadcaster (otherwise it can't die)
+ and unblock the waiting executeDispatch() request.
+
+ @param aResult
+ describes the result of the dispatch operation
+ */
+void SAL_CALL DispatchHelper::dispatchFinished(const css::frame::DispatchResultEvent& aResult)
+{
+ std::scoped_lock g(m_mutex);
+ m_aResult <<= aResult;
+ m_aBlockFlag = true;
+ m_aBlock.notify_one();
+ m_xBroadcaster.clear();
+}
+
+/** we have to release our broadcaster reference.
+
+ @param aEvent
+ describe the source of this event and MUST be our save broadcaster!
+ */
+void SAL_CALL DispatchHelper::disposing(const css::lang::EventObject&)
+{
+ std::scoped_lock g(m_mutex);
+ m_aResult.clear();
+ m_aBlockFlag = true;
+ m_aBlock.notify_one();
+ m_xBroadcaster.clear();
+}
+}
+
+extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface*
+framework_DispatchHelper_get_implementation(css::uno::XComponentContext* context,
+ css::uno::Sequence<css::uno::Any> const&)
+{
+ return cppu::acquire(new framework::DispatchHelper(context));
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/framework/source/services/frame.cxx b/framework/source/services/frame.cxx
new file mode 100644
index 000000000..5fc6309aa
--- /dev/null
+++ b/framework/source/services/frame.cxx
@@ -0,0 +1,3340 @@
+/* -*- 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 <dispatch/dispatchprovider.hxx>
+#include <dispatch/interceptionhelper.hxx>
+#include <dispatch/windowcommanddispatch.hxx>
+#include <loadenv/loadenv.hxx>
+#include <helper/oframes.hxx>
+#include <framework/framecontainer.hxx>
+#include <framework/titlehelper.hxx>
+#include <svtools/openfiledroptargetlistener.hxx>
+#include <classes/taskcreator.hxx>
+#include <loadenv/targethelper.hxx>
+#include <framework/framelistanalyzer.hxx>
+#include <helper/dockingareadefaultacceptor.hxx>
+#include <dispatch/dispatchinformationprovider.hxx>
+
+#include <pattern/window.hxx>
+#include <properties.h>
+#include <targets.h>
+
+#include <com/sun/star/awt/Toolkit.hpp>
+#include <com/sun/star/awt/XDevice.hpp>
+#include <com/sun/star/awt/XTopWindow.hpp>
+#include <com/sun/star/awt/PosSize.hpp>
+#include <com/sun/star/beans/PropertyAttribute.hpp>
+#include <com/sun/star/beans/PropertyExistException.hpp>
+#include <com/sun/star/beans/PropertyValue.hpp>
+#include <com/sun/star/beans/XPropertySet.hpp>
+#include <com/sun/star/container/XIndexAccess.hpp>
+#include <com/sun/star/datatransfer/dnd/XDropTarget.hpp>
+#include <com/sun/star/frame/XFrame2.hpp>
+#include <com/sun/star/frame/XModel.hpp>
+#include <com/sun/star/frame/XTitleChangeBroadcaster.hpp>
+#include <com/sun/star/frame/LayoutManager.hpp>
+#include <com/sun/star/frame/XDesktop.hpp>
+#include <com/sun/star/frame/FrameSearchFlag.hpp>
+#include <com/sun/star/lang/DisposedException.hpp>
+#include <com/sun/star/task/StatusIndicatorFactory.hpp>
+#include <com/sun/star/task/theJobExecutor.hpp>
+#include <com/sun/star/task/XJobExecutor.hpp>
+#include <com/sun/star/util/CloseVetoException.hpp>
+#include <com/sun/star/util/URLTransformer.hpp>
+#include <com/sun/star/util/XURLTransformer.hpp>
+#include <com/sun/star/util/XCloseable.hpp>
+#include <com/sun/star/lang/XServiceInfo.hpp>
+
+#include <cppuhelper/basemutex.hxx>
+#include <cppuhelper/compbase.hxx>
+#include <comphelper/multiinterfacecontainer3.hxx>
+#include <comphelper/multicontainer2.hxx>
+#include <cppuhelper/supportsservice.hxx>
+#include <cppuhelper/weak.hxx>
+#include <sal/log.hxx>
+#include <vcl/window.hxx>
+#include <vcl/wrkwin.hxx>
+#include <vcl/svapp.hxx>
+
+#include <toolkit/helper/vclunohelper.hxx>
+#include <unotools/moduleoptions.hxx>
+#include <tools/diagnose_ex.h>
+#include <unotools/cmdoptions.hxx>
+#include <vcl/threadex.hxx>
+#include <mutex>
+
+using namespace framework;
+
+namespace {
+
+// This enum can be used to set different active states of frames
+enum EActiveState
+{
+ E_INACTIVE, // I am not a member of active path in tree and i don't have the focus.
+ E_ACTIVE, // I am in the middle of an active path in tree and i don't have the focus.
+ E_FOCUS // I have the focus now. I must a member of an active path!
+};
+
+/*-************************************************************************************************************
+ @short implements a normal frame of hierarchy
+ @descr An instance of these class can be a normal node in frame tree. A frame support influencing of his
+ subtree, find of subframes, activate- and deactivate-mechanism as well as
+ set/get of a frame window, component or controller.
+*//*-*************************************************************************************************************/
+class XFrameImpl:
+ private cppu::BaseMutex,
+ public cppu::PartialWeakComponentImplHelper<
+ css::lang::XServiceInfo, css::frame::XFrame2, css::awt::XWindowListener,
+ css::awt::XTopWindowListener, css::awt::XFocusListener,
+ css::document::XActionLockable, css::util::XCloseable,
+ css::frame::XComponentLoader, css::frame::XTitle,
+ css::frame::XTitleChangeBroadcaster, css::beans::XPropertySet,
+ css::beans::XPropertySetInfo>
+{
+public:
+
+ explicit XFrameImpl(css::uno::Reference< css::uno::XComponentContext > xContext);
+
+ /// Initialization function after having acquire()'d.
+ void initListeners();
+
+ virtual OUString SAL_CALL getImplementationName() override
+ {
+ return "com.sun.star.comp.framework.Frame";
+ }
+
+ virtual sal_Bool SAL_CALL supportsService(OUString const & ServiceName) override
+ {
+ return cppu::supportsService(this, ServiceName);
+ }
+
+ virtual css::uno::Sequence<OUString> SAL_CALL getSupportedServiceNames() override
+ {
+ return {"com.sun.star.frame.Frame"};
+ }
+
+ // XComponentLoader
+
+ virtual css::uno::Reference< css::lang::XComponent > SAL_CALL loadComponentFromURL(
+ const OUString& sURL,
+ const OUString& sTargetFrameName,
+ sal_Int32 nSearchFlags,
+ const css::uno::Sequence< css::beans::PropertyValue >& lArguments ) override;
+
+ // XFramesSupplier
+
+ virtual css::uno::Reference < css::frame::XFrames > SAL_CALL getFrames() override;
+ virtual css::uno::Reference < css::frame::XFrame > SAL_CALL getActiveFrame() override;
+ virtual void SAL_CALL setActiveFrame(const css::uno::Reference < css::frame::XFrame > & xFrame) override;
+
+ // XFrame
+
+ virtual void SAL_CALL initialize(const css::uno::Reference < css::awt::XWindow > & xWindow) override;
+ virtual css::uno::Reference < css::awt::XWindow > SAL_CALL getContainerWindow() override;
+ virtual void SAL_CALL setCreator(const css::uno::Reference < css::frame::XFramesSupplier > & xCreator) override;
+ virtual css::uno::Reference < css::frame::XFramesSupplier > SAL_CALL getCreator() override;
+ virtual OUString SAL_CALL getName() override;
+ virtual void SAL_CALL setName(const OUString & sName) override;
+ virtual css::uno::Reference < css::frame::XFrame > SAL_CALL findFrame(
+ const OUString & sTargetFrameName,
+ sal_Int32 nSearchFlags) override;
+ virtual sal_Bool SAL_CALL isTop() override;
+ virtual void SAL_CALL activate() override;
+ virtual void SAL_CALL deactivate() override;
+ virtual sal_Bool SAL_CALL isActive() override;
+ virtual void SAL_CALL contextChanged() override;
+ virtual sal_Bool SAL_CALL setComponent(
+ const css::uno::Reference < css::awt::XWindow > & xComponentWindow,
+ const css::uno::Reference < css::frame::XController > & xController) override;
+ virtual css::uno::Reference < css::awt::XWindow > SAL_CALL getComponentWindow() override;
+ virtual css::uno::Reference < css::frame::XController > SAL_CALL getController() override;
+ virtual void SAL_CALL addFrameActionListener(const css::uno::Reference < css::frame::XFrameActionListener > & xListener) override;
+ virtual void SAL_CALL removeFrameActionListener(const css::uno::Reference < css::frame::XFrameActionListener > & xListener) override;
+
+ // XComponent
+
+ virtual void SAL_CALL disposing() override;
+ virtual void SAL_CALL addEventListener(const css::uno::Reference < css::lang::XEventListener > & xListener) override;
+ virtual void SAL_CALL removeEventListener(const css::uno::Reference < css::lang::XEventListener > & xListener) override;
+
+ // XStatusIndicatorFactory
+
+ virtual css::uno::Reference < css::task::XStatusIndicator > SAL_CALL createStatusIndicator() override;
+
+ // XDispatchProvider
+
+ virtual css::uno::Reference < css::frame::XDispatch > SAL_CALL queryDispatch(const css::util::URL & aURL,
+ const OUString & sTargetFrameName,
+ sal_Int32 nSearchFlags) override;
+ virtual css::uno::Sequence < css::uno::Reference < css::frame::XDispatch > > SAL_CALL queryDispatches(
+ const css::uno::Sequence < css::frame::DispatchDescriptor > & lDescriptor) override;
+
+ // XDispatchProviderInterception
+
+ virtual void SAL_CALL registerDispatchProviderInterceptor(
+ const css::uno::Reference < css::frame::XDispatchProviderInterceptor > & xInterceptor) override;
+ virtual void SAL_CALL releaseDispatchProviderInterceptor(
+ const css::uno::Reference < css::frame::XDispatchProviderInterceptor > & xInterceptor) override;
+
+ // XDispatchInformationProvider
+
+ virtual css::uno::Sequence < sal_Int16 > SAL_CALL getSupportedCommandGroups() override;
+ virtual css::uno::Sequence < css::frame::DispatchInformation > SAL_CALL getConfigurableDispatchInformation(sal_Int16 nCommandGroup) override;
+
+ // XWindowListener
+ // Attention: windowResized() and windowShown() are implement only! All other are empty!
+
+ virtual void SAL_CALL windowResized(const css::awt::WindowEvent & aEvent) override;
+ virtual void SAL_CALL windowMoved(const css::awt::WindowEvent & /*aEvent*/ ) override {};
+ virtual void SAL_CALL windowShown(const css::lang::EventObject & aEvent) override;
+ virtual void SAL_CALL windowHidden(const css::lang::EventObject & aEvent) override;
+
+ // XFocusListener
+ // Attention: focusLost() not implemented yet!
+
+ virtual void SAL_CALL focusGained(const css::awt::FocusEvent & aEvent) override;
+ virtual void SAL_CALL focusLost(const css::awt::FocusEvent & /*aEvent*/ ) override {};
+
+ // XTopWindowListener
+ // Attention: windowActivated(), windowDeactivated() and windowClosing() are implement only! All other are empty!
+
+ virtual void SAL_CALL windowActivated(const css::lang::EventObject & aEvent) override;
+ virtual void SAL_CALL windowDeactivated(const css::lang::EventObject & aEvent) override;
+ virtual void SAL_CALL windowOpened(const css::lang::EventObject & /*aEvent*/ ) override {};
+ virtual void SAL_CALL windowClosing(const css::lang::EventObject & aEvent) override;
+ virtual void SAL_CALL windowClosed(const css::lang::EventObject & /*aEvent*/ ) override {};
+ virtual void SAL_CALL windowMinimized(const css::lang::EventObject & /*aEvent*/ ) override {};
+ virtual void SAL_CALL windowNormalized(const css::lang::EventObject & /*aEvent*/ ) override {};
+
+ // XEventListener
+
+ virtual void SAL_CALL disposing(const css::lang::EventObject & aEvent) override;
+
+ // XActionLockable
+
+ virtual sal_Bool SAL_CALL isActionLocked() override;
+ virtual void SAL_CALL addActionLock() override;
+ virtual void SAL_CALL removeActionLock() override;
+ virtual void SAL_CALL setActionLocks(sal_Int16 nLock) override;
+ virtual sal_Int16 SAL_CALL resetActionLocks() override;
+
+ // XCloseable
+
+ virtual void SAL_CALL close(sal_Bool bDeliverOwnership) override;
+
+ // XCloseBroadcaster
+
+ virtual void SAL_CALL addCloseListener(const css::uno::Reference < css::util::XCloseListener > & xListener) override;
+ virtual void SAL_CALL removeCloseListener(const css::uno::Reference < css::util::XCloseListener > & xListener) override;
+
+ // XTitle
+
+ virtual OUString SAL_CALL getTitle() override;
+ virtual void SAL_CALL setTitle(const OUString & sTitle) override;
+
+ // XTitleChangeBroadcaster
+
+ virtual void SAL_CALL addTitleChangeListener(const css::uno::Reference < css::frame::XTitleChangeListener > & xListener) override;
+ virtual void SAL_CALL removeTitleChangeListener(const css::uno::Reference < css::frame::XTitleChangeListener > & xListenr) override;
+
+ // XFrame2 attributes
+
+ virtual css::uno::Reference < css::container::XNameContainer > SAL_CALL getUserDefinedAttributes() override;
+
+ virtual css::uno::Reference < css::frame::XDispatchRecorderSupplier > SAL_CALL getDispatchRecorderSupplier() override;
+ virtual void SAL_CALL setDispatchRecorderSupplier(const css::uno::Reference < css::frame::XDispatchRecorderSupplier > & ) override;
+
+ virtual css::uno::Reference < css::uno::XInterface > SAL_CALL getLayoutManager() override;
+ virtual void SAL_CALL setLayoutManager(const css::uno::Reference < css::uno::XInterface > & ) override;
+
+ // XPropertySet
+ virtual css::uno::Reference < css::beans::XPropertySetInfo > SAL_CALL getPropertySetInfo() override;
+
+ virtual void SAL_CALL setPropertyValue(const OUString & sProperty, const css::uno::Any & aValue) override;
+
+ virtual css::uno::Any SAL_CALL getPropertyValue(const OUString & sProperty) override;
+
+ virtual void SAL_CALL addPropertyChangeListener(
+ const OUString & sProperty,
+ const css::uno::Reference < css::beans::XPropertyChangeListener > & xListener) override;
+
+ virtual void SAL_CALL removePropertyChangeListener(
+ const OUString & sProperty,
+ const css::uno::Reference < css::beans::XPropertyChangeListener > & xListener) override;
+
+ virtual void SAL_CALL addVetoableChangeListener(
+ const OUString & sProperty,
+ const css::uno::Reference < css::beans::XVetoableChangeListener > & xListener) override;
+
+ virtual void SAL_CALL removeVetoableChangeListener(
+ const OUString & sProperty,
+ const css::uno::Reference < css::beans::XVetoableChangeListener > & xListener) override;
+
+ // XPropertySetInfo
+ virtual css::uno::Sequence < css::beans::Property > SAL_CALL getProperties() override;
+
+ virtual css::beans::Property SAL_CALL getPropertyByName(const OUString & sName) override;
+
+ virtual sal_Bool SAL_CALL hasPropertyByName(const OUString & sName) override;
+
+
+private:
+
+ void impl_setPropertyValue(sal_Int32 nHandle,
+ const css::uno::Any& aValue);
+
+ css::uno::Any impl_getPropertyValue(sal_Int32 nHandle);
+
+ /** set a new owner for this helper.
+ *
+ * This owner is used as source for all broadcasted events.
+ * Further we hold it weak, because we don't wish to be disposed() .-)
+ */
+ void impl_setPropertyChangeBroadcaster(const css::uno::Reference< css::uno::XInterface >& xBroadcaster);
+
+ /** add a new property info to the set of supported ones.
+ *
+ * @param aProperty
+ * describes the new property.
+ *
+ * @throw [css::beans::PropertyExistException]
+ * if a property with the same name already exists.
+ *
+ * Note: The consistence of the whole set of properties is not checked here.
+ * Means e.g. ... a handle which exists more than once is not detected.
+ * The owner of this class has to be sure, that every new property does
+ * not clash with any existing one.
+ */
+ void impl_addPropertyInfo(const css::beans::Property& aProperty);
+
+ /** mark the object as "dead".
+ */
+ void impl_disablePropertySet();
+
+ bool impl_existsVeto(const css::beans::PropertyChangeEvent& aEvent);
+
+ void impl_notifyChangeListener(const css::beans::PropertyChangeEvent& aEvent);
+
+ /*-****************************************************************************************************
+ @short helper methods
+ @descr Follow methods are needed at different points of our code (more than ones!).
+
+ @attention Threadsafe methods are signed by "implts_..."!
+ *//*-*****************************************************************************************************/
+
+ // threadsafe
+ void implts_sendFrameActionEvent ( const css::frame::FrameAction& aAction );
+ void implts_resizeComponentWindow ( );
+ void implts_setIconOnWindow ( );
+ void implts_startWindowListening ( );
+ void implts_stopWindowListening ( );
+ void implts_checkSuicide ( );
+ void implts_forgetSubFrames ( );
+
+ // non threadsafe
+ void impl_checkMenuCloser ( );
+ void impl_setCloser ( const css::uno::Reference< css::frame::XFrame2 >& xFrame , bool bState );
+
+ void disableLayoutManager(const css::uno::Reference< css::frame::XLayoutManager2 >& xLayoutManager);
+
+ void checkDisposed() {
+ osl::MutexGuard g(rBHelper.rMutex);
+ if (rBHelper.bInDispose || rBHelper.bDisposed) {
+ throw css::lang::DisposedException("Frame disposed");
+ }
+ }
+
+// variables
+// -threadsafe by SolarMutex
+
+ /// reference to factory, which has create this instance
+ css::uno::Reference< css::uno::XComponentContext > m_xContext;
+ /// reference to factory helper to create status indicator objects
+ css::uno::Reference< css::task::XStatusIndicatorFactory > m_xIndicatorFactoryHelper;
+ /// points to an external set progress, which should be used instead of the internal one.
+ css::uno::WeakReference< css::task::XStatusIndicator > m_xIndicatorInterception;
+ /// helper for XDispatch/Provider and interception interfaces
+ css::uno::Reference< css::frame::XDispatchProvider > m_xDispatchHelper;
+ /// helper for XFrames, XIndexAccess and XElementAccess interfaces
+ css::uno::Reference< css::frame::XFrames > m_xFramesHelper;
+ /// container for ALL Listener
+ comphelper::OMultiTypeInterfaceContainerHelper2 m_aListenerContainer;
+ /// parent of this frame
+ css::uno::Reference< css::frame::XFramesSupplier > m_xParent;
+ /// containerwindow of this frame for embedded components
+ css::uno::Reference< css::awt::XWindow > m_xContainerWindow;
+ /// window of the actual component
+ css::uno::Reference< css::awt::XWindow > m_xComponentWindow;
+ /// controller of the actual frame
+ css::uno::Reference< css::frame::XController > m_xController;
+ /// listen to drag & drop
+ css::uno::Reference< css::datatransfer::dnd::XDropTargetListener > m_xDropTargetListener;
+ /// state, if I am a member of active path in tree or I have the focus or...
+ EActiveState m_eActiveState;
+ /// name of this frame
+ OUString m_sName;
+ /// frame has no parent or the parent is a task or the desktop
+ bool m_bIsFrameTop;
+ /// due to FrameActionEvent
+ bool m_bConnected;
+ sal_Int16 m_nExternalLockCount;
+ /// is used for dispatch recording and will be set/get from outside. Frame provide it only!
+ css::uno::Reference< css::frame::XDispatchRecorderSupplier > m_xDispatchRecorderSupplier;
+ /// ref counted class to support disabling commands defined by configuration file
+ SvtCommandOptions m_aCommandOptions;
+ /// in case of CloseVetoException on method close() was thrown by ourself - we must close ourself later if no internal processes are running
+ bool m_bSelfClose;
+ /// indicates, if this frame is used in hidden mode or not
+ bool m_bIsHidden;
+ /// The container window has WindowExtendedStyle::DocHidden set.
+ bool m_bDocHidden = false;
+ /// is used to layout the child windows of the frame.
+ css::uno::Reference< css::frame::XLayoutManager2 > m_xLayoutManager;
+ css::uno::Reference< css::frame::XDispatchInformationProvider > m_xDispatchInfoHelper;
+ css::uno::Reference< css::frame::XTitle > m_xTitleHelper;
+
+ std::unique_ptr<WindowCommandDispatch> m_pWindowCommandDispatch;
+
+ typedef std::unordered_map<OUString, css::beans::Property> TPropInfoHash;
+ TPropInfoHash m_lProps;
+
+ comphelper::OMultiTypeInterfaceContainerHelperVar3<css::beans::XPropertyChangeListener, OUString> m_lSimpleChangeListener;
+ comphelper::OMultiTypeInterfaceContainerHelperVar3<css::beans::XVetoableChangeListener, OUString> m_lVetoChangeListener;
+
+ // hold it weak ... otherwise this helper has to be "killed" explicitly .-)
+ css::uno::WeakReference< css::uno::XInterface > m_xBroadcaster;
+
+ FrameContainer m_aChildFrameContainer; /// array of child frames
+ /**
+ * URL of the file that is being loaded, when the load already started by we have no controller
+ * yet.
+ */
+ OUString m_aURL;
+};
+
+
+/*-****************************************************************************************************
+ @short standard constructor to create instance by factory
+ @descr This constructor initialize a new instance of this class by valid factory,
+ and will be set valid values on his member and baseclasses.
+
+ @attention a) Don't use your own reference during a UNO-Service-ctor! There is no guarantee, that you
+ will get over this. (e.g. using of your reference as parameter to initialize some member)
+ Do such things in DEFINE_INIT_SERVICE() method, which is called automatically after your ctor!!!
+ b) Baseclass OBroadcastHelper is a typedef in namespace cppu!
+ The microsoft compiler has some problems to handle it right BY using namespace explicitly ::cppu::OBroadcastHelper.
+ If we write it without a namespace or expand the typedef to OBroadcastHelperVar<...> -> it will be OK!?
+ I don't know why! (other compiler not tested .. but it works!)
+
+ @seealso method DEFINE_INIT_SERVICE()
+
+ @param xContext is the multi service manager, which creates this instance.
+ The value must be different from NULL!
+ @onerror ASSERT in debug version or nothing in release version.
+*//*-*****************************************************************************************************/
+XFrameImpl::XFrameImpl( css::uno::Reference< css::uno::XComponentContext > xContext )
+ : PartialWeakComponentImplHelper(m_aMutex)
+ // init member
+ , m_xContext (std::move( xContext ))
+ , m_aListenerContainer ( m_aMutex )
+ , m_eActiveState ( E_INACTIVE )
+ , m_bIsFrameTop ( true ) // I think we are top without a parent ... and there is no parent yet!
+ , m_bConnected ( false ) // There exist no component inside of use => sal_False, we are not connected!
+ , m_nExternalLockCount ( 0 )
+ , m_bSelfClose ( false ) // Important!
+ , m_bIsHidden ( true )
+ , m_lSimpleChangeListener ( m_aMutex )
+ , m_lVetoChangeListener ( m_aMutex )
+{
+}
+
+void XFrameImpl::initListeners()
+{
+ css::uno::Reference< css::uno::XInterface > xThis(static_cast< ::cppu::OWeakObject* >(this), css::uno::UNO_QUERY_THROW);
+
+ // Initialize a new dispatchhelper-object to handle dispatches.
+ // We use these helper as slave for our interceptor helper ... not directly!
+ // But he is event listener on THIS instance!
+ rtl::Reference<DispatchProvider> xDispatchProvider = new DispatchProvider( m_xContext, this );
+
+ m_xDispatchInfoHelper = new DispatchInformationProvider(m_xContext, this);
+
+ // Initialize a new interception helper object to handle dispatches and implement an interceptor mechanism.
+ // Set created dispatch provider as slowest slave of it.
+ // Hold interception helper by reference only - not by pointer!
+ // So it's easier to destroy it.
+ m_xDispatchHelper = new InterceptionHelper( this, xDispatchProvider );
+
+ // Initialize a new XFrames-helper-object to handle XIndexAccess and XElementAccess.
+ // We hold member as reference ... not as pointer too!
+ // Attention: We share our frame container with this helper. Container is threadsafe himself ... So I think we can do that.
+ // But look on dispose() for right order of deinitialization.
+ m_xFramesHelper = new OFrames( this, &m_aChildFrameContainer );
+
+ // Initialize the drop target listener.
+ // We hold member as reference ... not as pointer too!
+ m_xDropTargetListener = new OpenFileDropTargetListener( m_xContext, this );
+
+ // Safe impossible cases
+ // We can't work without these helpers!
+ SAL_WARN_IF( !xDispatchProvider.is(), "fwk.frame", "XFrameImpl::XFrameImpl(): Slowest slave for dispatch- and interception helper "
+ "is not valid. XDispatchProvider, XDispatch, XDispatchProviderInterception are not full supported!" );
+ SAL_WARN_IF( !m_xDispatchHelper.is(), "fwk.frame", "XFrameImpl::XFrameImpl(): Interception helper is not valid. XDispatchProvider, "
+ "XDispatch, XDispatchProviderInterception are not full supported!" );
+ SAL_WARN_IF( !m_xFramesHelper.is(), "fwk.frame", "XFrameImpl::XFrameImpl(): Frames helper is not valid. XFrames, "
+ "XIndexAccess and XElementAccess are not supported!" );
+ SAL_WARN_IF( !m_xDropTargetListener.is(), "fwk.frame", "XFrameImpl::XFrameImpl(): DropTarget helper is not valid. "
+ "Drag and drop without functionality!" );
+
+ // establish notifies for changing of "disabled commands" configuration during runtime
+ m_aCommandOptions.EstablishFrameCallback(this);
+
+ // Create an initial layout manager
+ // Create layout manager and connect it to the newly created frame
+ m_xLayoutManager = css::frame::LayoutManager::create(m_xContext);
+
+ // set information about all supported properties
+ impl_setPropertyChangeBroadcaster(static_cast< css::frame::XFrame* >(this));
+ impl_addPropertyInfo(
+ css::beans::Property(
+ FRAME_PROPNAME_ASCII_DISPATCHRECORDERSUPPLIER,
+ FRAME_PROPHANDLE_DISPATCHRECORDERSUPPLIER,
+ cppu::UnoType<css::frame::XDispatchRecorderSupplier>::get(),
+ css::beans::PropertyAttribute::TRANSIENT));
+ impl_addPropertyInfo(
+ css::beans::Property(
+ FRAME_PROPNAME_ASCII_INDICATORINTERCEPTION,
+ FRAME_PROPHANDLE_INDICATORINTERCEPTION,
+ cppu::UnoType<css::task::XStatusIndicator>::get(),
+ css::beans::PropertyAttribute::TRANSIENT));
+ impl_addPropertyInfo(
+ css::beans::Property(
+ FRAME_PROPNAME_ASCII_ISHIDDEN,
+ FRAME_PROPHANDLE_ISHIDDEN,
+ cppu::UnoType<bool>::get(),
+ css::beans::PropertyAttribute::TRANSIENT | css::beans::PropertyAttribute::READONLY));
+ impl_addPropertyInfo(
+ css::beans::Property(
+ FRAME_PROPNAME_ASCII_LAYOUTMANAGER,
+ FRAME_PROPHANDLE_LAYOUTMANAGER,
+ cppu::UnoType<css::frame::XLayoutManager>::get(),
+ css::beans::PropertyAttribute::TRANSIENT));
+ impl_addPropertyInfo(
+ css::beans::Property(
+ FRAME_PROPNAME_ASCII_TITLE,
+ FRAME_PROPHANDLE_TITLE,
+ cppu::UnoType<OUString>::get(),
+ css::beans::PropertyAttribute::TRANSIENT));
+ impl_addPropertyInfo(css::beans::Property(FRAME_PROPNAME_ASCII_URL, FRAME_PROPHANDLE_URL,
+ cppu::UnoType<OUString>::get(),
+ css::beans::PropertyAttribute::TRANSIENT));
+}
+
+/*-************************************************************************************************************
+ @interface XComponentLoader
+ @short try to load given URL into a task
+ @descr You can give us some information about the content, which you will load into a frame.
+ We search or create this target for you, make a type detection of given URL and try to load it.
+ As result of this operation we return the new created component or nothing, if loading failed.
+ @param "sURL" , URL, which represent the content
+ @param "sTargetFrameName" , name of target frame or special value like "_self", "_blank" ...
+ @param "nSearchFlags" , optional arguments for frame search, if target is not a special one
+ @param "lArguments" , optional arguments for loading
+ @return A valid component reference, if loading was successful.
+ A null reference otherwise.
+
+ @onerror We return a null reference.
+ @threadsafe yes
+*//*-*************************************************************************************************************/
+css::uno::Reference< css::lang::XComponent > SAL_CALL XFrameImpl::loadComponentFromURL(
+ const OUString& sURL,
+ const OUString& sTargetFrameName,
+ sal_Int32 nSearchFlags,
+ const css::uno::Sequence< css::beans::PropertyValue >& lArguments )
+{
+ checkDisposed();
+
+ css::uno::Reference< css::frame::XComponentLoader > xThis(this);
+
+ utl::MediaDescriptor aDescriptor(lArguments);
+ bool bOnMainThread = aDescriptor.getUnpackedValueOrDefault("OnMainThread", false);
+
+ if (bOnMainThread)
+ {
+ // Make sure that we own the solar mutex, otherwise later
+ // vcl::SolarThreadExecutor::execute() will release the solar mutex, even if it's owned by
+ // another thread, leading to an std::abort() at the end.
+ SolarMutexGuard g;
+
+ return vcl::solarthread::syncExecute(std::bind(&LoadEnv::loadComponentFromURL, xThis,
+ m_xContext, sURL, sTargetFrameName,
+ nSearchFlags, lArguments));
+ }
+ else
+ return LoadEnv::loadComponentFromURL(xThis, m_xContext, sURL, sTargetFrameName,
+ nSearchFlags, lArguments);
+}
+
+/*-****************************************************************************************************
+ @short return access to append or remove children on desktop
+ @descr We don't implement these interface directly. We use a helper class to do this.
+ If you wish to add or delete children to/from the container, call these method to get
+ a reference to the helper.
+
+ @seealso class OFrames
+ @return A reference to the helper which answer your queries.
+
+ @onerror A null reference is returned.
+*//*-*****************************************************************************************************/
+css::uno::Reference< css::frame::XFrames > SAL_CALL XFrameImpl::getFrames()
+{
+ checkDisposed();
+
+ SolarMutexGuard g;
+ // Return access to all child frames to caller.
+ // Our childframe container is implemented in helper class OFrames and used as a reference m_xFramesHelper!
+ return m_xFramesHelper;
+}
+
+/*-****************************************************************************************************
+ @short get the current active child frame
+ @descr It must be a frameto. Direct children of a frame are frames only! No task or desktop is accepted.
+ We don't save this information directly in this class. We use our container-helper
+ to do that.
+
+ @seealso class OFrameContainer
+ @seealso method setActiveFrame()
+ @return A reference to our current active childframe, if anyone exist.
+ @return A null reference, if nobody is active.
+
+ @onerror A null reference is returned.
+*//*-*****************************************************************************************************/
+css::uno::Reference< css::frame::XFrame > SAL_CALL XFrameImpl::getActiveFrame()
+{
+ checkDisposed();
+
+ SolarMutexGuard g;
+ // Return current active frame.
+ // This information is available on the container.
+ return m_aChildFrameContainer.getActive();
+}
+
+/*-****************************************************************************************************
+ @short set the new active direct child frame
+ @descr It must be a frame to. Direct children of frame are frames only! No task or desktop is accepted.
+ We don't save this information directly in this class. We use our container-helper
+ to do that.
+
+ @seealso class OFrameContainer
+ @seealso method getActiveFrame()
+
+ @param "xFrame", reference to new active child. It must be an already existing child!
+ @onerror An assertion is thrown and element is ignored, if given frame isn't already a child of us.
+*//*-*****************************************************************************************************/
+void SAL_CALL XFrameImpl::setActiveFrame( const css::uno::Reference< css::frame::XFrame >& xFrame )
+{
+ checkDisposed();
+
+ /* SAFE AREA ----------------------------------------------------------------------------------------------- */
+ SolarMutexResettableGuard aWriteLock;
+
+ // Copy necessary member for threadsafe access!
+ // m_aChildFrameContainer is threadsafe himself and he live if we live!!!
+ css::uno::Reference< css::frame::XFrame > xActiveChild = m_aChildFrameContainer.getActive();
+ EActiveState eActiveState = m_eActiveState;
+
+ aWriteLock.clear();
+ /* UNSAFE AREA --------------------------------------------------------------------------------------------- */
+
+ // Don't work, if "new" active frame isn't different from current one!
+ // (xFrame==NULL is allowed to UNSET it!)
+ if( xActiveChild != xFrame )
+ {
+ // ... otherwise set new and deactivate old one.
+ m_aChildFrameContainer.setActive( xFrame );
+ if (
+ ( eActiveState != E_INACTIVE ) &&
+ xActiveChild.is()
+ )
+ {
+ xActiveChild->deactivate();
+ }
+ }
+
+ if( xFrame.is() )
+ {
+ // If last active frame had focus ...
+ // ... reset state to ACTIVE and send right FrameActionEvent for focus lost.
+ if( eActiveState == E_FOCUS )
+ {
+ aWriteLock.reset();
+ eActiveState = E_ACTIVE;
+ m_eActiveState = eActiveState;
+ aWriteLock.clear();
+ implts_sendFrameActionEvent( css::frame::FrameAction_FRAME_UI_DEACTIVATING );
+ }
+
+ // If last active frame was active ...
+ // but new one is not it ...
+ // ... set it as active one.
+ if ( eActiveState == E_ACTIVE && !xFrame->isActive() )
+ {
+ xFrame->activate();
+ }
+ }
+ else
+ // If this frame is active and has no active subframe anymore it is UI active too
+ if( eActiveState == E_ACTIVE )
+ {
+ aWriteLock.reset();
+ eActiveState = E_FOCUS;
+ m_eActiveState = eActiveState;
+ aWriteLock.clear();
+ implts_sendFrameActionEvent( css::frame::FrameAction_FRAME_UI_ACTIVATED );
+ }
+}
+
+/*-****************************************************************************************************
+ initialize new created layout manager
+**/
+void lcl_enableLayoutManager(const css::uno::Reference< css::frame::XLayoutManager2 >& xLayoutManager,
+ const css::uno::Reference< css::frame::XFrame >& xFrame )
+{
+ // Provide container window to our layout manager implementation
+ xLayoutManager->attachFrame(xFrame);
+
+ xFrame->addFrameActionListener(xLayoutManager);
+
+ rtl::Reference<DockingAreaDefaultAcceptor> xDockingAreaAcceptor = new DockingAreaDefaultAcceptor(xFrame);
+ xLayoutManager->setDockingAreaAcceptor(xDockingAreaAcceptor);
+}
+
+/*-****************************************************************************************************
+ deinitialize layout manager
+**/
+void XFrameImpl::disableLayoutManager(const css::uno::Reference< css::frame::XLayoutManager2 >& xLayoutManager)
+{
+ removeFrameActionListener(xLayoutManager);
+ xLayoutManager->setDockingAreaAcceptor(css::uno::Reference< css::ui::XDockingAreaAcceptor >());
+ xLayoutManager->attachFrame(css::uno::Reference< css::frame::XFrame >());
+}
+
+/*-****************************************************************************************************
+ @short initialize frame instance
+ @descr A frame needs a window. This method set a new one ... but should called one times only!
+ We use this window to listen for window events and forward it to our set component.
+ It's used as parent of component window too.
+
+ @seealso method getContainerWindow()
+ @seealso method setComponent()
+ @seealso member m_xContainerWindow
+
+ @param "xWindow", reference to new container window - must be valid!
+ @onerror We do nothing.
+*//*-*****************************************************************************************************/
+void SAL_CALL XFrameImpl::initialize( const css::uno::Reference< css::awt::XWindow >& xWindow )
+{
+ /* UNSAFE AREA --------------------------------------------------------------------------------------------- */
+ if (!xWindow.is())
+ throw css::uno::RuntimeException(
+ "XFrameImpl::initialize() called without a valid container window reference.",
+ static_cast< css::frame::XFrame* >(this));
+
+ checkDisposed();
+
+ /* SAFE AREA ----------------------------------------------------------------------------------------------- */
+ SolarMutexResettableGuard aWriteLock;
+
+ if ( m_xContainerWindow.is() )
+ throw css::uno::RuntimeException(
+ "XFrameImpl::initialized() is called more than once, which is not useful nor allowed.",
+ static_cast< css::frame::XFrame* >(this));
+
+ // This must be the first call of this method!
+ // We should initialize our object and open it for working.
+ // Set the new window.
+ SAL_WARN_IF( m_xContainerWindow.is(), "fwk.frame", "XFrameImpl::initialize(): Leak detected! This state should never occur ..." );
+ m_xContainerWindow = xWindow;
+
+ // if window is initially visible, we will never get a windowShowing event
+ VclPtr<vcl::Window> pWindow = VCLUnoHelper::GetWindow(xWindow);
+ if (pWindow)
+ {
+ if (pWindow->IsVisible())
+ m_bIsHidden = false;
+ m_bDocHidden
+ = static_cast<bool>(pWindow->GetExtendedStyle() & WindowExtendedStyle::DocHidden);
+ }
+
+ css::uno::Reference< css::frame::XLayoutManager2 > xLayoutManager = m_xLayoutManager;
+
+ // Release lock ... because we call some impl methods, which are threadsafe by himself.
+ // If we hold this lock - we will produce our own deadlock!
+ aWriteLock.clear();
+ /* UNSAFE AREA --------------------------------------------------------------------------------------------- */
+
+ // Avoid enabling the layout manager for hidden frames: it's expensive and
+ // provides little value.
+ if (xLayoutManager.is() && !m_bDocHidden)
+ lcl_enableLayoutManager(xLayoutManager, this);
+
+ // create progress helper
+ css::uno::Reference< css::frame::XFrame > xThis (static_cast< css::frame::XFrame* >(this),
+ css::uno::UNO_QUERY_THROW);
+ css::uno::Reference< css::task::XStatusIndicatorFactory > xIndicatorFactory =
+ css::task::StatusIndicatorFactory::createWithFrame(m_xContext, xThis,
+ false/*DisableReschedule*/, true/*AllowParentShow*/ );
+
+ // SAFE -> ----------------------------------
+ aWriteLock.reset();
+ m_xIndicatorFactoryHelper = xIndicatorFactory;
+ aWriteLock.clear();
+ // <- SAFE ----------------------------------
+
+ // Start listening for events after setting it on helper class ...
+ // So superfluous messages are filtered to NULL :-)
+ implts_startWindowListening();
+
+ m_pWindowCommandDispatch.reset(new WindowCommandDispatch(m_xContext, this));
+
+ // Initialize title functionality
+ m_xTitleHelper = new TitleHelper( m_xContext, xThis, nullptr );
+}
+
+/*-****************************************************************************************************
+ @short returns current set container window
+ @descr The ContainerWindow property is used as a container for the component
+ in this frame. So this object implements a container interface too.
+ The instantiation of the container window is done by the user of this class.
+ The frame is the owner of its container window.
+
+ @seealso method initialize()
+ @return A reference to current set containerwindow.
+
+ @onerror A null reference is returned.
+*//*-*****************************************************************************************************/
+css::uno::Reference< css::awt::XWindow > SAL_CALL XFrameImpl::getContainerWindow()
+{
+ SolarMutexGuard g;
+ return m_xContainerWindow;
+}
+
+/*-****************************************************************************************************
+ @short set parent frame
+ @descr We need a parent to support some functionality! e.g. findFrame()
+ By the way we use the chance to set an internal information about our top state.
+ So we must not check this information during every isTop() call.
+ We are top, if our parent is the desktop instance or we haven't any parent.
+
+ @seealso getCreator()
+ @seealso findFrame()
+ @seealso isTop()
+ @seealso m_bIsFrameTop
+
+ @param xCreator
+ valid reference to our new owner frame, which should implement a supplier interface
+
+ @threadsafe yes
+*//*-*****************************************************************************************************/
+void SAL_CALL XFrameImpl::setCreator( const css::uno::Reference< css::frame::XFramesSupplier >& xCreator )
+{
+ checkDisposed();
+
+ /* SAFE { */
+ {
+ SolarMutexGuard aWriteLock;
+ m_xParent = xCreator;
+ }
+ /* } SAFE */
+
+ css::uno::Reference< css::frame::XDesktop > xIsDesktop( xCreator, css::uno::UNO_QUERY );
+ m_bIsFrameTop = ( xIsDesktop.is() || ! xCreator.is() );
+}
+
+/*-****************************************************************************************************
+ @short returns current parent frame
+ @descr The Creator is the parent frame container. If it is NULL, the frame is the uppermost one.
+
+ @seealso method setCreator()
+ @return A reference to current set parent frame container.
+
+ @onerror A null reference is returned.
+*//*-*****************************************************************************************************/
+css::uno::Reference< css::frame::XFramesSupplier > SAL_CALL XFrameImpl::getCreator()
+{
+ checkDisposed();
+ SolarMutexGuard g;
+ return m_xParent;
+}
+
+/*-****************************************************************************************************
+ @short returns current set name of frame
+ @descr This name is used to find target of findFrame() or queryDispatch() calls.
+
+ @seealso method setName()
+ @return Current set name of frame.
+
+ @onerror An empty string is returned.
+*//*-*****************************************************************************************************/
+OUString SAL_CALL XFrameImpl::getName()
+{
+ SolarMutexGuard g;
+ return m_sName;
+}
+
+/*-****************************************************************************************************
+ @short set new name for frame
+ @descr This name is used to find target of findFrame() or queryDispatch() calls.
+
+ @attention Special names like "_blank", "_self" aren't allowed...
+ "_beamer" excepts this rule!
+
+ @seealso method getName()
+
+ @param "sName", new frame name.
+ @onerror We do nothing.
+*//*-*****************************************************************************************************/
+void SAL_CALL XFrameImpl::setName( const OUString& sName )
+{
+ SolarMutexGuard g;
+ // Set new name... but look for invalid special target names!
+ // They are not allowed to set.
+ if (TargetHelper::isValidNameForFrame(sName))
+ m_sName = sName;
+}
+
+/*-****************************************************************************************************
+ @short search for frames
+ @descr This method searches for a frame with the specified name.
+ Frames may contain other frames (e.g. a frameset) and may
+ be contained in other frames. This hierarchy is searched by
+ this method.
+ First some special names are taken into account, i.e. "",
+ "_self", "_top", "_blank" etc. The nSearchFlags are ignored
+ when comparing these names with sTargetFrameName, further steps are
+ controlled by the search flags. If allowed, the name of the frame
+ itself is compared with the desired one, then ( again if allowed )
+ the method findFrame() is called for all children, for siblings
+ and as last for the parent frame.
+ If no frame with the given name is found until the top frames container,
+ a new top one is created, if this is allowed by a special
+ flag. The new frame also gets the desired name.
+
+ @param sTargetFrameName
+ special names (_blank, _self) or real name of target frame
+ @param nSearchFlags
+ optional flags which regulate search for non special target frames
+
+ @return A reference to found or may be new created frame.
+ @threadsafe yes
+*//*-*****************************************************************************************************/
+css::uno::Reference< css::frame::XFrame > SAL_CALL XFrameImpl::findFrame( const OUString& sTargetFrameName,
+ sal_Int32 nSearchFlags )
+{
+ css::uno::Reference< css::frame::XFrame > xTarget;
+
+ // 0) Ignore wrong parameter!
+ // We don't support search for following special targets.
+ // If we reject this requests - we must not check for such names
+ // in following code again and again. If we do not so -wrong
+ // search results can occur!
+
+ if ( sTargetFrameName == SPECIALTARGET_DEFAULT ) // valid for dispatches - not for findFrame()!
+ {
+ return nullptr;
+ }
+
+ // I) check for special defined targets first which must be handled exclusive.
+ // force using of "if() else if() ..."
+
+ // get threadsafe some necessary member which are necessary for following functionality
+ /* SAFE { */
+ SolarMutexResettableGuard aReadLock;
+ css::uno::Reference< css::frame::XFrame > xParent = m_xParent;
+ bool bIsTopFrame = m_bIsFrameTop;
+ bool bIsTopWindow = WindowHelper::isTopWindow(m_xContainerWindow);
+ aReadLock.clear();
+ /* } SAFE */
+
+ // I.I) "_blank"
+ // Not allowed for a normal frame - but for the desktop.
+ // Use helper class to do so. It use the desktop automatically.
+
+ if ( sTargetFrameName==SPECIALTARGET_BLANK )
+ {
+ TaskCreator aCreator(m_xContext);
+ xTarget = aCreator.createTask(sTargetFrameName, utl::MediaDescriptor());
+ }
+
+ // I.II) "_parent"
+ // It doesn't matter if we have a valid parent or not. User ask for him and get it.
+ // An empty result is a valid result too.
+
+ else if ( sTargetFrameName==SPECIALTARGET_PARENT )
+ {
+ xTarget = xParent;
+ }
+
+ // I.III) "_top"
+ // If we are not the top frame in this hierarchy, we must forward request to our parent.
+ // Otherwise we must return ourself.
+
+ else if ( sTargetFrameName==SPECIALTARGET_TOP )
+ {
+ if (bIsTopFrame)
+ xTarget = this;
+ else if (xParent.is()) // If we are not top - the parent MUST exist. But may it's better to check it again .-)
+ xTarget = xParent->findFrame(SPECIALTARGET_TOP,0);
+ }
+
+ // I.IV) "_self", ""
+ // This mean this frame in every case.
+
+ else if (
+ ( sTargetFrameName==SPECIALTARGET_SELF ) ||
+ ( sTargetFrameName.isEmpty() )
+ )
+ {
+ xTarget = this;
+ }
+
+ // I.V) "_beamer"
+ // This is a special sub frame of any task. We must return it if we found it on our direct children
+ // or create it there if it not already exists.
+ // Note: Such beamer exists for task(top) frames only!
+
+ else if ( sTargetFrameName==SPECIALTARGET_BEAMER )
+ {
+ // We are a task => search or create the beamer
+ if (bIsTopWindow)
+ {
+ xTarget = m_aChildFrameContainer.searchOnDirectChildrens(SPECIALTARGET_BEAMER);
+ if ( ! xTarget.is() )
+ {
+ /* TODO
+ Creation not supported yet!
+ Wait for new layout manager service because we can't plug it
+ inside already opened document of this frame...
+ */
+ }
+ }
+ // We aren't a task => forward request to our parent or ignore it.
+ else if (xParent.is())
+ xTarget = xParent->findFrame(SPECIALTARGET_BEAMER,0);
+ }
+
+ else
+ {
+
+ // II) otherwise use optional given search flags
+ // force using of combinations of such flags. means no "else" part of use if() statements.
+ // But we ust break further searches if target was already found.
+ // Order of using flags is fix: SELF - CHILDREN - SIBLINGS - PARENT
+ // TASK and CREATE are handled special.
+
+ // get threadsafe some necessary member which are necessary for following functionality
+ /* SAFE { */
+ aReadLock.reset();
+ OUString sOwnName = m_sName;
+ aReadLock.clear();
+ /* } SAFE */
+
+ // II.I) SELF
+ // Check for right name. If it's the searched one return ourself - otherwise
+ // ignore this flag.
+
+ if (
+ (nSearchFlags & css::frame::FrameSearchFlag::SELF) &&
+ (sOwnName == sTargetFrameName )
+ )
+ {
+ xTarget = this;
+ }
+
+ // II.II) CHILDREN
+ // Search on all children for the given target name.
+ // An empty name value can't occur here - because it must be already handled as "_self"
+ // before. Used helper function of container doesn't create any frame.
+ // It makes a deep search only.
+
+ if (
+ ( ! xTarget.is() ) &&
+ (nSearchFlags & css::frame::FrameSearchFlag::CHILDREN)
+ )
+ {
+ xTarget = m_aChildFrameContainer.searchOnAllChildrens(sTargetFrameName);
+ }
+
+ // II.III) TASKS
+ // This is a special flag. It regulate search on this task tree only or allow search on
+ // all other ones (which are sibling trees of us) too.
+ // Upper search must stop at this frame if we are the topest one and the TASK flag is not set
+ // or we can ignore it if we have no valid parent.
+
+ if (
+ ( bIsTopFrame && (nSearchFlags & css::frame::FrameSearchFlag::TASKS) ) ||
+ ( ! bIsTopFrame )
+ )
+ {
+
+ // II.III.I) SIBLINGS
+ // Search on all our direct siblings - means all children of our parent.
+ // Use this flag in combination with TASK. We must suppress such upper search if
+ // user has not set it and if we are a top frame.
+ // Attention: Don't forward this request to our parent as a findFrame() call.
+ // In such case we must protect us against recursive calls.
+ // Use snapshot of our parent. But don't use queryFrames() of XFrames interface.
+ // Because it's return all siblings and all her children including our children too
+ // if we call it with the CHILDREN flag. We don't need that - we need the direct container
+ // items of our parent only to start searches there. So we must use the container interface
+ // XIndexAccess instead of XFrames.
+
+ if (
+ ( ! xTarget.is() ) &&
+ (nSearchFlags & css::frame::FrameSearchFlag::SIBLINGS) &&
+ ( xParent.is() ) // search on siblings is impossible without a parent
+ )
+ {
+ css::uno::Reference< css::frame::XFramesSupplier > xSupplier( xParent, css::uno::UNO_QUERY );
+ if (xSupplier.is())
+ {
+ css::uno::Reference< css::container::XIndexAccess > xContainer = xSupplier->getFrames();
+ if (xContainer.is())
+ {
+ sal_Int32 nCount = xContainer->getCount();
+ for( sal_Int32 i=0; i<nCount; ++i )
+ {
+ css::uno::Reference< css::frame::XFrame > xSibling;
+ if (
+ // control unpacking
+ ( !(xContainer->getByIndex(i)>>=xSibling) ) ||
+ // check for valid items
+ ( ! xSibling.is() ) ||
+ // ignore ourself! (We are a part of this container too - but search on our children was already done.)
+ ( xSibling==static_cast< ::cppu::OWeakObject* >(this) )
+ )
+ {
+ continue;
+ }
+
+ // Don't allow upper search here! Use right flags to regulate it.
+ // And allow deep search on children only - if it was allowed for us too.
+ sal_Int32 nRightFlags = css::frame::FrameSearchFlag::SELF;
+ if (nSearchFlags & css::frame::FrameSearchFlag::CHILDREN)
+ nRightFlags |= css::frame::FrameSearchFlag::CHILDREN;
+ xTarget = xSibling->findFrame(sTargetFrameName, nRightFlags );
+ // perform search be breaking further search if a result exist.
+ if (xTarget.is())
+ break;
+ }
+ }
+ }
+ }
+
+ // II.III.II) PARENT
+ // Forward search to our parent (if he exists.)
+ // To prevent us against recursive and superfluous calls (which can occur if we allow him
+ // to search on his children too) we must change used search flags.
+
+ if (
+ ( ! xTarget.is() ) &&
+ (nSearchFlags & css::frame::FrameSearchFlag::PARENT) &&
+ ( xParent.is() )
+ )
+ {
+ if (xParent->getName() == sTargetFrameName)
+ xTarget = xParent;
+ else
+ {
+ sal_Int32 nRightFlags = nSearchFlags & ~css::frame::FrameSearchFlag::CHILDREN;
+ xTarget = xParent->findFrame(sTargetFrameName, nRightFlags);
+ }
+ }
+ }
+
+ // II.IV) CREATE
+ // If we haven't found any valid target frame by using normal flags - but user allowed us to create
+ // a new one ... we should do that. Used TaskCreator use Desktop instance automatically as parent!
+
+ if (
+ ( ! xTarget.is() ) &&
+ (nSearchFlags & css::frame::FrameSearchFlag::CREATE)
+ )
+ {
+ TaskCreator aCreator(m_xContext);
+ xTarget = aCreator.createTask(sTargetFrameName, utl::MediaDescriptor());
+ }
+ }
+
+ return xTarget;
+}
+
+/*-****************************************************************************************************
+ @descr Returns sal_True, if this frame is a "top frame", otherwise sal_False.
+ The "m_bIsFrameTop" member must be set in the ctor or setCreator() method.
+ A top frame is a member of the top frame container or a member of the
+ task frame container. Both containers can create new frames if the findFrame()
+ method of their css::frame::XFrame interface is called with a frame name not yet known.
+
+ @seealso ctor
+ @seealso method setCreator()
+ @seealso method findFrame()
+ @return true, if is it a top frame ... false otherwise.
+
+ @onerror No error should occur!
+*//*-*****************************************************************************************************/
+sal_Bool SAL_CALL XFrameImpl::isTop()
+{
+ checkDisposed();
+ SolarMutexGuard g;
+ // This information is set in setCreator().
+ // We are top, if our parent is a task or the desktop or if no parent exist!
+ return m_bIsFrameTop;
+}
+
+/*-****************************************************************************************************
+ @short activate frame in hierarchy
+ @descr This feature is used to mark active paths in our frame hierarchy.
+ You can be a listener for this event to react for it ... change some internal states or something else.
+
+ @seealso method deactivate()
+ @seealso method isActivate()
+ @seealso enum EActiveState
+ @seealso listener mechanism
+*//*-*****************************************************************************************************/
+void SAL_CALL XFrameImpl::activate()
+{
+ checkDisposed();
+
+ /* SAFE AREA ----------------------------------------------------------------------------------------------- */
+ SolarMutexResettableGuard aWriteLock;
+
+ // Copy necessary member and free the lock.
+ // It's not necessary for m_aChildFrameContainer ... because
+ // he is threadsafe himself and live if we live.
+ css::uno::Reference< css::frame::XFrame > xActiveChild = m_aChildFrameContainer.getActive();
+ css::uno::Reference< css::frame::XFramesSupplier > xParent = m_xParent;
+ css::uno::Reference< css::frame::XFrame > xThis(this);
+ EActiveState eState = m_eActiveState;
+
+ aWriteLock.clear();
+ /* UNSAFE AREA --------------------------------------------------------------------------------------------- */
+
+ // 1) If I am not active before...
+ if( eState == E_INACTIVE )
+ {
+ // ... do it then.
+ aWriteLock.reset();
+ eState = E_ACTIVE;
+ m_eActiveState = eState;
+ aWriteLock.clear();
+ // Deactivate sibling path and forward activation to parent ... if any parent exist!
+ if( xParent.is() )
+ {
+ // Every time set THIS frame as active child of parent and activate it.
+ // We MUST have a valid path from bottom to top as active path!
+ // But we must deactivate the old active sibling path first.
+
+ // Attention: Deactivation of an active path, deactivate the whole path ... from bottom to top!
+ // But we wish to deactivate founded sibling-tree only.
+ // [ see deactivate() / step 4) for further information! ]
+
+ xParent->setActiveFrame( xThis );
+
+ // Then we can activate from here to top.
+ // Attention: We are ACTIVE now. And the parent will call activate() at us!
+ // But we do nothing then! We are already activated.
+ xParent->activate();
+ }
+ // It's necessary to send event NOW - not before.
+ // Activation goes from bottom to top!
+ // That's the reason to activate parent first and send event now.
+ implts_sendFrameActionEvent( css::frame::FrameAction_FRAME_ACTIVATED );
+ }
+
+ // 2) I was active before or current activated and there is a path from here to bottom, who CAN be active.
+ // But our direct child of path is not active yet.
+ // (It can be, if activation occur in the middle of a current path!)
+ // In these case we activate path to bottom to set focus on right frame!
+ if ( eState == E_ACTIVE && xActiveChild.is() && !xActiveChild->isActive() )
+ {
+ xActiveChild->activate();
+ }
+
+ // 3) I was active before or current activated. But if I have no active child => I will get the focus!
+ if ( eState == E_ACTIVE && !xActiveChild.is() )
+ {
+ aWriteLock.reset();
+ eState = E_FOCUS;
+ m_eActiveState = eState;
+ aWriteLock.clear();
+ implts_sendFrameActionEvent( css::frame::FrameAction_FRAME_UI_ACTIVATED );
+ }
+}
+
+/*-****************************************************************************************************
+ @short deactivate frame in hierarchy
+ @descr This feature is used to deactivate paths in our frame hierarchy.
+ You can be a listener for this event to react for it... change some internal states or something else.
+
+ @seealso method activate()
+ @seealso method isActivate()
+ @seealso enum EActiveState
+ @seealso listener mechanism
+*//*-*****************************************************************************************************/
+void SAL_CALL XFrameImpl::deactivate()
+{
+ checkDisposed();
+
+ /* SAFE AREA ----------------------------------------------------------------------------------------------- */
+ SolarMutexResettableGuard aWriteLock;
+
+ // Copy necessary member and free the lock.
+ css::uno::Reference< css::frame::XFrame > xActiveChild = m_aChildFrameContainer.getActive();
+ css::uno::Reference< css::frame::XFramesSupplier > xParent = m_xParent;
+ css::uno::Reference< css::frame::XFrame > xThis(this);
+ EActiveState eState = m_eActiveState;
+
+ aWriteLock.clear();
+ /* UNSAFE AREA --------------------------------------------------------------------------------------------- */
+
+ // Work only, if there something to do!
+ if( eState == E_INACTIVE )
+ return;
+
+
+ // 1) Deactivate all active children.
+ if ( xActiveChild.is() && xActiveChild->isActive() )
+ {
+ xActiveChild->deactivate();
+ }
+
+ // 2) If I have the focus - I will lost it now.
+ if( eState == E_FOCUS )
+ {
+ // Set new state INACTIVE(!) and send message to all listener.
+ // Don't set ACTIVE as new state. This frame is deactivated for next time - due to activate().
+ aWriteLock.reset();
+ eState = E_ACTIVE;
+ m_eActiveState = eState;
+ aWriteLock.clear();
+ implts_sendFrameActionEvent( css::frame::FrameAction_FRAME_UI_DEACTIVATING );
+ }
+
+ // 3) If I am active - I will be deactivated now.
+ if( eState == E_ACTIVE )
+ {
+ // Set new state and send message to all listener.
+ aWriteLock.reset();
+ eState = E_INACTIVE;
+ m_eActiveState = eState;
+ aWriteLock.clear();
+ implts_sendFrameActionEvent( css::frame::FrameAction_FRAME_DEACTIVATING );
+ }
+
+ // 4) If there is a path from here to my parent...
+ // ... I am on the top or in the middle of deactivated subtree and action was started here.
+ // I must deactivate all frames from here to top, which are members of current path.
+ // Stop, if THESE frame not the active frame of our parent!
+ if ( xParent.is() && xParent->getActiveFrame() == xThis )
+ {
+ // We MUST break the path - otherwise we will get the focus - not our parent! ...
+ // Attention: Our parent don't call us again - WE ARE NOT ACTIVE YET!
+ // [ see step 3 and condition "if ( m_eActiveState!=INACTIVE ) ..." in this method! ]
+ xParent->deactivate();
+ }
+}
+
+/*-****************************************************************************************************
+ @short returns active state
+ @descr Call it to get information about current active state of this frame.
+
+ @seealso method activate()
+ @seealso method deactivate()
+ @seealso enum EActiveState
+ @return true if active, false otherwise.
+
+ @onerror No error should occur.
+*//*-*****************************************************************************************************/
+sal_Bool SAL_CALL XFrameImpl::isActive()
+{
+ checkDisposed();
+ SolarMutexGuard g;
+ return m_eActiveState == E_ACTIVE || m_eActiveState == E_FOCUS;
+}
+
+/*-****************************************************************************************************
+ @short ???
+*//*-*****************************************************************************************************/
+void SAL_CALL XFrameImpl::contextChanged()
+{
+ // Sometimes called during closing object...
+ // Impl-method is threadsafe himself!
+ // Send event to all listener for frame actions.
+ implts_sendFrameActionEvent( css::frame::FrameAction_CONTEXT_CHANGED );
+}
+
+/*-****************************************************************************************************
+ @short set new component inside the frame
+ @descr A frame is a container for a component. Use this method to set, change or release it!
+ We accept null references! The xComponentWindow will be a child of our container window
+ and get all window events from us.
+
+ @attention (a) A current set component can disagree with the suspend() request!
+ We don't set the new one and return with false then.
+ (b) It's possible to set:
+ (b1) a simple component here which supports the window only - no controller;
+ (b2) a full featured component which supports window and controller;
+ (b3) or both to NULL if outside code which to forget this component.
+
+ @seealso method getComponentWindow()
+ @seealso method getController()
+
+ @param xComponentWindow
+ valid reference to new component window which will be a child of internal container window
+ May <NULL/> for releasing.
+ @param xController
+ reference to new component controller
+ (may <NULL/> for releasing or setting of a simple component)
+
+ @return <TRUE/> if operation was successful, <FALSE/> otherwise.
+
+ @onerror We return <FALSE/>.
+ @threadsafe yes
+*//*-*****************************************************************************************************/
+sal_Bool SAL_CALL XFrameImpl::setComponent(const css::uno::Reference< css::awt::XWindow >& xComponentWindow,
+ const css::uno::Reference< css::frame::XController >& xController )
+{
+
+ // Ignore this HACK of sfx2!
+ // He call us with a valid controller without a valid window... that's not allowed!
+ if ( xController.is() && ! xComponentWindow.is() )
+ return true;
+
+ checkDisposed();
+
+ // Get threadsafe some copies of used members.
+ /* SAFE { */
+ SolarMutexClearableGuard aReadLock;
+ css::uno::Reference< css::awt::XWindow > xContainerWindow = m_xContainerWindow;
+ css::uno::Reference< css::awt::XWindow > xOldComponentWindow = m_xComponentWindow;
+ css::uno::Reference< css::frame::XController > xOldController = m_xController;
+ VclPtr<vcl::Window> pOwnWindow = VCLUnoHelper::GetWindow( xContainerWindow );
+ bool bHadFocus = pOwnWindow != nullptr && pOwnWindow->HasChildPathFocus();
+ bool bWasConnected = m_bConnected;
+ aReadLock.clear();
+ /* } SAFE */
+
+ // stop listening on old window
+ // May it produce some trouble.
+ // But don't forget to listen on new window again ... or reactivate listening
+ // if we reject this setComponent() request and leave this method without changing the old window.
+ implts_stopWindowListening();
+
+ // Notify all listener, that this component (if current one exist) will be unloaded.
+ if (bWasConnected)
+ implts_sendFrameActionEvent( css::frame::FrameAction_COMPONENT_DETACHING );
+
+ // otherwise release old component first
+ // Always release controller before releasing window,
+ // because controller may want to access its window!
+ // But check for real changes - may the new controller is the old one.
+ if (
+ (xOldController.is() ) &&
+ (xOldController != xController)
+ )
+ {
+ /* ATTENTION
+ Don't suspend the old controller here. Because the outside caller must do that
+ by definition. We have to dispose it here only.
+ */
+
+ // Before we dispose this controller we should hide it inside this frame instance.
+ // We hold it alive for next calls by using xOldController!
+ /* SAFE {*/
+ {
+ SolarMutexGuard aWriteLock;
+ m_xController = nullptr;
+
+ auto pInterceptionHelper = dynamic_cast<InterceptionHelper*>(m_xDispatchHelper.get());
+ if (pInterceptionHelper)
+ {
+ css::uno::Reference<css::frame::XDispatchProvider> xDispatchProvider = pInterceptionHelper->GetSlave();
+ auto pDispatchProvider = dynamic_cast<DispatchProvider*>(xDispatchProvider.get());
+ if (pDispatchProvider)
+ {
+ pDispatchProvider->ClearProtocolHandlers();
+ }
+ }
+ }
+ /* } SAFE */
+
+ if (xOldController.is())
+ {
+ try
+ {
+ xOldController->dispose();
+ }
+ catch(const css::lang::DisposedException&)
+ {}
+ }
+ xOldController = nullptr;
+ }
+
+ // Now it's time to release the component window.
+ // If controller wasn't released successfully - this code line shouldn't be reached.
+ // Because in case of "suspend()==false" we return immediately with false ...
+ // see before
+ // Check for real changes too.
+ if (
+ (xOldComponentWindow.is() ) &&
+ (xOldComponentWindow != xComponentWindow)
+ )
+ {
+ /* SAFE { */
+ {
+ SolarMutexGuard aWriteLock;
+ m_xComponentWindow = nullptr;
+ }
+ /* } SAFE */
+
+ if (xOldComponentWindow.is())
+ {
+ try
+ {
+ xOldComponentWindow->dispose();
+ }
+ catch(const css::lang::DisposedException&)
+ {
+ }
+ }
+ xOldComponentWindow = nullptr;
+ }
+
+ // Now it's time to set the new component ...
+ // By the way - find out our new "load state" - means if we have a valid component inside.
+ /* SAFE { */
+ SolarMutexResettableGuard aWriteLock;
+ m_xComponentWindow = xComponentWindow;
+ m_xController = xController;
+
+ // Clear the URL on the frame itself, now that the controller has it.
+ m_aURL.clear();
+
+ m_bConnected = (m_xComponentWindow.is() || m_xController.is());
+ bool bIsConnected = m_bConnected;
+ aWriteLock.clear();
+ /* } SAFE */
+
+ // notifies all interest listener, that current component was changed or a new one was loaded
+ if (bIsConnected && bWasConnected)
+ implts_sendFrameActionEvent( css::frame::FrameAction_COMPONENT_REATTACHED );
+ else if (bIsConnected && !bWasConnected)
+ implts_sendFrameActionEvent( css::frame::FrameAction_COMPONENT_ATTACHED );
+
+ // A new component window doesn't know anything about current active/focus states.
+ // Set this information on it!
+ if ( bHadFocus && xComponentWindow.is() )
+ {
+ xComponentWindow->setFocus();
+ }
+
+ // If it was a new component window - we must resize it to fill out
+ // our container window.
+ implts_resizeComponentWindow();
+ // New component should change our current icon ...
+ implts_setIconOnWindow();
+ // OK - start listening on new window again - or do nothing if it is an empty one.
+ implts_startWindowListening();
+
+ /* SAFE { */
+ aWriteLock.reset();
+ impl_checkMenuCloser();
+ aWriteLock.clear();
+ /* } SAFE */
+
+ return true;
+}
+
+/*-****************************************************************************************************
+ @short returns current set component window
+ @descr Frames are used to display components. The actual displayed component is
+ held by the m_xComponentWindow property. If the component implements only a
+ XComponent interface, the communication between the frame and the
+ component is very restricted. Better integration is achievable through a
+ XController interface.
+ If the component wants other objects to be able to get information about its
+ ResourceDescriptor it has to implement a XModel interface.
+ This frame is the owner of the component window.
+
+ @seealso method setComponent()
+ @return css::uno::Reference to current set component window.
+
+ @onerror A null reference is returned.
+*//*-*****************************************************************************************************/
+css::uno::Reference< css::awt::XWindow > SAL_CALL XFrameImpl::getComponentWindow()
+{
+ checkDisposed();
+ SolarMutexGuard g;
+ return m_xComponentWindow;
+}
+
+/*-****************************************************************************************************
+ @short returns current set controller
+ @descr Frames are used to display components. The actual displayed component is
+ held by the m_xComponentWindow property. If the component implements only a
+ XComponent interface, the communication between the frame and the
+ component is very restricted. Better integration is achievable through a
+ XController interface.
+ If the component wants other objects to be able to get information about its
+ ResourceDescriptor it has to implement a XModel interface.
+ This frame is the owner of the component window.
+
+ @seealso method setComponent()
+ @return css::uno::Reference to current set controller.
+
+ @onerror A null reference is returned.
+*//*-*****************************************************************************************************/
+css::uno::Reference< css::frame::XController > SAL_CALL XFrameImpl::getController()
+{
+ SolarMutexGuard g;
+ return m_xController;
+}
+
+/*-****************************************************************************************************
+ @short add/remove listener for activate/deactivate/contextChanged events
+ @seealso method activate()
+ @seealso method deactivate()
+ @seealso method contextChanged()
+
+ @param "xListener" reference to your listener object
+ @onerror Listener is ignored.
+*//*-*****************************************************************************************************/
+void SAL_CALL XFrameImpl::addFrameActionListener( const css::uno::Reference< css::frame::XFrameActionListener >& xListener )
+{
+ checkDisposed();
+ m_aListenerContainer.addInterface( cppu::UnoType<css::frame::XFrameActionListener>::get(), xListener );
+}
+
+void SAL_CALL XFrameImpl::removeFrameActionListener( const css::uno::Reference< css::frame::XFrameActionListener >& xListener )
+{
+ m_aListenerContainer.removeInterface( cppu::UnoType<css::frame::XFrameActionListener>::get(), xListener );
+}
+
+/*-****************************************************************************************************
+ @short support two way mechanism to release a frame
+ @descr This method ask internal component (controller) if he accept this close request.
+ In case of <TRUE/> nothing will be happen (from point of caller of this close method).
+ In case of <FALSE/> a CloseVetoException is thrown. After such exception given parameter
+ <var>bDeliverOwnership</var> regulate which will be the new owner of this instance.
+
+ @attention It's the replacement for XTask::close() which is marked as obsolete method.
+
+ @param bDeliverOwnership
+ If parameter is set to <FALSE/> the original caller will be the owner after thrown
+ veto exception and must try to close this frame at later time again. Otherwise the
+ source of thrown exception is the right one. May it will be the frame himself.
+
+ @throws CloseVetoException
+ if any internal things will not be closed
+
+ @threadsafe yes
+*//*-*****************************************************************************************************/
+void SAL_CALL XFrameImpl::close( sal_Bool bDeliverOwnership )
+{
+ checkDisposed();
+
+ // At the end of this method may we must dispose ourself...
+ // and may nobody from outside hold a reference to us...
+ // then it's a good idea to do that by ourself.
+ css::uno::Reference< css::uno::XInterface > xSelfHold( static_cast< ::cppu::OWeakObject* >(this) );
+
+ // Check any close listener before we look for currently running internal processes.
+ // Because if a listener disagree with this close() request - we have time to finish this
+ // internal operations too...
+ // Note: container is threadsafe himself.
+ css::lang::EventObject aSource (static_cast< ::cppu::OWeakObject*>(this));
+ comphelper::OInterfaceContainerHelper2* pContainer = m_aListenerContainer.getContainer( cppu::UnoType<css::util::XCloseListener>::get());
+ if (pContainer!=nullptr)
+ {
+ comphelper::OInterfaceIteratorHelper2 pIterator(*pContainer);
+ while (pIterator.hasMoreElements())
+ {
+ try
+ {
+ static_cast<css::util::XCloseListener*>(pIterator.next())->queryClosing( aSource, bDeliverOwnership );
+ }
+ catch( const css::uno::RuntimeException& )
+ {
+ pIterator.remove();
+ }
+ }
+ }
+
+ // Ok - no listener disagreed with this close() request
+ // check if this frame is used for any load process currently
+ if (isActionLocked())
+ {
+ if (bDeliverOwnership)
+ {
+ SolarMutexGuard g;
+ m_bSelfClose = true;
+ }
+
+ throw css::util::CloseVetoException("Frame in use for loading document...",static_cast< ::cppu::OWeakObject*>(this));
+ }
+
+ if ( ! setComponent(nullptr,nullptr) )
+ throw css::util::CloseVetoException("Component couldn't be detached...",static_cast< ::cppu::OWeakObject*>(this));
+
+ // If closing is allowed... inform all listeners and dispose this frame!
+ pContainer = m_aListenerContainer.getContainer( cppu::UnoType<css::util::XCloseListener>::get());
+ if (pContainer!=nullptr)
+ {
+ comphelper::OInterfaceIteratorHelper2 pIterator(*pContainer);
+ while (pIterator.hasMoreElements())
+ {
+ try
+ {
+ static_cast<css::util::XCloseListener*>(pIterator.next())->notifyClosing( aSource );
+ }
+ catch( const css::uno::RuntimeException& )
+ {
+ pIterator.remove();
+ }
+ }
+ }
+
+ /* SAFE { */
+ {
+ SolarMutexGuard aWriteLock;
+ m_bIsHidden = true;
+ }
+ /* } SAFE */
+ impl_checkMenuCloser();
+
+ dispose();
+}
+
+/*-****************************************************************************************************
+ @short be a listener for close events!
+ @descr Adds/remove a CloseListener at this frame instance. If the close() method is called on
+ this object, the such listener are informed and can disagree with that by throwing
+ a CloseVetoException.
+
+ @seealso XFrameImpl::close()
+
+ @param xListener
+ reference to your listener object
+
+ @onerror Listener is ignored.
+
+ @threadsafe yes
+*//*-*****************************************************************************************************/
+void SAL_CALL XFrameImpl::addCloseListener( const css::uno::Reference< css::util::XCloseListener >& xListener )
+{
+ checkDisposed();
+ m_aListenerContainer.addInterface( cppu::UnoType<css::util::XCloseListener>::get(), xListener );
+}
+
+void SAL_CALL XFrameImpl::removeCloseListener( const css::uno::Reference< css::util::XCloseListener >& xListener )
+{
+ m_aListenerContainer.removeInterface( cppu::UnoType<css::util::XCloseListener>::get(), xListener );
+}
+
+OUString SAL_CALL XFrameImpl::getTitle()
+{
+ checkDisposed();
+
+ // SAFE ->
+ SolarMutexClearableGuard aReadLock;
+ css::uno::Reference< css::frame::XTitle > xTitle(m_xTitleHelper, css::uno::UNO_SET_THROW);
+ aReadLock.clear();
+ // <- SAFE
+
+ return xTitle->getTitle();
+}
+
+void SAL_CALL XFrameImpl::setTitle( const OUString& sTitle )
+{
+ checkDisposed();
+
+ // SAFE ->
+ SolarMutexClearableGuard aReadLock;
+ css::uno::Reference< css::frame::XTitle > xTitle(m_xTitleHelper, css::uno::UNO_SET_THROW);
+ aReadLock.clear();
+ // <- SAFE
+
+ xTitle->setTitle(sTitle);
+}
+
+void SAL_CALL XFrameImpl::addTitleChangeListener( const css::uno::Reference< css::frame::XTitleChangeListener >& xListener)
+{
+ checkDisposed();
+
+ // SAFE ->
+ SolarMutexClearableGuard aReadLock;
+ css::uno::Reference< css::frame::XTitleChangeBroadcaster > xTitle(m_xTitleHelper, css::uno::UNO_QUERY_THROW);
+ aReadLock.clear();
+ // <- SAFE
+
+ xTitle->addTitleChangeListener(xListener);
+}
+
+void SAL_CALL XFrameImpl::removeTitleChangeListener( const css::uno::Reference< css::frame::XTitleChangeListener >& xListener )
+{
+ checkDisposed();
+
+ // SAFE ->
+ SolarMutexClearableGuard aReadLock;
+ css::uno::Reference< css::frame::XTitleChangeBroadcaster > xTitle(m_xTitleHelper, css::uno::UNO_QUERY_THROW);
+ aReadLock.clear();
+ // <- SAFE
+
+ xTitle->removeTitleChangeListener(xListener);
+}
+
+css::uno::Reference<css::container::XNameContainer> SAL_CALL XFrameImpl::getUserDefinedAttributes()
+{
+ // optional attribute
+ return nullptr;
+}
+
+css::uno::Reference<css::frame::XDispatchRecorderSupplier> SAL_CALL XFrameImpl::getDispatchRecorderSupplier()
+{
+ SolarMutexGuard g;
+ return m_xDispatchRecorderSupplier;
+}
+
+void SAL_CALL XFrameImpl::setDispatchRecorderSupplier(const css::uno::Reference<css::frame::XDispatchRecorderSupplier>& p)
+{
+ checkDisposed();
+ SolarMutexGuard g;
+ m_xDispatchRecorderSupplier.set(p);
+}
+
+css::uno::Reference<css::uno::XInterface> SAL_CALL XFrameImpl::getLayoutManager()
+{
+ SolarMutexGuard g;
+ return m_xLayoutManager;
+}
+
+void SAL_CALL XFrameImpl::setLayoutManager(const css::uno::Reference<css::uno::XInterface>& p1)
+{
+ checkDisposed();
+ SolarMutexGuard g;
+
+ css::uno::Reference<css::frame::XLayoutManager2> xOldLayoutManager = m_xLayoutManager;
+ css::uno::Reference<css::frame::XLayoutManager2> xNewLayoutManager(p1, css::uno::UNO_QUERY);
+
+ if (xOldLayoutManager != xNewLayoutManager)
+ {
+ m_xLayoutManager = xNewLayoutManager;
+ if (xOldLayoutManager.is())
+ disableLayoutManager(xOldLayoutManager);
+ if (xNewLayoutManager.is() && !m_bDocHidden)
+ lcl_enableLayoutManager(xNewLayoutManager, this);
+ }
+}
+
+css::uno::Reference< css::beans::XPropertySetInfo > SAL_CALL XFrameImpl::getPropertySetInfo()
+{
+ checkDisposed();
+ return css::uno::Reference< css::beans::XPropertySetInfo >(this);
+}
+
+void SAL_CALL XFrameImpl::setPropertyValue(const OUString& sProperty,
+ const css::uno::Any& aValue )
+{
+ // TODO look for e.g. readonly props and reject setProp() call!
+
+ checkDisposed();
+
+ // SAFE ->
+ SolarMutexGuard g;
+
+ TPropInfoHash::const_iterator pIt = m_lProps.find(sProperty);
+ if (pIt == m_lProps.end())
+ throw css::beans::UnknownPropertyException(sProperty);
+
+ css::beans::Property aPropInfo = pIt->second;
+
+ css::uno::Any aCurrentValue = impl_getPropertyValue(aPropInfo.Handle);
+
+ bool bWillBeChanged = (aCurrentValue != aValue);
+ if (! bWillBeChanged)
+ return;
+
+ css::beans::PropertyChangeEvent aEvent;
+ aEvent.PropertyName = aPropInfo.Name;
+ aEvent.Further = false;
+ aEvent.PropertyHandle = aPropInfo.Handle;
+ aEvent.OldValue = aCurrentValue;
+ aEvent.NewValue = aValue;
+ aEvent.Source.set(m_xBroadcaster.get(), css::uno::UNO_QUERY);
+
+ if (impl_existsVeto(aEvent))
+ throw css::beans::PropertyVetoException();
+
+ impl_setPropertyValue(aPropInfo.Handle, aValue);
+
+ impl_notifyChangeListener(aEvent);
+}
+
+css::uno::Any SAL_CALL XFrameImpl::getPropertyValue(const OUString& sProperty)
+{
+ checkDisposed();
+
+ // SAFE ->
+ SolarMutexGuard aReadLock;
+
+ TPropInfoHash::const_iterator pIt = m_lProps.find(sProperty);
+ if (pIt == m_lProps.end())
+ throw css::beans::UnknownPropertyException(sProperty);
+
+ css::beans::Property aPropInfo = pIt->second;
+
+ return impl_getPropertyValue(aPropInfo.Handle);
+}
+
+void SAL_CALL XFrameImpl::addPropertyChangeListener(
+ const OUString& sProperty,
+ const css::uno::Reference< css::beans::XPropertyChangeListener >& xListener)
+{
+ checkDisposed();
+
+ // SAFE ->
+ {
+ SolarMutexGuard aReadLock;
+
+ TPropInfoHash::const_iterator pIt = m_lProps.find(sProperty);
+ if (pIt == m_lProps.end())
+ throw css::beans::UnknownPropertyException(sProperty);
+ }
+ // <- SAFE
+
+ m_lSimpleChangeListener.addInterface(sProperty, xListener);
+}
+
+void SAL_CALL XFrameImpl::removePropertyChangeListener(
+ const OUString& sProperty,
+ const css::uno::Reference< css::beans::XPropertyChangeListener >& xListener)
+{
+ // SAFE ->
+ {
+ SolarMutexGuard aReadLock;
+
+ TPropInfoHash::const_iterator pIt = m_lProps.find(sProperty);
+ if (pIt == m_lProps.end())
+ throw css::beans::UnknownPropertyException(sProperty);
+ }
+ // <- SAFE
+
+ m_lSimpleChangeListener.removeInterface(sProperty, xListener);
+}
+
+void SAL_CALL XFrameImpl::addVetoableChangeListener(
+ const OUString& sProperty,
+ const css::uno::Reference< css::beans::XVetoableChangeListener >& xListener)
+{
+ checkDisposed();
+
+ // SAFE ->
+ {
+ SolarMutexGuard aReadLock;
+
+ TPropInfoHash::const_iterator pIt = m_lProps.find(sProperty);
+ if (pIt == m_lProps.end())
+ throw css::beans::UnknownPropertyException(sProperty);
+ }
+ // <- SAFE
+
+ m_lVetoChangeListener.addInterface(sProperty, xListener);
+}
+
+void SAL_CALL XFrameImpl::removeVetoableChangeListener(
+ const OUString& sProperty,
+ const css::uno::Reference< css::beans::XVetoableChangeListener >& xListener)
+{
+ // SAFE ->
+ {
+ SolarMutexGuard aReadLock;
+
+ TPropInfoHash::const_iterator pIt = m_lProps.find(sProperty);
+ if (pIt == m_lProps.end())
+ throw css::beans::UnknownPropertyException(sProperty);
+ }
+ // <- SAFE
+
+ m_lVetoChangeListener.removeInterface(sProperty, xListener);
+}
+
+css::uno::Sequence< css::beans::Property > SAL_CALL XFrameImpl::getProperties()
+{
+ checkDisposed();
+
+ SolarMutexGuard g;
+
+ sal_Int32 c = static_cast<sal_Int32>(m_lProps.size());
+ css::uno::Sequence< css::beans::Property > lProps(c);
+ auto lPropsRange = asNonConstRange(lProps);
+ for (auto const& elem : m_lProps)
+ {
+ lPropsRange[--c] = elem.second;
+ }
+
+ return lProps;
+}
+
+css::beans::Property SAL_CALL XFrameImpl::getPropertyByName(const OUString& sName)
+{
+ checkDisposed();
+
+ SolarMutexGuard g;
+
+ TPropInfoHash::const_iterator pIt = m_lProps.find(sName);
+ if (pIt == m_lProps.end())
+ throw css::beans::UnknownPropertyException(sName);
+
+ return pIt->second;
+}
+
+sal_Bool SAL_CALL XFrameImpl::hasPropertyByName(const OUString& sName)
+{
+ checkDisposed();
+
+ SolarMutexGuard g;
+
+ TPropInfoHash::iterator pIt = m_lProps.find(sName);
+ bool bExist = (pIt != m_lProps.end());
+
+ return bExist;
+}
+
+/*-****************************************************************************************************/
+void XFrameImpl::implts_forgetSubFrames()
+{
+ // SAFE ->
+ SolarMutexClearableGuard aReadLock;
+ css::uno::Reference< css::container::XIndexAccess > xContainer(m_xFramesHelper, css::uno::UNO_QUERY_THROW);
+ aReadLock.clear();
+ // <- SAFE
+
+ sal_Int32 c = xContainer->getCount();
+ sal_Int32 i = 0;
+
+ for (i=0; i<c; ++i)
+ {
+ try
+ {
+ css::uno::Reference< css::frame::XFrame > xFrame;
+ xContainer->getByIndex(i) >>= xFrame;
+ if (xFrame.is())
+ xFrame->setCreator(css::uno::Reference< css::frame::XFramesSupplier >());
+ }
+ catch(const css::uno::Exception&)
+ {
+ // Ignore errors here.
+ // Nobody can guarantee a stable index in multi threaded environments .-)
+ }
+ }
+
+ SolarMutexGuard g;
+ m_xFramesHelper.clear(); // clear uno reference
+ m_aChildFrameContainer.clear(); // clear container content
+}
+
+/*-****************************************************************************************************
+ @short destroy instance
+ @descr The owner of this object calls the dispose method if the object
+ should be destroyed. All other objects and components, that are registered
+ as an EventListener are forced to release their references to this object.
+ Furthermore this frame is removed from its parent frame container to release
+ this reference. The reference attributes are disposed and released also.
+
+ @attention Look for globale description at beginning of file too!
+ (DisposedException, FairRWLock ..., initialize, dispose)
+
+ @seealso method initialize()
+ @seealso baseclass FairRWLockBase!
+*//*-*****************************************************************************************************/
+void SAL_CALL XFrameImpl::disposing()
+{
+ // We should hold a reference to ourself ...
+ // because our owner dispose us and release our reference ...
+ // May be we will die before we could finish this method ...
+ css::uno::Reference< css::frame::XFrame > xThis(this);
+
+ SAL_INFO("fwk.frame", "[Frame] " << m_sName << " send dispose event to listener");
+
+ // First operation should be... "stop all listening for window events on our container window".
+ // These events are superfluous but can make trouble!
+ // We will die, die and die...
+ implts_stopWindowListening();
+
+ css::uno::Reference<css::frame::XLayoutManager2> layoutMgr;
+ {
+ SolarMutexGuard g;
+ layoutMgr = m_xLayoutManager;
+ }
+ if (layoutMgr.is()) {
+ disableLayoutManager(layoutMgr);
+ }
+
+ std::unique_ptr<WindowCommandDispatch> disp;
+ {
+ SolarMutexGuard g;
+ std::swap(disp, m_pWindowCommandDispatch);
+ }
+ disp.reset();
+
+ // Send message to all listener and forget her references.
+ css::lang::EventObject aEvent( xThis );
+ m_aListenerContainer.disposeAndClear( aEvent );
+
+ // set "end of live" for our property set helper
+ impl_disablePropertySet();
+
+ // interception/dispatch chain must be destructed explicitly
+ // Otherwise some dispatches and/or interception objects won't die.
+ css::uno::Reference< css::lang::XEventListener > xDispatchHelper;
+ {
+ SolarMutexGuard g;
+ xDispatchHelper.set(m_xDispatchHelper, css::uno::UNO_QUERY_THROW);
+ }
+ xDispatchHelper->disposing(aEvent);
+ xDispatchHelper.clear();
+
+ // Don't show any dialogs, errors or something else any more!
+ // If somewhere called dispose() without close() before - normally no dialogs
+ // should exist. Otherwise it's the problem of the outside caller.
+ // Note:
+ // (a) Do it after stopWindowListening(). May that force some active/deactivate
+ // notifications which we don't need here really.
+ // (b) Don't forget to save the old value of IsDialogCancelEnabled() to
+ // restore it afterwards (to not kill headless mode).
+ DialogCancelMode old = Application::GetDialogCancelMode();
+ Application::SetDialogCancelMode( DialogCancelMode::Silent );
+
+ // We should be alone for ever and further dispose calls are rejected by lines before ...
+ // I hope it :-)
+
+ // Free references of our frame tree.
+ // Force parent container to forget this frame too ...
+ // ( It's contained in m_xParent and so no css::lang::XEventListener for m_xParent! )
+ // It's important to do that before we free some other internal structures.
+ // Because if our parent gets an activate and found us as last possible active frame
+ // he try to deactivate us ... and we run into some trouble (DisposedExceptions!).
+ css::uno::Reference<css::frame::XFramesSupplier> parent;
+ {
+ SolarMutexGuard g;
+ std::swap(parent, m_xParent);
+ }
+ if( parent.is() )
+ {
+ parent->getFrames()->remove( xThis );
+ }
+
+ /* } SAFE */
+ // Forget our internal component and her window first.
+ // So we can release our container window later without problems.
+ // Because this container window is the parent of the component window ...
+ // Note: Dispose it hard - because suspending must be done inside close() call!
+ // But try to dispose the controller first before you destroy the window.
+ // Because the window is used by the controller too ...
+ css::uno::Reference< css::lang::XComponent > xDisposableCtrl;
+ css::uno::Reference< css::lang::XComponent > xDisposableComp;
+ {
+ SolarMutexGuard g;
+ xDisposableCtrl = m_xController;
+ xDisposableComp = m_xComponentWindow;
+ }
+ if (xDisposableCtrl.is())
+ xDisposableCtrl->dispose();
+ if (xDisposableComp.is())
+ xDisposableComp->dispose();
+
+ impl_checkMenuCloser();
+
+ css::uno::Reference<css::awt::XWindow> contWin;
+ {
+ SolarMutexGuard g;
+ std::swap(contWin, m_xContainerWindow);
+ }
+ if( contWin.is() )
+ {
+ contWin->setVisible( false );
+ // All VclComponents are XComponents; so call dispose before discarding
+ // a css::uno::Reference< XVclComponent >, because this frame is the owner of the window
+ contWin->dispose();
+ }
+
+ /*ATTENTION
+ Clear container after successful removing from parent container ...
+ because our parent could be the desktop which stand in dispose too!
+ If we have already cleared our own container we lost our child before this could be
+ remove himself at this instance ...
+ Release m_xFramesHelper after that ... it's the same problem between parent and child!
+ "m_xParent->getFrames()->remove( xThis );" needs this helper ...
+ Otherwise we get a null reference and could finish removing successfully.
+ => You see: Order of calling operations is important!!!
+ */
+ implts_forgetSubFrames();
+
+ {
+ SolarMutexGuard g;
+
+ // Release some other references.
+ // This calls should be easy ... I hope it :-)
+ m_xDispatchHelper.clear();
+ m_xDropTargetListener.clear();
+ m_xDispatchRecorderSupplier.clear();
+ m_xLayoutManager.clear();
+ m_xIndicatorFactoryHelper.clear();
+
+ // It's important to set default values here!
+ // If may be later somewhere change the disposed-behaviour of this implementation
+ // and doesn't throw any DisposedExceptions we must guarantee best matching default values ...
+ m_eActiveState = E_INACTIVE;
+ m_sName.clear();
+ m_bIsFrameTop = false;
+ m_bConnected = false;
+ m_nExternalLockCount = 0;
+ m_bSelfClose = false;
+ m_bIsHidden = true;
+ }
+
+ // Don't forget it restore old value -
+ // otherwise no dialogs can be shown anymore in other frames.
+ Application::SetDialogCancelMode( old );
+}
+
+/*-****************************************************************************************************
+ @short Be a listener for dispose events!
+ @descr Adds/remove an EventListener to this object. If the dispose method is called on
+ this object, the disposing method of the listener is called.
+ @param "xListener" reference to your listener object.
+ @onerror Listener is ignored.
+*//*-*****************************************************************************************************/
+void SAL_CALL XFrameImpl::addEventListener( const css::uno::Reference< css::lang::XEventListener >& xListener )
+{
+ checkDisposed();
+ m_aListenerContainer.addInterface( cppu::UnoType<css::lang::XEventListener>::get(), xListener );
+}
+
+void SAL_CALL XFrameImpl::removeEventListener( const css::uno::Reference< css::lang::XEventListener >& xListener )
+{
+ m_aListenerContainer.removeInterface( cppu::UnoType<css::lang::XEventListener>::get(), xListener );
+}
+
+/*-****************************************************************************************************
+ @short create new status indicator
+ @descr Use returned status indicator to show progresses and some text information.
+ All created objects share the same dialog! Only the last one can show his information.
+
+ @seealso class StatusIndicatorFactory
+ @seealso class StatusIndicator
+ @return A reference to created object.
+
+ @onerror We return a null reference.
+*//*-*****************************************************************************************************/
+css::uno::Reference< css::task::XStatusIndicator > SAL_CALL XFrameImpl::createStatusIndicator()
+{
+ checkDisposed();
+
+ /* SAFE AREA ----------------------------------------------------------------------------------------------- */
+ SolarMutexClearableGuard aReadLock;
+
+ // Make snapshot of necessary member and define default return value.
+ css::uno::Reference< css::task::XStatusIndicator > xExternal(m_xIndicatorInterception.get(), css::uno::UNO_QUERY);
+ css::uno::Reference< css::task::XStatusIndicatorFactory > xFactory = m_xIndicatorFactoryHelper;
+
+ aReadLock.clear();
+ /* UNSAFE AREA ----------------------------------------------------------------------------------------- */
+
+ // Was set from outside to intercept any progress activities!
+ if (xExternal.is())
+ return xExternal;
+
+ // Or use our own factory as fallback, to create such progress.
+ if (xFactory.is())
+ return xFactory->createStatusIndicator();
+
+ return css::uno::Reference< css::task::XStatusIndicator >();
+}
+
+/*-****************************************************************************************************
+ @short search for target to load URL
+ @descr This method searches for a dispatch for the specified DispatchDescriptor.
+ The FrameSearchFlags and the FrameName of the DispatchDescriptor are
+ treated as described for findFrame.
+
+ @seealso method findFrame()
+ @seealso method queryDispatches()
+ @seealso method set/getName()
+ @seealso class TargetFinder
+
+ @param "aURL" , URL for loading
+ @param "sTargetFrameName" , name of target frame
+ @param "nSearchFlags" , additional flags to regulate search if sTargetFrameName is not clear
+ @return css::uno::Reference to dispatch handler.
+
+ @onerror A null reference is returned.
+*//*-*****************************************************************************************************/
+css::uno::Reference< css::frame::XDispatch > SAL_CALL XFrameImpl::queryDispatch( const css::util::URL& aURL,
+ const OUString& sTargetFrameName,
+ sal_Int32 nSearchFlags)
+{
+ // Don't check incoming parameter here! Our helper do it for us and it is not a good idea to do it more than once!
+
+ checkDisposed();
+
+ // Remove uno and cmd protocol part as we want to support both of them. We store only the command part
+ // in our hash map. All other protocols are stored with the protocol part.
+ OUString aCommand( aURL.Main );
+ if ( aURL.Protocol.equalsIgnoreAsciiCase(".uno:") )
+ aCommand = aURL.Path;
+
+ // Make std::unordered_map lookup if the current URL is in the disabled list
+ if ( m_aCommandOptions.Lookup( SvtCommandOptions::CMDOPTION_DISABLED, aCommand ) )
+ return css::uno::Reference< css::frame::XDispatch >();
+ else
+ {
+ // We use a helper to support these interface and an interceptor mechanism.
+ css::uno::Reference<css::frame::XDispatchProvider> disp;
+ {
+ SolarMutexGuard g;
+ disp = m_xDispatchHelper;
+ }
+ if (!disp.is()) {
+ throw css::lang::DisposedException("Frame disposed");
+ }
+ return disp->queryDispatch( aURL, sTargetFrameName, nSearchFlags );
+ }
+}
+
+/*-****************************************************************************************************
+ @short handle more than ones dispatches at same call
+ @descr Returns a sequence of dispatches. For details see the queryDispatch method.
+ For failed dispatches we return empty items in list!
+
+ @seealso method queryDispatch()
+
+ @param "lDescriptor" list of dispatch arguments for queryDispatch()!
+ @return List of dispatch references. Some elements can be NULL!
+
+ @onerror An empty list is returned.
+*//*-*****************************************************************************************************/
+css::uno::Sequence< css::uno::Reference< css::frame::XDispatch > > SAL_CALL XFrameImpl::queryDispatches(
+ const css::uno::Sequence< css::frame::DispatchDescriptor >& lDescriptor )
+{
+ // Don't check incoming parameter here! Our helper do it for us and it is not a good idea to do it more than ones!
+
+ checkDisposed();
+
+ // We use a helper to support these interface and an interceptor mechanism.
+ css::uno::Reference<css::frame::XDispatchProvider> disp;
+ {
+ SolarMutexGuard g;
+ disp = m_xDispatchHelper;
+ }
+ if (!disp.is()) {
+ throw css::lang::DisposedException("Frame disposed");
+ }
+ return disp->queryDispatches( lDescriptor );
+}
+
+/*-****************************************************************************************************
+ @short register/unregister interceptor for dispatch calls
+ @descr If you wish to handle some dispatches by himself ... you should be
+ an interceptor for it. Please see class OInterceptionHelper for further information.
+
+ @seealso class OInterceptionHelper
+
+ @param "xInterceptor", reference to your interceptor implementation.
+ @onerror Interceptor is ignored.
+*//*-*****************************************************************************************************/
+void SAL_CALL XFrameImpl::registerDispatchProviderInterceptor(
+ const css::uno::Reference< css::frame::XDispatchProviderInterceptor >& xInterceptor )
+{
+ // We use a helper to support these interface and an interceptor mechanism.
+ // This helper is threadsafe himself and check incoming parameter too.
+ // I think we don't need any lock here!
+
+ checkDisposed();
+
+ css::uno::Reference< css::frame::XDispatchProviderInterception > xInterceptionHelper;
+ {
+ SolarMutexGuard g;
+ xInterceptionHelper.set( m_xDispatchHelper, css::uno::UNO_QUERY );
+ }
+ if (xInterceptionHelper.is()) {
+ xInterceptionHelper->registerDispatchProviderInterceptor( xInterceptor );
+ }
+}
+
+void SAL_CALL XFrameImpl::releaseDispatchProviderInterceptor(
+ const css::uno::Reference< css::frame::XDispatchProviderInterceptor >& xInterceptor )
+{
+ // We use a helper to support these interface and an interceptor mechanism.
+ // This helper is threadsafe himself and check incoming parameter too.
+ // I think we don't need any lock here!
+
+ // Sometimes we are called during our dispose() method
+
+ css::uno::Reference< css::frame::XDispatchProviderInterception > xInterceptionHelper;
+ {
+ SolarMutexGuard g;
+ xInterceptionHelper.set( m_xDispatchHelper, css::uno::UNO_QUERY );
+ }
+ if (xInterceptionHelper.is()) {
+ xInterceptionHelper->releaseDispatchProviderInterceptor( xInterceptor );
+ }
+}
+
+/*-****************************************************************************************************
+ @short provides information about all possible dispatch functions
+ inside the current frame environment
+*//*-*****************************************************************************************************/
+css::uno::Sequence< sal_Int16 > SAL_CALL XFrameImpl::getSupportedCommandGroups()
+{
+ return m_xDispatchInfoHelper->getSupportedCommandGroups();
+}
+
+css::uno::Sequence< css::frame::DispatchInformation > SAL_CALL XFrameImpl::getConfigurableDispatchInformation(
+ sal_Int16 nCommandGroup)
+{
+ return m_xDispatchInfoHelper->getConfigurableDispatchInformation(nCommandGroup);
+}
+
+/*-****************************************************************************************************
+ @short notifications for window events
+ @descr We are a listener on our container window to forward it to our component window.
+
+ @seealso method setComponent()
+ @seealso member m_xContainerWindow
+ @seealso member m_xComponentWindow
+
+ @param "aEvent" describe source of detected event
+*//*-*****************************************************************************************************/
+void SAL_CALL XFrameImpl::windowResized( const css::awt::WindowEvent& )
+{
+ // Part of dispose-mechanism
+
+ /* SAFE AREA ----------------------------------------------------------------------------------------------- */
+ // Impl-method is threadsafe!
+ // If we have a current component window - we must resize it!
+ implts_resizeComponentWindow();
+}
+
+void SAL_CALL XFrameImpl::focusGained( const css::awt::FocusEvent& )
+{
+ // Part of dispose() mechanism
+
+ /* SAFE AREA ----------------------------------------------------------------------------------------------- */
+ SolarMutexClearableGuard aReadLock;
+ // Make snapshot of member!
+ css::uno::Reference< css::awt::XWindow > xComponentWindow = m_xComponentWindow;
+ aReadLock.clear();
+ /* UNSAFE AREA --------------------------------------------------------------------------------------------- */
+
+ if( xComponentWindow.is() )
+ {
+ xComponentWindow->setFocus();
+ }
+}
+
+/*-****************************************************************************************************
+ @short notifications for window events
+ @descr We are a listener on our container window to forward it to our component window ...
+ but a XTopWindowListener we are only if we are a top frame!
+
+ @seealso method setComponent()
+ @seealso member m_xContainerWindow
+ @seealso member m_xComponentWindow
+
+ @param "aEvent" describe source of detected event
+*//*-*****************************************************************************************************/
+void SAL_CALL XFrameImpl::windowActivated( const css::lang::EventObject& )
+{
+ checkDisposed();
+
+ /* SAFE AREA ----------------------------------------------------------------------------------------------- */
+ SolarMutexClearableGuard aReadLock;
+ // Make snapshot of member!
+ EActiveState eState = m_eActiveState;
+ aReadLock.clear();
+ /* UNSAFE AREA --------------------------------------------------------------------------------------------- */
+ // Activate the new active path from here to top.
+ if( eState == E_INACTIVE )
+ {
+ setActiveFrame( css::uno::Reference< css::frame::XFrame >() );
+ activate();
+ }
+}
+
+void SAL_CALL XFrameImpl::windowDeactivated( const css::lang::EventObject& )
+{
+ // Sometimes called during dispose()
+
+ /* SAFE AREA ----------------------------------------------------------------------------------------------- */
+ SolarMutexClearableGuard aReadLock;
+
+ css::uno::Reference< css::frame::XFrame > xParent = m_xParent;
+ css::uno::Reference< css::awt::XWindow > xContainerWindow = m_xContainerWindow;
+ EActiveState eActiveState = m_eActiveState;
+
+ aReadLock.clear();
+
+ if( eActiveState == E_INACTIVE )
+ return;
+
+ // Deactivation is always done implicitly by activation of another frame.
+ // Only if no activation is done, deactivations have to be processed if the activated window
+ // is a parent window of the last active Window!
+ SolarMutexClearableGuard aSolarGuard;
+ vcl::Window* pFocusWindow = Application::GetFocusWindow();
+ if ( !xContainerWindow.is() || !xParent.is() ||
+ css::uno::Reference< css::frame::XDesktop >( xParent, css::uno::UNO_QUERY ).is()
+ )
+ return;
+
+ css::uno::Reference< css::awt::XWindow > xParentWindow = xParent->getContainerWindow();
+ VclPtr<vcl::Window> pParentWindow = VCLUnoHelper::GetWindow( xParentWindow );
+ //#i70261#: dialogs opened from an OLE object will cause a deactivate on the frame of the OLE object
+ // on Solaris/Linux at that time pFocusWindow is still NULL because the focus handling is different; right after
+ // the deactivation the focus will be set into the dialog!
+ // currently I see no case where a sub frame could get a deactivate with pFocusWindow being NULL permanently
+ // so for now this case is omitted from handled deactivations
+ if( pFocusWindow && pParentWindow->IsChild( pFocusWindow ) )
+ {
+ css::uno::Reference< css::frame::XFramesSupplier > xSupplier( xParent, css::uno::UNO_QUERY );
+ if( xSupplier.is() )
+ {
+ aSolarGuard.clear();
+ xSupplier->setActiveFrame( css::uno::Reference< css::frame::XFrame >() );
+ }
+ }
+}
+
+void SAL_CALL XFrameImpl::windowClosing( const css::lang::EventObject& )
+{
+ checkDisposed();
+
+ // deactivate this frame ...
+ deactivate();
+
+ // ... and try to close it
+ // But do it asynchronous inside the main thread.
+ // VCL has no fun to do such things outside his main thread :-(
+ // Note: The used dispatch make it asynchronous for us .-)
+
+ /*ATTENTION!
+ Don't try to suspend the controller here! Because it's done inside used dispatch().
+ Otherwise the dialog "would you save your changes?" will be shown more than once ...
+ */
+
+ css::util::URL aURL;
+ aURL.Complete = ".uno:CloseFrame";
+ css::uno::Reference< css::util::XURLTransformer > xParser(css::util::URLTransformer::create(m_xContext));
+ xParser->parseStrict(aURL);
+
+ css::uno::Reference< css::frame::XDispatch > xCloser = queryDispatch(aURL, SPECIALTARGET_SELF, 0);
+ if (xCloser.is())
+ xCloser->dispatch(aURL, css::uno::Sequence< css::beans::PropertyValue >());
+
+ // Attention: If this dispatch works synchronous ... and fulfill its job ...
+ // this line of code will never be reached ...
+ // Or if it will be reached it will be for sure that all your member are gone .-)
+}
+
+/*-****************************************************************************************************
+ @short react for a show event for the internal container window
+ @descr Normally we don't need this information really. But we can use it to
+ implement the special feature "trigger first visible task".
+
+ Algorithm: - first we have to check if we are a top (task) frame
+ It's not enough to be a top frame! Because we MUST have the desktop as parent.
+ But frames without a parent are top too. So it's not possible to check isTop() here!
+ We have to look for the type of our parent.
+ - if we are a task frame, then we have to check if we are the first one.
+ We use a static variable to do so. They will be reset to afterwards be sure
+ that further calls of this method doesn't do anything then.
+ - Then we have to trigger the right event string on the global job executor.
+
+ @seealso css::task::JobExecutor
+
+ @param aEvent
+ describes the source of this event
+ We are not interested on this information. We are interested on the visible state only.
+
+ @threadsafe yes
+*//*-*****************************************************************************************************/
+void SAL_CALL XFrameImpl::windowShown( const css::lang::EventObject& )
+{
+ static std::mutex aFirstVisibleLock;
+
+ /* SAFE { */
+ SolarMutexClearableGuard aReadLock;
+ css::uno::Reference< css::frame::XDesktop > xDesktopCheck( m_xParent, css::uno::UNO_QUERY );
+ m_bIsHidden = false;
+ aReadLock.clear();
+ /* } SAFE */
+
+ impl_checkMenuCloser();
+
+ if (!xDesktopCheck.is())
+ return;
+
+ static bool bFirstVisibleTask = true;
+ std::unique_lock aGuard(aFirstVisibleLock);
+ bool bMustBeTriggered = bFirstVisibleTask;
+ bFirstVisibleTask = false;
+ aGuard.unlock();
+
+ if (bMustBeTriggered)
+ {
+ css::uno::Reference< css::task::XJobExecutor > xExecutor
+ = css::task::theJobExecutor::get( m_xContext );
+ xExecutor->trigger( "onFirstVisibleTask" );
+ }
+}
+
+void SAL_CALL XFrameImpl::windowHidden( const css::lang::EventObject& )
+{
+ /* SAFE { */
+ {
+ SolarMutexGuard aReadLock;
+ m_bIsHidden = true;
+ }
+ /* } SAFE */
+
+ impl_checkMenuCloser();
+}
+
+/*-****************************************************************************************************
+ @short called by dispose of our windows!
+ @descr This object is forced to release all references to the interfaces given
+ by the parameter source. We are a listener at our container window and
+ should listen for his disposing.
+
+ @seealso XWindowListener
+ @seealso XTopWindowListener
+ @seealso XFocusListener
+*//*-*****************************************************************************************************/
+void SAL_CALL XFrameImpl::disposing( const css::lang::EventObject& aEvent )
+{
+ /* SAFE AREA ----------------------------------------------------------------------------------------------- */
+ SolarMutexResettableGuard aWriteLock;
+
+ if( aEvent.Source == m_xContainerWindow )
+ {
+ // NECESSARY: Impl-method is threadsafe by himself!
+ aWriteLock.clear();
+ implts_stopWindowListening();
+ aWriteLock.reset();
+ m_xContainerWindow.clear();
+ }
+}
+
+/*-************************************************************************************************************
+ @interface com.sun.star.document.XActionLockable
+ @short implement locking of frame/task from outside
+ @descr Sometimes we have problems to decide if closing of task is allowed. Because; frame/task
+ could be used for pending loading jobs. So you can lock this object from outside and
+ prevent instance against closing during using! But - don't do it in a wrong or expensive manner.
+ Otherwise task couldn't die anymore!!!
+
+ @seealso interface XActionLockable
+ @seealso method BaseDispatcher::implts_loadIt()
+ @seealso method Desktop::loadComponentFromURL()
+ @return true if frame/task is locked
+ false otherwise
+ @threadsafe yes
+*//*-*************************************************************************************************************/
+sal_Bool SAL_CALL XFrameImpl::isActionLocked()
+{
+ SolarMutexGuard g;
+ return( m_nExternalLockCount!=0);
+}
+
+void SAL_CALL XFrameImpl::addActionLock()
+{
+ SolarMutexGuard g;
+ ++m_nExternalLockCount;
+}
+
+void SAL_CALL XFrameImpl::removeActionLock()
+{
+ {
+ SolarMutexGuard g;
+ SAL_WARN_IF( m_nExternalLockCount<=0, "fwk.frame", "XFrameImpl::removeActionLock(): Frame is not locked! "
+ "Possible multithreading problem detected." );
+ --m_nExternalLockCount;
+ }
+
+ implts_checkSuicide();
+}
+
+void SAL_CALL XFrameImpl::setActionLocks( sal_Int16 nLock )
+{
+ SolarMutexGuard g;
+ // Attention: If somewhere called resetActionLocks() before and get e.g. 5 locks ...
+ // and tried to set these 5 ones here after his operations ...
+ // we can't ignore set requests during these two calls!
+ // So we must add(!) these 5 locks here.
+ m_nExternalLockCount = m_nExternalLockCount + nLock;
+}
+
+sal_Int16 SAL_CALL XFrameImpl::resetActionLocks()
+{
+ sal_Int16 nCurrentLocks = 0;
+ {
+ SolarMutexGuard g;
+ nCurrentLocks = m_nExternalLockCount;
+ m_nExternalLockCount = 0;
+ }
+
+ // Attention:
+ // external lock count is 0 here every time... but if
+ // member m_bSelfClose is set to true too... we call our own close()/dispose().
+ // See close() for further information
+ implts_checkSuicide();
+
+ return nCurrentLocks;
+}
+
+void XFrameImpl::impl_setPropertyValue(sal_Int32 nHandle,
+ const css::uno::Any& aValue)
+
+{
+ /* There is no need to lock any mutex here. Because we share the
+ solar mutex with our base class. And we said to our base class: "don't release it on calling us" .-)
+ */
+
+ /* Attention: You can use nHandle only, if you are sure that all supported
+ properties has a unique handle. That must be guaranteed
+ inside method initListeners()!
+ */
+ switch (nHandle)
+ {
+ case FRAME_PROPHANDLE_TITLE :
+ {
+ OUString sExternalTitle;
+ aValue >>= sExternalTitle;
+ setTitle (sExternalTitle);
+ }
+ break;
+
+ case FRAME_PROPHANDLE_DISPATCHRECORDERSUPPLIER :
+ aValue >>= m_xDispatchRecorderSupplier;
+ break;
+
+ case FRAME_PROPHANDLE_LAYOUTMANAGER :
+ {
+ css::uno::Reference< css::frame::XLayoutManager2 > xOldLayoutManager = m_xLayoutManager;
+ css::uno::Reference< css::frame::XLayoutManager2 > xNewLayoutManager;
+ aValue >>= xNewLayoutManager;
+
+ if (xOldLayoutManager != xNewLayoutManager)
+ {
+ m_xLayoutManager = xNewLayoutManager;
+ if (xOldLayoutManager.is())
+ disableLayoutManager(xOldLayoutManager);
+ if (xNewLayoutManager.is() && !m_bDocHidden)
+ lcl_enableLayoutManager(xNewLayoutManager, this);
+ }
+ }
+ break;
+
+ case FRAME_PROPHANDLE_INDICATORINTERCEPTION :
+ {
+ css::uno::Reference< css::task::XStatusIndicator > xProgress;
+ aValue >>= xProgress;
+ m_xIndicatorInterception = xProgress;
+ }
+ break;
+
+ case FRAME_PROPHANDLE_URL:
+ {
+ aValue >>= m_aURL;
+ }
+ break;
+ default :
+ SAL_INFO("fwk.frame", "XFrameImpl::setFastPropertyValue_NoBroadcast(): Invalid handle detected!" );
+ break;
+ }
+}
+
+css::uno::Any XFrameImpl::impl_getPropertyValue(sal_Int32 nHandle)
+{
+ /* There is no need to lock any mutex here. Because we share the
+ solar mutex with our base class. And we said to our base class: "don't release it on calling us" .-)
+ */
+
+ /* Attention: You can use nHandle only, if you are sure that all supported
+ properties has a unique handle. That must be guaranteed
+ inside method initListeners()!
+ */
+ css::uno::Any aValue;
+ switch (nHandle)
+ {
+ case FRAME_PROPHANDLE_TITLE :
+ aValue <<= getTitle ();
+ break;
+
+ case FRAME_PROPHANDLE_DISPATCHRECORDERSUPPLIER :
+ aValue <<= m_xDispatchRecorderSupplier;
+ break;
+
+ case FRAME_PROPHANDLE_ISHIDDEN :
+ aValue <<= m_bIsHidden;
+ break;
+
+ case FRAME_PROPHANDLE_LAYOUTMANAGER :
+ aValue <<= m_xLayoutManager;
+ break;
+
+ case FRAME_PROPHANDLE_INDICATORINTERCEPTION :
+ {
+ css::uno::Reference< css::task::XStatusIndicator > xProgress(m_xIndicatorInterception.get(),
+ css::uno::UNO_QUERY);
+ aValue <<= xProgress;
+ }
+ break;
+
+ case FRAME_PROPHANDLE_URL:
+ {
+ aValue <<= m_aURL;
+ }
+ break;
+ default :
+ SAL_INFO("fwk.frame", "XFrameImpl::getFastPropertyValue(): Invalid handle detected!" );
+ break;
+ }
+
+ return aValue;
+}
+
+void XFrameImpl::impl_setPropertyChangeBroadcaster(const css::uno::Reference< css::uno::XInterface >& xBroadcaster)
+{
+ SolarMutexGuard g;
+ m_xBroadcaster = xBroadcaster;
+}
+
+void XFrameImpl::impl_addPropertyInfo(const css::beans::Property& aProperty)
+{
+ SolarMutexGuard g;
+
+ TPropInfoHash::const_iterator pIt = m_lProps.find(aProperty.Name);
+ if (pIt != m_lProps.end())
+ throw css::beans::PropertyExistException();
+
+ m_lProps[aProperty.Name] = aProperty;
+}
+
+void XFrameImpl::impl_disablePropertySet()
+{
+ SolarMutexGuard g;
+
+ css::uno::Reference< css::uno::XInterface > xThis(static_cast< css::beans::XPropertySet* >(this), css::uno::UNO_QUERY);
+ css::lang::EventObject aEvent(xThis);
+
+ m_lSimpleChangeListener.disposeAndClear(aEvent);
+ m_lVetoChangeListener.disposeAndClear(aEvent);
+ m_lProps.clear();
+}
+
+bool XFrameImpl::impl_existsVeto(const css::beans::PropertyChangeEvent& aEvent)
+{
+ /* Don't use the lock here!
+ The used helper is threadsafe and it lives for the whole lifetime of
+ our own object.
+ */
+ ::comphelper::OInterfaceContainerHelper3<css::beans::XVetoableChangeListener>* pVetoListener = m_lVetoChangeListener.getContainer(aEvent.PropertyName);
+ if (! pVetoListener)
+ return false;
+
+ ::comphelper::OInterfaceIteratorHelper3 pListener(*pVetoListener);
+ while (pListener.hasMoreElements())
+ {
+ try
+ {
+ pListener.next()->vetoableChange(aEvent);
+ }
+ catch(const css::uno::RuntimeException&)
+ { pListener.remove(); }
+ catch(const css::beans::PropertyVetoException&)
+ { return true; }
+ }
+
+ return false;
+}
+
+void XFrameImpl::impl_notifyChangeListener(const css::beans::PropertyChangeEvent& aEvent)
+{
+ /* Don't use the lock here!
+ The used helper is threadsafe and it lives for the whole lifetime of
+ our own object.
+ */
+ ::comphelper::OInterfaceContainerHelper3<css::beans::XPropertyChangeListener>* pSimpleListener = m_lSimpleChangeListener.getContainer(aEvent.PropertyName);
+ if (! pSimpleListener)
+ return;
+
+ ::comphelper::OInterfaceIteratorHelper3 pListener(*pSimpleListener);
+ while (pListener.hasMoreElements())
+ {
+ try
+ {
+ pListener.next()->propertyChange(aEvent);
+ }
+ catch(const css::uno::RuntimeException&)
+ { pListener.remove(); }
+ }
+}
+
+/*-****************************************************************************************************
+ @short send frame action event to our listener
+ @descr This method is threadsafe AND can be called by our dispose method too!
+ @param "aAction", describe the event for sending
+*//*-*****************************************************************************************************/
+void XFrameImpl::implts_sendFrameActionEvent( const css::frame::FrameAction& aAction )
+{
+ // Sometimes used by dispose()
+
+ // Log information about order of events to file!
+ // (only activated in debug version!)
+ SAL_INFO( "fwk.frame",
+ "[Frame] " << m_sName << " send event " <<
+ (aAction == css::frame::FrameAction_COMPONENT_ATTACHED ? OUString("COMPONENT ATTACHED") :
+ (aAction == css::frame::FrameAction_COMPONENT_DETACHING ? OUString("COMPONENT DETACHING") :
+ (aAction == css::frame::FrameAction_COMPONENT_REATTACHED ? OUString("COMPONENT REATTACHED") :
+ (aAction == css::frame::FrameAction_FRAME_ACTIVATED ? OUString("FRAME ACTIVATED") :
+ (aAction == css::frame::FrameAction_FRAME_DEACTIVATING ? OUString("FRAME DEACTIVATING") :
+ (aAction == css::frame::FrameAction_CONTEXT_CHANGED ? OUString("CONTEXT CHANGED") :
+ (aAction == css::frame::FrameAction_FRAME_UI_ACTIVATED ? OUString("FRAME UI ACTIVATED") :
+ (aAction == css::frame::FrameAction_FRAME_UI_DEACTIVATING ? OUString("FRAME UI DEACTIVATING") :
+ (aAction == css::frame::FrameAction::FrameAction_MAKE_FIXED_SIZE ? OUString("MAKE_FIXED_SIZE") :
+ OUString("*invalid*")))))))))));
+
+ /* SAFE AREA ----------------------------------------------------------------------------------------------- */
+ // Send css::frame::FrameAction event to all listener.
+ // Get container for right listener.
+ // FOLLOW LINES ARE THREADSAFE!!!
+ // ( OInterfaceContainerHelper2 is synchronized with m_aListenerContainer! )
+ comphelper::OInterfaceContainerHelper2* pContainer = m_aListenerContainer.getContainer(
+ cppu::UnoType<css::frame::XFrameActionListener>::get());
+
+ if( pContainer == nullptr )
+ return;
+
+ // Build action event.
+ css::frame::FrameActionEvent aFrameActionEvent( static_cast< ::cppu::OWeakObject* >(this), this, aAction );
+
+ // Get iterator for access to listener.
+ comphelper::OInterfaceIteratorHelper2 aIterator( *pContainer );
+ // Send message to all listener.
+ while( aIterator.hasMoreElements() )
+ {
+ try
+ {
+ static_cast<css::frame::XFrameActionListener*>(aIterator.next())->frameAction( aFrameActionEvent );
+ }
+ catch( const css::uno::RuntimeException& )
+ {
+ aIterator.remove();
+ }
+ }
+}
+
+/*-****************************************************************************************************
+ @short helper to resize our component window
+ @descr A frame contains 2 windows - a container ~ and a component window.
+ This method resize inner component window to full size of outer container window.
+ This method is threadsafe AND can be called by our dispose method too!
+*//*-*****************************************************************************************************/
+void XFrameImpl::implts_resizeComponentWindow()
+{
+ // usually the LayoutManager does the resizing
+ // in case there is no LayoutManager resizing has to be done here
+ if ( m_xLayoutManager.is() )
+ return;
+
+ css::uno::Reference< css::awt::XWindow > xComponentWindow( getComponentWindow() );
+ if( !xComponentWindow.is() )
+ return;
+
+ css::uno::Reference< css::awt::XDevice > xDevice( getContainerWindow(), css::uno::UNO_QUERY );
+
+ // Convert relative size to output size.
+ css::awt::Rectangle aRectangle = getContainerWindow()->getPosSize();
+ css::awt::DeviceInfo aInfo = xDevice->getInfo();
+ css::awt::Size aSize( aRectangle.Width - aInfo.LeftInset - aInfo.RightInset,
+ aRectangle.Height - aInfo.TopInset - aInfo.BottomInset );
+
+ // Resize our component window.
+ xComponentWindow->setPosSize( 0, 0, aSize.Width, aSize.Height, css::awt::PosSize::POSSIZE );
+}
+
+/*-****************************************************************************************************
+ @short helper to set icon on our container window (if it is a system window!)
+ @descr We use our internal set controller (if it exist) to specify which factory he represented.
+ This information can be used to find right icon. But our controller can say it us directly
+ too ... we should ask his optional property set first ...
+
+ @seealso method Window::SetIcon()
+ @onerror We do nothing.
+*//*-*****************************************************************************************************/
+void XFrameImpl::implts_setIconOnWindow()
+{
+ checkDisposed();
+
+ /* SAFE AREA ----------------------------------------------------------------------------------------------- */
+ // Make snapshot of necessary members and release lock.
+ SolarMutexClearableGuard aReadLock;
+ css::uno::Reference< css::awt::XWindow > xContainerWindow = m_xContainerWindow;
+ css::uno::Reference< css::frame::XController > xController = m_xController;
+ aReadLock.clear();
+ /* UNSAFE AREA --------------------------------------------------------------------------------------------- */
+
+ if( !(xContainerWindow.is() && xController.is()) )
+ return;
+
+
+ // a) set default value to an invalid one. So we can start further searches for right icon id, if
+ // first steps failed!
+ // We must reset it to any fallback value - if no search step returns a valid result.
+ sal_Int32 nIcon = -1;
+
+ // b) try to find information on controller propertyset directly
+ // Don't forget to catch possible exceptions - because these property is an optional one!
+ css::uno::Reference< css::beans::XPropertySet > xSet( xController, css::uno::UNO_QUERY );
+ if( xSet.is() )
+ {
+ try
+ {
+ css::uno::Reference< css::beans::XPropertySetInfo > const xPSI( xSet->getPropertySetInfo(),
+ css::uno::UNO_SET_THROW );
+ if ( xPSI->hasPropertyByName( "IconId" ) )
+ xSet->getPropertyValue( "IconId" ) >>= nIcon;
+ }
+ catch( css::uno::Exception& )
+ {
+ DBG_UNHANDLED_EXCEPTION("fwk");
+ }
+ }
+
+ // c) if b) failed... analyze argument list of currently loaded document inside the frame to find the filter.
+ // He can be used to detect right factory - and these can be used to match factory to icon...
+ if( nIcon == -1 )
+ {
+ css::uno::Reference< css::frame::XModel > xModel = xController->getModel();
+ if( xModel.is() )
+ {
+ SvtModuleOptions::EFactory eFactory = SvtModuleOptions::ClassifyFactoryByModel(xModel);
+ if (eFactory != SvtModuleOptions::EFactory::UNKNOWN_FACTORY)
+ nIcon = SvtModuleOptions().GetFactoryIcon( eFactory );
+ }
+ }
+
+ // d) if all steps failed - use fallback!
+ if( nIcon == -1 )
+ {
+ nIcon = 0;
+ }
+
+ // e) set icon on container window now
+ // Don't forget SolarMutex! We use vcl directly :-(
+ // Check window pointer for right WorkWindow class too!!!
+ /* SAFE AREA ----------------------------------------------------------------------------------------------- */
+ {
+ SolarMutexGuard aSolarGuard;
+ VclPtr<vcl::Window> pWindow = VCLUnoHelper::GetWindow( xContainerWindow );
+ if(
+ ( pWindow != nullptr ) &&
+ ( pWindow->GetType() == WindowType::WORKWINDOW )
+ )
+ {
+ WorkWindow* pWorkWindow = static_cast<WorkWindow*>(pWindow.get());
+ pWorkWindow->SetIcon( static_cast<sal_uInt16>(nIcon) );
+ }
+ }
+ /* UNSAFE AREA --------------------------------------------------------------------------------------------- */
+}
+
+/*-************************************************************************************************************
+ @short helper to start/stop listening for window events on container window
+ @descr If we get a new container window, we must set it on internal member ...
+ and stop listening at old one ... and start listening on new one!
+ But sometimes (in dispose() call!) it's necessary to stop listening without starting
+ on new connections. So we split this functionality to make it easier at use.
+
+ @seealso method initialize()
+ @seealso method dispose()
+ @onerror We do nothing!
+ @threadsafe yes
+*//*-*************************************************************************************************************/
+void XFrameImpl::implts_startWindowListening()
+{
+ checkDisposed();
+
+ /* SAFE AREA ----------------------------------------------------------------------------------------------- */
+ // Make snapshot of necessary member!
+ SolarMutexClearableGuard aReadLock;
+ css::uno::Reference< css::awt::XWindow > xContainerWindow = m_xContainerWindow;
+ css::uno::Reference< css::datatransfer::dnd::XDropTargetListener > xDragDropListener = m_xDropTargetListener;
+ css::uno::Reference< css::awt::XWindowListener > xWindowListener(this);
+ css::uno::Reference< css::awt::XFocusListener > xFocusListener(this);
+ css::uno::Reference< css::awt::XTopWindowListener > xTopWindowListener(this);
+ aReadLock.clear();
+ /* UNSAFE AREA --------------------------------------------------------------------------------------------- */
+
+ if( !xContainerWindow.is() )
+ return;
+
+ xContainerWindow->addWindowListener( xWindowListener);
+ xContainerWindow->addFocusListener ( xFocusListener );
+
+ css::uno::Reference< css::awt::XTopWindow > xTopWindow( xContainerWindow, css::uno::UNO_QUERY );
+ if( xTopWindow.is() )
+ {
+ xTopWindow->addTopWindowListener( xTopWindowListener );
+
+ css::uno::Reference< css::awt::XToolkit2 > xToolkit = css::awt::Toolkit::create( m_xContext );
+ css::uno::Reference< css::datatransfer::dnd::XDropTarget > xDropTarget = xToolkit->getDropTarget( xContainerWindow );
+ if( xDropTarget.is() )
+ {
+ xDropTarget->addDropTargetListener( xDragDropListener );
+ xDropTarget->setActive( true );
+ }
+ }
+}
+
+void XFrameImpl::implts_stopWindowListening()
+{
+ // Sometimes used by dispose()
+
+ /* SAFE AREA ----------------------------------------------------------------------------------------------- */
+ // Make snapshot of necessary member!
+ SolarMutexClearableGuard aReadLock;
+ css::uno::Reference< css::awt::XWindow > xContainerWindow = m_xContainerWindow;
+ css::uno::Reference< css::datatransfer::dnd::XDropTargetListener > xDragDropListener = m_xDropTargetListener;
+ css::uno::Reference< css::awt::XWindowListener > xWindowListener(this);
+ css::uno::Reference< css::awt::XFocusListener > xFocusListener(this);
+ css::uno::Reference< css::awt::XTopWindowListener > xTopWindowListener(this);
+ aReadLock.clear();
+ /* UNSAFE AREA --------------------------------------------------------------------------------------------- */
+
+ if( !xContainerWindow.is() )
+ return;
+
+ xContainerWindow->removeWindowListener( xWindowListener);
+ xContainerWindow->removeFocusListener ( xFocusListener );
+
+ css::uno::Reference< css::awt::XTopWindow > xTopWindow( xContainerWindow, css::uno::UNO_QUERY );
+ if( !xTopWindow.is() )
+ return;
+
+ xTopWindow->removeTopWindowListener( xTopWindowListener );
+
+ css::uno::Reference< css::awt::XToolkit2 > xToolkit = css::awt::Toolkit::create( m_xContext );
+ css::uno::Reference< css::datatransfer::dnd::XDropTarget > xDropTarget =
+ xToolkit->getDropTarget( xContainerWindow );
+ if( xDropTarget.is() )
+ {
+ xDropTarget->removeDropTargetListener( xDragDropListener );
+ xDropTarget->setActive( false );
+ }
+}
+
+/*-****************************************************************************************************
+ @short helper to force broken close() request again
+ @descr If we self disagree with a close() request, and detect that all external locks are gone ...
+ then we must try to close this frame again.
+
+ @seealso XCloseable::close()
+ @seealso XFrameImpl::close()
+ @seealso XFrameImpl::removeActionLock()
+ @seealso XFrameImpl::resetActionLock()
+ @seealso m_bSelfClose
+ @seealso m_nExternalLockCount
+
+ @threadsafe yes
+*//*-*****************************************************************************************************/
+void XFrameImpl::implts_checkSuicide()
+{
+ /* SAFE */
+ SolarMutexClearableGuard aReadLock;
+ // in case of lock==0 and safed state of previous close() request m_bSelfClose
+ // we must force close() again. Because we had disagreed with that before.
+ bool bSuicide = (m_nExternalLockCount==0 && m_bSelfClose);
+ m_bSelfClose = false;
+ aReadLock.clear();
+ /* } SAFE */
+ // force close and deliver ownership to source of possible thrown veto exception
+ // Attention: Because this method is not designed to throw such exception we must suppress
+ // it for outside code!
+ try
+ {
+ if (bSuicide)
+ close(true);
+ }
+ catch(const css::util::CloseVetoException&)
+ {}
+ catch(const css::lang::DisposedException&)
+ {}
+}
+
+/** little helper to enable/disable the menu closer at the menubar of the given frame.
+
+ @param xFrame
+ we use its layout manager to set/reset a special callback.
+ Its existence regulate visibility of this closer item.
+
+ @param bState
+ <TRUE/> enable; <FALSE/> disable this state
+ */
+
+void XFrameImpl::impl_setCloser( /*IN*/ const css::uno::Reference< css::frame::XFrame2 >& xFrame ,
+ /*IN*/ bool bState )
+{
+ // Note: If start module is not installed - no closer has to be shown!
+ if (!SvtModuleOptions().IsModuleInstalled(SvtModuleOptions::EModule::STARTMODULE))
+ return;
+
+ try
+ {
+ css::uno::Reference< css::beans::XPropertySet > xFrameProps(xFrame, css::uno::UNO_QUERY_THROW);
+ css::uno::Reference< css::frame::XLayoutManager > xLayoutManager;
+ xFrameProps->getPropertyValue(FRAME_PROPNAME_ASCII_LAYOUTMANAGER) >>= xLayoutManager;
+ css::uno::Reference< css::beans::XPropertySet > xLayoutProps(xLayoutManager, css::uno::UNO_QUERY_THROW);
+ xLayoutProps->setPropertyValue(LAYOUTMANAGER_PROPNAME_MENUBARCLOSER, css::uno::Any(bState));
+ }
+ catch(const css::uno::RuntimeException&)
+ { throw; }
+ catch(const css::uno::Exception&)
+ {}
+}
+
+/** it checks, which of the top level task frames must have the special menu closer for
+ switching to the backing window mode.
+
+ It analyze the current list of visible top level frames. Only the last real document
+ frame can have this symbol. Not the help frame nor the backing task itself.
+ Here we do anything related to this closer. We remove it from the old frame and set it
+ for the new one.
+ */
+
+void XFrameImpl::impl_checkMenuCloser()
+{
+ /* SAFE { */
+ SolarMutexClearableGuard aReadLock;
+
+ // only top frames, which are part of our desktop hierarchy, can
+ // do so! By the way - we need the desktop instance to have access
+ // to all other top level frames too.
+ css::uno::Reference< css::frame::XDesktop > xDesktop (m_xParent, css::uno::UNO_QUERY);
+ css::uno::Reference< css::frame::XFramesSupplier > xTaskSupplier(xDesktop , css::uno::UNO_QUERY);
+ if ( !xDesktop.is() || !xTaskSupplier.is() )
+ return;
+
+ aReadLock.clear();
+ /* } SAFE */
+
+ // analyze the list of current open tasks
+ // Suppress search for other views to the same model ...
+ // It's not needed here and can be very expensive.
+ FrameListAnalyzer aAnalyzer(
+ xTaskSupplier,
+ this,
+ FrameAnalyzerFlags::Hidden | FrameAnalyzerFlags::Help | FrameAnalyzerFlags::BackingComponent);
+
+ // specify the new frame, which must have this special state...
+ css::uno::Reference< css::frame::XFrame2 > xNewCloserFrame;
+
+ // a)
+ // If there exist at least one other frame - there are two frames currently open.
+ // But we can enable this closer only, if one of these two tasks includes the help module.
+ // The "other frame" couldn't be the help. Because then it wouldn't be part of this "other list".
+ // In such case it will be separated to the reference aAnalyzer.m_xHelp!
+ // But we must check, if we include ourself the help...
+ // Check aAnalyzer.m_bReferenceIsHelp!
+ if (
+ (aAnalyzer.m_lOtherVisibleFrames.size()==1) &&
+ (
+ (aAnalyzer.m_bReferenceIsHelp ) ||
+ (aAnalyzer.m_bReferenceIsHidden)
+ )
+ )
+ {
+ // others[0] can't be the backing component!
+ // Because it's set at the special member aAnalyzer.m_xBackingComponent ... :-)
+ xNewCloserFrame.set( aAnalyzer.m_lOtherVisibleFrames[0], css::uno::UNO_QUERY_THROW );
+ }
+
+ // b)
+ // There is no other frame... means no other document frame. The help module
+ // will be handled separately and must(!) be ignored here... excepting if we include ourself the help.
+ else if (
+ (aAnalyzer.m_lOtherVisibleFrames.empty()) &&
+ (!aAnalyzer.m_bReferenceIsHelp) &&
+ (!aAnalyzer.m_bReferenceIsHidden) &&
+ (!aAnalyzer.m_bReferenceIsBacking)
+ )
+ {
+ xNewCloserFrame = this;
+ }
+
+ // Look for necessary actions ...
+ // Only if the closer state must be moved from one frame to another one
+ // or must be enabled/disabled at all.
+ SolarMutexGuard aGuard;
+ // Holds the only frame, which must show the special closer menu item (can be NULL!)
+ static css::uno::WeakReference< css::frame::XFrame2 > s_xCloserFrame;
+ css::uno::Reference< css::frame::XFrame2 > xCloserFrame (s_xCloserFrame.get(), css::uno::UNO_QUERY);
+ if (xCloserFrame!=xNewCloserFrame)
+ {
+ if (xCloserFrame.is())
+ impl_setCloser(xCloserFrame, false);
+ if (xNewCloserFrame.is())
+ impl_setCloser(xNewCloserFrame, true);
+ s_xCloserFrame = xNewCloserFrame;
+ }
+}
+
+}
+
+extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface *
+com_sun_star_comp_framework_Frame_get_implementation(
+ css::uno::XComponentContext *context,
+ css::uno::Sequence<css::uno::Any> const &)
+{
+ rtl::Reference<XFrameImpl> inst = new XFrameImpl(context);
+ css::uno::XInterface *acquired_inst = cppu::acquire(inst.get());
+
+ inst->initListeners();
+
+ return acquired_inst;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/framework/source/services/mediatypedetectionhelper.cxx b/framework/source/services/mediatypedetectionhelper.cxx
new file mode 100644
index 000000000..894f95740
--- /dev/null
+++ b/framework/source/services/mediatypedetectionhelper.cxx
@@ -0,0 +1,92 @@
+/* -*- 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 <services/mediatypedetectionhelper.hxx>
+#include <svl/inettype.hxx>
+#include <cppuhelper/supportsservice.hxx>
+#include <com/sun/star/uno/XComponentContext.hpp>
+
+namespace framework
+{
+
+using namespace ::com::sun::star;
+
+// constructor
+
+MediaTypeDetectionHelper::MediaTypeDetectionHelper()
+{
+}
+
+// destructor
+
+MediaTypeDetectionHelper::~MediaTypeDetectionHelper()
+{
+}
+
+// XInterface, XTypeProvider, XServiceInfo
+
+OUString SAL_CALL MediaTypeDetectionHelper::getImplementationName()
+{
+ return "com.sun.star.comp.framework.MediaTypeDetectionHelper";
+}
+
+sal_Bool SAL_CALL MediaTypeDetectionHelper::supportsService( const OUString& sServiceName )
+{
+ return cppu::supportsService(this, sServiceName);
+}
+
+css::uno::Sequence< OUString > SAL_CALL MediaTypeDetectionHelper::getSupportedServiceNames()
+{
+ return { "com.sun.star.frame.MediaTypeDetectionHelper" };
+}
+
+
+// XStringMapping
+
+sal_Bool SAL_CALL MediaTypeDetectionHelper::mapStrings(uno::Sequence< OUString >& rSeq)
+{
+ bool bModified = false;
+ auto rSeqRange = asNonConstRange(rSeq);
+ for( sal_Int32 i = rSeq.getLength(); i--; )
+ {
+
+ OUString& rUrl = rSeqRange[i];
+ INetContentType eType = INetContentTypes::GetContentTypeFromURL( rUrl );
+
+ OUString aType( INetContentTypes::GetContentType( eType ) );
+ if (!aType.isEmpty())
+ {
+ rUrl = aType;
+ bModified = true;
+ }
+ }
+ return bModified;
+}
+
+} // namespace framework
+
+
+extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface*
+framework_MediaTypeDetectionHelper_get_implementation(
+ css::uno::XComponentContext* , css::uno::Sequence<css::uno::Any> const& )
+{
+ return cppu::acquire(new framework::MediaTypeDetectionHelper());
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/framework/source/services/modulemanager.cxx b/framework/source/services/modulemanager.cxx
new file mode 100644
index 000000000..475084c4c
--- /dev/null
+++ b/framework/source/services/modulemanager.cxx
@@ -0,0 +1,350 @@
+/* -*- 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/frame/UnknownModuleException.hpp>
+#include <com/sun/star/frame/XFrame.hpp>
+#include <com/sun/star/frame/XController.hpp>
+#include <com/sun/star/frame/XModel.hpp>
+#include <com/sun/star/frame/XModule.hpp>
+#include <com/sun/star/lang/XServiceInfo.hpp>
+#include <com/sun/star/frame/XModuleManager2.hpp>
+#include <com/sun/star/container/XNameReplace.hpp>
+#include <com/sun/star/container/XContainerQuery.hpp>
+#include <com/sun/star/uno/XComponentContext.hpp>
+
+#include <cppuhelper/implbase.hxx>
+#include <cppuhelper/supportsservice.hxx>
+#include <comphelper/configurationhelper.hxx>
+#include <comphelper/sequenceashashmap.hxx>
+#include <comphelper/sequence.hxx>
+#include <comphelper/enumhelper.hxx>
+#include <utility>
+
+namespace {
+
+class ModuleManager:
+ public cppu::WeakImplHelper<
+ css::lang::XServiceInfo,
+ css::frame::XModuleManager2,
+ css::container::XContainerQuery >
+{
+private:
+
+ /** the global uno service manager.
+ Must be used to create own needed services.
+ */
+ css::uno::Reference< css::uno::XComponentContext > m_xContext;
+
+ /** points to the underlying configuration.
+ This ModuleManager does not cache - it calls directly the
+ configuration API!
+ */
+ css::uno::Reference< css::container::XNameAccess > m_xCFG;
+
+public:
+
+ explicit ModuleManager(css::uno::Reference< css::uno::XComponentContext > xContext);
+
+ ModuleManager(const ModuleManager&) = delete;
+ ModuleManager& operator=(const ModuleManager&) = delete;
+
+ // XServiceInfo
+ virtual OUString SAL_CALL getImplementationName() override;
+
+ virtual sal_Bool SAL_CALL supportsService(
+ OUString const & ServiceName) override;
+
+ virtual css::uno::Sequence< OUString > SAL_CALL
+ getSupportedServiceNames() override;
+
+ // XModuleManager
+ virtual OUString SAL_CALL identify(const css::uno::Reference< css::uno::XInterface >& xModule) override;
+
+ // XNameReplace
+ virtual void SAL_CALL replaceByName(const OUString& sName ,
+ const css::uno::Any& aValue) override;
+
+ // XNameAccess
+ virtual css::uno::Any SAL_CALL getByName(const OUString& sName) override;
+
+ virtual css::uno::Sequence< OUString > SAL_CALL getElementNames() override;
+
+ virtual sal_Bool SAL_CALL hasByName(const OUString& sName) override;
+
+ // XElementAccess
+ virtual css::uno::Type SAL_CALL getElementType() override;
+
+ virtual sal_Bool SAL_CALL hasElements() override;
+
+ // XContainerQuery
+ virtual css::uno::Reference< css::container::XEnumeration > SAL_CALL createSubSetEnumerationByQuery(const OUString& sQuery) override;
+
+ virtual css::uno::Reference< css::container::XEnumeration > SAL_CALL createSubSetEnumerationByProperties(const css::uno::Sequence< css::beans::NamedValue >& lProperties) override;
+
+private:
+
+ /** @short makes the real identification of the module.
+
+ @descr It checks for the optional but preferred interface
+ XModule first. If this module does not exists at the
+ given component it tries to use XServiceInfo instead.
+
+ Note: This method try to locate a suitable module name.
+ Nothing else. Selecting the right component and throwing suitable
+ exceptions must be done outside.
+
+ @see identify()
+
+ @param xComponent
+ the module for identification.
+
+ @return The identifier of the given module.
+ Can be empty if given component is not a real module !
+
+ @threadsafe
+ */
+ OUString implts_identify(const css::uno::Reference< css::uno::XInterface >& xComponent);
+};
+
+ModuleManager::ModuleManager(css::uno::Reference< css::uno::XComponentContext > xContext)
+ : m_xContext(std::move(xContext))
+{
+ m_xCFG.set( comphelper::ConfigurationHelper::openConfig(
+ m_xContext, "/org.openoffice.Setup/Office/Factories",
+ comphelper::EConfigurationModes::ReadOnly ),
+ css::uno::UNO_QUERY_THROW );
+}
+
+OUString ModuleManager::getImplementationName()
+{
+ return "com.sun.star.comp.framework.ModuleManager";
+}
+
+sal_Bool ModuleManager::supportsService(OUString const & ServiceName)
+{
+ return cppu::supportsService(this, ServiceName);
+}
+
+css::uno::Sequence< OUString > ModuleManager::getSupportedServiceNames()
+{
+ return { "com.sun.star.frame.ModuleManager" };
+}
+
+OUString SAL_CALL ModuleManager::identify(const css::uno::Reference< css::uno::XInterface >& xModule)
+{
+ // valid parameter?
+ css::uno::Reference< css::frame::XFrame > xFrame (xModule, css::uno::UNO_QUERY);
+ css::uno::Reference< css::awt::XWindow > xWindow (xModule, css::uno::UNO_QUERY);
+ css::uno::Reference< css::frame::XController > xController(xModule, css::uno::UNO_QUERY);
+ css::uno::Reference< css::frame::XModel > xModel (xModule, css::uno::UNO_QUERY);
+
+ if (
+ (!xFrame.is() ) &&
+ (!xWindow.is() ) &&
+ (!xController.is()) &&
+ (!xModel.is() )
+ )
+ {
+ throw css::lang::IllegalArgumentException(
+ "Given module is not a frame nor a window, controller or model.",
+ static_cast< ::cppu::OWeakObject* >(this),
+ 1);
+ }
+
+ if (xFrame.is())
+ {
+ xController = xFrame->getController();
+ xWindow = xFrame->getComponentWindow();
+ }
+ if (xController.is())
+ xModel = xController->getModel();
+
+ // modules are implemented by the deepest component in hierarchy ...
+ // Means: model -> controller -> window
+ // No fallbacks to higher components are allowed !
+ // Note : A frame provides access to module components only ... but it's not a module by himself.
+
+ OUString sModule;
+ if (xModel.is())
+ sModule = implts_identify(xModel);
+ else if (xController.is())
+ sModule = implts_identify(xController);
+ else if (xWindow.is())
+ sModule = implts_identify(xWindow);
+
+ if (sModule.isEmpty())
+ throw css::frame::UnknownModuleException(
+ "Can not find suitable module for the given component.",
+ static_cast< ::cppu::OWeakObject* >(this));
+
+ return sModule;
+}
+
+void SAL_CALL ModuleManager::replaceByName(const OUString& sName ,
+ const css::uno::Any& aValue)
+{
+ ::comphelper::SequenceAsHashMap lProps(aValue);
+ if (lProps.empty() )
+ {
+ throw css::lang::IllegalArgumentException(
+ "No properties given to replace part of module.",
+ static_cast< cppu::OWeakObject * >(this),
+ 2);
+ }
+
+ // get access to the element
+ // Note: Don't use impl_getConfig() method here. Because it creates a readonly access only, further
+ // it cache it as a member of this module manager instance. If we change some props there ... but don't
+ // flush changes (because an error occurred) we will read them later. If we use a different config access
+ // we can close it without a flush... and our read data won't be affected .-)
+ css::uno::Reference< css::uno::XInterface > xCfg = ::comphelper::ConfigurationHelper::openConfig(
+ m_xContext,
+ "/org.openoffice.Setup/Office/Factories",
+ ::comphelper::EConfigurationModes::Standard);
+ css::uno::Reference< css::container::XNameAccess > xModules (xCfg, css::uno::UNO_QUERY_THROW);
+ css::uno::Reference< css::container::XNameReplace > xModule ;
+
+ xModules->getByName(sName) >>= xModule;
+ if (!xModule.is())
+ {
+ throw css::uno::RuntimeException(
+ "Was not able to get write access to the requested module entry inside configuration.",
+ static_cast< cppu::OWeakObject * >(this));
+ }
+
+ for (auto const& prop : lProps)
+ {
+ // let "NoSuchElementException" out ! We support the same API ...
+ // and without a flush() at the end all changed data before will be ignored !
+ xModule->replaceByName(prop.first.maString, prop.second);
+ }
+
+ ::comphelper::ConfigurationHelper::flush(xCfg);
+}
+
+css::uno::Any SAL_CALL ModuleManager::getByName(const OUString& sName)
+{
+ // get access to the element
+ css::uno::Reference< css::container::XNameAccess > xModule;
+ m_xCFG->getByName(sName) >>= xModule;
+ if (!xModule.is())
+ {
+ throw css::uno::RuntimeException(
+ "Was not able to get write access to the requested module entry inside configuration.",
+ static_cast< cppu::OWeakObject * >(this));
+ }
+
+ // convert it to seq< PropertyValue >
+ const css::uno::Sequence< OUString > lPropNames = xModule->getElementNames();
+ comphelper::SequenceAsHashMap lProps;
+
+ lProps[OUString("ooSetupFactoryModuleIdentifier")] <<= sName;
+ for (const OUString& sPropName : lPropNames)
+ {
+ lProps[sPropName] = xModule->getByName(sPropName);
+ }
+
+ return css::uno::Any(lProps.getAsConstPropertyValueList());
+}
+
+css::uno::Sequence< OUString > SAL_CALL ModuleManager::getElementNames()
+{
+ return m_xCFG->getElementNames();
+}
+
+sal_Bool SAL_CALL ModuleManager::hasByName(const OUString& sName)
+{
+ return m_xCFG->hasByName(sName);
+}
+
+css::uno::Type SAL_CALL ModuleManager::getElementType()
+{
+ return cppu::UnoType<css::uno::Sequence< css::beans::PropertyValue >>::get();
+}
+
+sal_Bool SAL_CALL ModuleManager::hasElements()
+{
+ return m_xCFG->hasElements();
+}
+
+css::uno::Reference< css::container::XEnumeration > SAL_CALL ModuleManager::createSubSetEnumerationByQuery(const OUString&)
+{
+ return css::uno::Reference< css::container::XEnumeration >();
+}
+
+css::uno::Reference< css::container::XEnumeration > SAL_CALL ModuleManager::createSubSetEnumerationByProperties(const css::uno::Sequence< css::beans::NamedValue >& lProperties)
+{
+ ::comphelper::SequenceAsHashMap lSearchProps(lProperties);
+ const css::uno::Sequence< OUString > lModules = getElementNames();
+ ::std::vector< css::uno::Any > lResult;
+
+ for (const OUString& rModuleName : lModules)
+ {
+ try
+ {
+ ::comphelper::SequenceAsHashMap lModuleProps = getByName(rModuleName);
+ if (lModuleProps.match(lSearchProps))
+ lResult.push_back(css::uno::Any(lModuleProps.getAsConstPropertyValueList()));
+ }
+ catch(const css::uno::Exception&)
+ {
+ }
+ }
+
+ return new ::comphelper::OAnyEnumeration(comphelper::containerToSequence(lResult));
+}
+
+OUString ModuleManager::implts_identify(const css::uno::Reference< css::uno::XInterface >& xComponent)
+{
+ // Search for an optional (!) interface XModule first.
+ // It's used to overrule an existing service name. Used e.g. by our database form designer
+ // which uses a writer module internally.
+ css::uno::Reference< css::frame::XModule > xModule(xComponent, css::uno::UNO_QUERY);
+ if (xModule.is())
+ return xModule->getIdentifier();
+
+ // detect modules in a generic way...
+ // comparing service names with configured entries...
+ css::uno::Reference< css::lang::XServiceInfo > xInfo(xComponent, css::uno::UNO_QUERY);
+ if (!xInfo.is())
+ return OUString();
+
+ const css::uno::Sequence< OUString > lKnownModules = getElementNames();
+ for (const OUString& rName : lKnownModules)
+ {
+ if (xInfo->supportsService(rName))
+ return rName;
+ }
+
+ return OUString();
+}
+
+}
+
+extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface *
+com_sun_star_comp_framework_ModuleManager_get_implementation(
+ css::uno::XComponentContext *context,
+ css::uno::Sequence<css::uno::Any> const &)
+{
+ return cppu::acquire(new ModuleManager(context));
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/framework/source/services/pathsettings.cxx b/framework/source/services/pathsettings.cxx
new file mode 100644
index 000000000..9457ad862
--- /dev/null
+++ b/framework/source/services/pathsettings.cxx
@@ -0,0 +1,1422 @@
+/* -*- 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 <string_view>
+#include <utility>
+#include <unordered_map>
+
+#include <properties.h>
+#include <helper/mischelper.hxx>
+
+#include <com/sun/star/beans/Property.hpp>
+#include <com/sun/star/beans/XProperty.hpp>
+#include <com/sun/star/beans/PropertyAttribute.hpp>
+#include <com/sun/star/beans/XPropertySet.hpp>
+#include <com/sun/star/util/XChangesNotifier.hpp>
+#include <com/sun/star/util/PathSubstitution.hpp>
+#include <com/sun/star/container/XNameAccess.hpp>
+#include <com/sun/star/lang/XServiceInfo.hpp>
+#include <com/sun/star/util/XStringSubstitution.hpp>
+#include <com/sun/star/util/XChangesListener.hpp>
+#include <com/sun/star/util/XPathSettings.hpp>
+
+#include <tools/urlobj.hxx>
+#include <rtl/ustrbuf.hxx>
+#include <rtl/ref.hxx>
+#include <sal/log.hxx>
+
+#include <cppuhelper/basemutex.hxx>
+#include <cppuhelper/propshlp.hxx>
+#include <cppuhelper/compbase.hxx>
+#include <cppuhelper/supportsservice.hxx>
+#include <comphelper/sequence.hxx>
+#include <comphelper/configurationhelper.hxx>
+#include <unotools/configpaths.hxx>
+#include <o3tl/string_view.hxx>
+
+using namespace framework;
+
+constexpr OUStringLiteral CFGPROP_USERPATHS = u"UserPaths";
+constexpr OUStringLiteral CFGPROP_WRITEPATH = u"WritePath";
+
+/*
+ 0 : old style "Template" string using ";" as separator
+ 1 : internal paths "Template_internal" string list
+ 2 : user paths "Template_user" string list
+ 3 : write path "Template_write" string
+ */
+
+constexpr OUStringLiteral POSTFIX_INTERNAL_PATHS = u"_internal";
+constexpr OUStringLiteral POSTFIX_USER_PATHS = u"_user";
+constexpr OUStringLiteral POSTFIX_WRITE_PATH = u"_writable";
+
+namespace {
+
+const sal_Int32 IDGROUP_OLDSTYLE = 0;
+const sal_Int32 IDGROUP_INTERNAL_PATHS = 1;
+const sal_Int32 IDGROUP_USER_PATHS = 2;
+const sal_Int32 IDGROUP_WRITE_PATH = 3;
+
+const sal_Int32 IDGROUP_COUNT = 4;
+
+sal_Int32 impl_getPropGroup(sal_Int32 nID)
+{
+ return (nID % IDGROUP_COUNT);
+}
+
+/* enable it if you wish to migrate old user settings (using the old cfg schema) on demand...
+ disable it in case only the new schema must be used.
+ */
+
+typedef ::cppu::WeakComponentImplHelper<
+ css::lang::XServiceInfo,
+ css::util::XChangesListener, // => XEventListener
+ css::util::XPathSettings> // => XPropertySet
+ PathSettings_BASE;
+
+class PathSettings : private cppu::BaseMutex
+ , public PathSettings_BASE
+ , public ::cppu::OPropertySetHelper
+{
+ struct PathInfo
+ {
+ public:
+
+ PathInfo()
+ : bIsSinglePath (false)
+ , bIsReadonly (false)
+ {}
+
+ /// an internal name describing this path
+ OUString sPathName;
+
+ /// contains all paths, which are used internally - but are not visible for the user.
+ std::vector<OUString> lInternalPaths;
+
+ /// contains all paths configured by the user
+ std::vector<OUString> lUserPaths;
+
+ /// this special path is used to generate feature depending content there
+ OUString sWritePath;
+
+ /// indicates real single paths, which uses WritePath property only
+ bool bIsSinglePath;
+
+ /// simple handling of finalized/mandatory states ... => we know one state READONLY only .-)
+ bool bIsReadonly;
+ };
+
+ typedef std::unordered_map<OUString, PathSettings::PathInfo> PathHash;
+
+ enum EChangeOp
+ {
+ E_UNDEFINED,
+ E_ADDED,
+ E_CHANGED,
+ E_REMOVED
+ };
+
+private:
+
+ /** reference to factory, which has create this instance. */
+ css::uno::Reference< css::uno::XComponentContext > m_xContext;
+
+ /** list of all path variables and her corresponding values. */
+ PathSettings::PathHash m_lPaths;
+
+ /** describes all properties available on our interface.
+ Will be generated on demand based on our path list m_lPaths. */
+ css::uno::Sequence< css::beans::Property > m_lPropDesc;
+
+ /** helper needed to (re-)substitute all internal save path values. */
+ css::uno::Reference< css::util::XStringSubstitution > m_xSubstitution;
+
+ /** provides access to the old configuration schema (which will be migrated on demand). */
+ css::uno::Reference< css::container::XNameAccess > m_xCfgOld;
+
+ /** provides access to the new configuration schema. */
+ css::uno::Reference< css::container::XNameAccess > m_xCfgNew;
+
+ /** helper to listen for configuration changes without ownership cycle problems */
+ css::uno::Reference< css::util::XChangesListener > m_xCfgNewListener;
+
+ std::unique_ptr<::cppu::OPropertyArrayHelper> m_pPropHelp;
+
+public:
+
+ /** initialize a new instance of this class.
+ Attention: It's necessary for right function of this class, that the order of base
+ classes is the right one. Because we transfer information from one base to another
+ during this ctor runs! */
+ explicit PathSettings(css::uno::Reference< css::uno::XComponentContext > xContext);
+
+ /** free all used resources ... if it was not already done. */
+ virtual ~PathSettings() override;
+
+ virtual OUString SAL_CALL getImplementationName() override
+ {
+ return "com.sun.star.comp.framework.PathSettings";
+ }
+
+ virtual sal_Bool SAL_CALL supportsService(OUString const & ServiceName) override
+ {
+ return cppu::supportsService(this, ServiceName);
+ }
+
+ virtual css::uno::Sequence<OUString> SAL_CALL getSupportedServiceNames() override
+ {
+ return {"com.sun.star.util.PathSettings"};
+ }
+
+ // XInterface
+ virtual css::uno::Any SAL_CALL queryInterface( const css::uno::Type& type) override;
+ virtual void SAL_CALL acquire() noexcept override
+ { OWeakObject::acquire(); }
+ virtual void SAL_CALL release() noexcept override
+ { OWeakObject::release(); }
+
+ // XTypeProvider
+ virtual css::uno::Sequence< css::uno::Type > SAL_CALL getTypes( ) override;
+
+ // css::util::XChangesListener
+ virtual void SAL_CALL changesOccurred(const css::util::ChangesEvent& aEvent) override;
+
+ // css::lang::XEventListener
+ virtual void SAL_CALL disposing(const css::lang::EventObject& aSource) override;
+
+ /**
+ * XPathSettings attribute methods
+ */
+ virtual OUString SAL_CALL getAddin() override
+ { return getStringProperty("Addin"); }
+ virtual void SAL_CALL setAddin(const OUString& p1) override
+ { setStringProperty("Addin", p1); }
+ virtual OUString SAL_CALL getAutoCorrect() override
+ { return getStringProperty("AutoCorrect"); }
+ virtual void SAL_CALL setAutoCorrect(const OUString& p1) override
+ { setStringProperty("AutoCorrect", p1); }
+ virtual OUString SAL_CALL getAutoText() override
+ { return getStringProperty("AutoText"); }
+ virtual void SAL_CALL setAutoText(const OUString& p1) override
+ { setStringProperty("AutoText", p1); }
+ virtual OUString SAL_CALL getBackup() override
+ { return getStringProperty("Backup"); }
+ virtual void SAL_CALL setBackup(const OUString& p1) override
+ { setStringProperty("Backup", p1); }
+ virtual OUString SAL_CALL getBasic() override
+ { return getStringProperty("Basic"); }
+ virtual void SAL_CALL setBasic(const OUString& p1) override
+ { setStringProperty("Basic", p1); }
+ virtual OUString SAL_CALL getBitmap() override
+ { return getStringProperty("Bitmap"); }
+ virtual void SAL_CALL setBitmap(const OUString& p1) override
+ { setStringProperty("Bitmap", p1); }
+ virtual OUString SAL_CALL getConfig() override
+ { return getStringProperty("Config"); }
+ virtual void SAL_CALL setConfig(const OUString& p1) override
+ { setStringProperty("Config", p1); }
+ virtual OUString SAL_CALL getDictionary() override
+ { return getStringProperty("Dictionary"); }
+ virtual void SAL_CALL setDictionary(const OUString& p1) override
+ { setStringProperty("Dictionary", p1); }
+ virtual OUString SAL_CALL getFavorite() override
+ { return getStringProperty("Favorite"); }
+ virtual void SAL_CALL setFavorite(const OUString& p1) override
+ { setStringProperty("Favorite", p1); }
+ virtual OUString SAL_CALL getFilter() override
+ { return getStringProperty("Filter"); }
+ virtual void SAL_CALL setFilter(const OUString& p1) override
+ { setStringProperty("Filter", p1); }
+ virtual OUString SAL_CALL getGallery() override
+ { return getStringProperty("Gallery"); }
+ virtual void SAL_CALL setGallery(const OUString& p1) override
+ { setStringProperty("Gallery", p1); }
+ virtual OUString SAL_CALL getGraphic() override
+ { return getStringProperty("Graphic"); }
+ virtual void SAL_CALL setGraphic(const OUString& p1) override
+ { setStringProperty("Graphic", p1); }
+ virtual OUString SAL_CALL getHelp() override
+ { return getStringProperty("Help"); }
+ virtual void SAL_CALL setHelp(const OUString& p1) override
+ { setStringProperty("Help", p1); }
+ virtual OUString SAL_CALL getLinguistic() override
+ { return getStringProperty("Linguistic"); }
+ virtual void SAL_CALL setLinguistic(const OUString& p1) override
+ { setStringProperty("Linguistic", p1); }
+ virtual OUString SAL_CALL getModule() override
+ { return getStringProperty("Module"); }
+ virtual void SAL_CALL setModule(const OUString& p1) override
+ { setStringProperty("Module", p1); }
+ virtual OUString SAL_CALL getPalette() override
+ { return getStringProperty("Palette"); }
+ virtual void SAL_CALL setPalette(const OUString& p1) override
+ { setStringProperty("Palette", p1); }
+ virtual OUString SAL_CALL getPlugin() override
+ { return getStringProperty("Plugin"); }
+ virtual void SAL_CALL setPlugin(const OUString& p1) override
+ { setStringProperty("Plugin", p1); }
+ virtual OUString SAL_CALL getStorage() override
+ { return getStringProperty("Storage"); }
+ virtual void SAL_CALL setStorage(const OUString& p1) override
+ { setStringProperty("Storage", p1); }
+ virtual OUString SAL_CALL getTemp() override
+ { return getStringProperty("Temp"); }
+ virtual void SAL_CALL setTemp(const OUString& p1) override
+ { setStringProperty("Temp", p1); }
+ virtual OUString SAL_CALL getTemplate() override
+ { return getStringProperty("Template"); }
+ virtual void SAL_CALL setTemplate(const OUString& p1) override
+ { setStringProperty("Template", p1); }
+ virtual OUString SAL_CALL getUIConfig() override
+ { return getStringProperty("UIConfig"); }
+ virtual void SAL_CALL setUIConfig(const OUString& p1) override
+ { setStringProperty("UIConfig", p1); }
+ virtual OUString SAL_CALL getUserConfig() override
+ { return getStringProperty("UserConfig"); }
+ virtual void SAL_CALL setUserConfig(const OUString& p1) override
+ { setStringProperty("UserConfig", p1); }
+ virtual OUString SAL_CALL getUserDictionary() override
+ { return getStringProperty("UserDictionary"); }
+ virtual void SAL_CALL setUserDictionary(const OUString& p1) override
+ { setStringProperty("UserDictionary", p1); }
+ virtual OUString SAL_CALL getWork() override
+ { return getStringProperty("Work"); }
+ virtual void SAL_CALL setWork(const OUString& p1) override
+ { setStringProperty("Work", p1); }
+ virtual OUString SAL_CALL getBasePathShareLayer() override
+ { return getStringProperty("UIConfig"); }
+ virtual void SAL_CALL setBasePathShareLayer(const OUString& p1) override
+ { setStringProperty("UIConfig", p1); }
+ virtual OUString SAL_CALL getBasePathUserLayer() override
+ { return getStringProperty("UserConfig"); }
+ virtual void SAL_CALL setBasePathUserLayer(const OUString& p1) override
+ { setStringProperty("UserConfig", p1); }
+
+ /**
+ * overrides to resolve inheritance ambiguity
+ */
+ virtual void SAL_CALL setPropertyValue(const OUString& p1, const css::uno::Any& p2) override
+ { ::cppu::OPropertySetHelper::setPropertyValue(p1, p2); }
+ virtual css::uno::Any SAL_CALL getPropertyValue(const OUString& p1) override
+ { return ::cppu::OPropertySetHelper::getPropertyValue(p1); }
+ virtual void SAL_CALL addPropertyChangeListener(const OUString& p1, const css::uno::Reference<css::beans::XPropertyChangeListener>& p2) override
+ { ::cppu::OPropertySetHelper::addPropertyChangeListener(p1, p2); }
+ virtual void SAL_CALL removePropertyChangeListener(const OUString& p1, const css::uno::Reference<css::beans::XPropertyChangeListener>& p2) override
+ { ::cppu::OPropertySetHelper::removePropertyChangeListener(p1, p2); }
+ virtual void SAL_CALL addVetoableChangeListener(const OUString& p1, const css::uno::Reference<css::beans::XVetoableChangeListener>& p2) override
+ { ::cppu::OPropertySetHelper::addVetoableChangeListener(p1, p2); }
+ virtual void SAL_CALL removeVetoableChangeListener(const OUString& p1, const css::uno::Reference<css::beans::XVetoableChangeListener>& p2) override
+ { ::cppu::OPropertySetHelper::removeVetoableChangeListener(p1, p2); }
+ /** read all configured paths and create all needed internal structures. */
+ void impl_readAll();
+
+private:
+ virtual void SAL_CALL disposing() final override;
+
+ /// @throws css::uno::RuntimeException
+ OUString getStringProperty(const OUString& p1);
+
+ /// @throws css::uno::RuntimeException
+ void setStringProperty(const OUString& p1, const OUString& p2);
+
+ /** read a path info using the old cfg schema.
+ This is needed for "migration on demand" reasons only.
+ Can be removed for next major release .-) */
+ std::vector<OUString> impl_readOldFormat(const OUString& sPath);
+
+ /** read a path info using the new cfg schema. */
+ PathSettings::PathInfo impl_readNewFormat(const OUString& sPath);
+
+ /** filter "real user defined paths" from the old configuration schema
+ and set it as UserPaths on the new schema.
+ Can be removed with new major release ... */
+
+ void impl_mergeOldUserPaths( PathSettings::PathInfo& rPath,
+ const std::vector<OUString>& lOld );
+
+ /** reload one path directly from the new configuration schema (because
+ it was updated by any external code) */
+ PathSettings::EChangeOp impl_updatePath(const OUString& sPath ,
+ bool bNotifyListener);
+
+ /** replace all might existing placeholder variables inside the given path ...
+ or check if the given path value uses paths, which can be replaced with predefined
+ placeholder variables ...
+ */
+ void impl_subst(std::vector<OUString>& lVals ,
+ const css::uno::Reference< css::util::XStringSubstitution >& xSubst ,
+ bool bReSubst);
+
+ void impl_subst(PathSettings::PathInfo& aPath ,
+ bool bReSubst);
+
+ /** converts our new string list schema to the old ";" separated schema ... */
+ OUString impl_convertPath2OldStyle(const PathSettings::PathInfo& rPath ) const;
+ std::vector<OUString> impl_convertOldStyle2Path(std::u16string_view sOldStylePath) const;
+
+ /** remove still known paths from the given lList argument.
+ So real user defined paths can be extracted from the list of
+ fix internal paths !
+ */
+ void impl_purgeKnownPaths(PathSettings::PathInfo& rPath,
+ std::vector<OUString>& lList);
+
+ /** rebuild the member m_lPropDesc using the path list m_lPaths. */
+ void impl_rebuildPropertyDescriptor();
+
+ /** provides direct access to the list of path values
+ using its internal property id.
+ */
+ css::uno::Any impl_getPathValue( sal_Int32 nID ) const;
+ void impl_setPathValue( sal_Int32 nID ,
+ const css::uno::Any& aVal);
+
+ /** check the given handle and return the corresponding PathInfo reference.
+ These reference can be used then directly to manipulate these path. */
+ PathSettings::PathInfo* impl_getPathAccess (sal_Int32 nHandle);
+ const PathSettings::PathInfo* impl_getPathAccessConst(sal_Int32 nHandle) const;
+
+ /** it checks, if the given path value seems to be a valid URL or system path. */
+ bool impl_isValidPath(std::u16string_view sPath) const;
+ bool impl_isValidPath(const std::vector<OUString>& lPath) const;
+
+ void impl_storePath(const PathSettings::PathInfo& aPath);
+
+ css::uno::Sequence< sal_Int32 > impl_mapPathName2IDList(std::u16string_view sPath);
+
+ void impl_notifyPropListener( std::u16string_view sPath ,
+ const PathSettings::PathInfo* pPathOld,
+ const PathSettings::PathInfo* pPathNew);
+
+ // OPropertySetHelper
+ virtual sal_Bool SAL_CALL convertFastPropertyValue( css::uno::Any& aConvertedValue,
+ css::uno::Any& aOldValue,
+ sal_Int32 nHandle,
+ const css::uno::Any& aValue ) override;
+ virtual void SAL_CALL setFastPropertyValue_NoBroadcast( sal_Int32 nHandle,
+ const css::uno::Any& aValue ) override;
+ virtual void SAL_CALL getFastPropertyValue( css::uno::Any& aValue,
+ sal_Int32 nHandle ) const override;
+ // Avoid:
+ // warning: 'virtual css::uno::Any cppu::OPropertySetHelper::getFastPropertyValue(sal_Int32)' was hidden [-Woverloaded-virtual]
+ // warning: by ‘virtual void {anonymous}::PathSettings::getFastPropertyValue(css::uno::Any&, sal_Int32) const’ [-Woverloaded-virtual]
+ using cppu::OPropertySetHelper::getFastPropertyValue;
+ virtual ::cppu::IPropertyArrayHelper& SAL_CALL getInfoHelper() override;
+ virtual css::uno::Reference< css::beans::XPropertySetInfo > SAL_CALL getPropertySetInfo() override;
+
+ /** factory methods to guarantee right (but on demand) initialized members ... */
+ css::uno::Reference< css::util::XStringSubstitution > fa_getSubstitution();
+ css::uno::Reference< css::container::XNameAccess > fa_getCfgOld();
+ css::uno::Reference< css::container::XNameAccess > fa_getCfgNew();
+};
+
+PathSettings::PathSettings( css::uno::Reference< css::uno::XComponentContext > xContext )
+ : PathSettings_BASE(m_aMutex)
+ , ::cppu::OPropertySetHelper(cppu::WeakComponentImplHelperBase::rBHelper)
+ , m_xContext (std::move(xContext))
+{
+}
+
+PathSettings::~PathSettings()
+{
+ disposing();
+}
+
+void SAL_CALL PathSettings::disposing()
+{
+ osl::MutexGuard g(cppu::WeakComponentImplHelperBase::rBHelper.rMutex);
+
+ css::uno::Reference< css::util::XChangesNotifier >
+ xBroadcaster(m_xCfgNew, css::uno::UNO_QUERY);
+ if (xBroadcaster.is())
+ xBroadcaster->removeChangesListener(m_xCfgNewListener);
+
+ m_xSubstitution.clear();
+ m_xCfgOld.clear();
+ m_xCfgNew.clear();
+ m_xCfgNewListener.clear();
+
+ m_pPropHelp.reset();
+}
+
+css::uno::Any SAL_CALL PathSettings::queryInterface( const css::uno::Type& _rType )
+{
+ css::uno::Any aRet = PathSettings_BASE::queryInterface( _rType );
+ if ( !aRet.hasValue() )
+ aRet = ::cppu::OPropertySetHelper::queryInterface( _rType );
+ return aRet;
+}
+
+css::uno::Sequence< css::uno::Type > SAL_CALL PathSettings::getTypes( )
+{
+ return comphelper::concatSequences(
+ PathSettings_BASE::getTypes(),
+ ::cppu::OPropertySetHelper::getTypes()
+ );
+}
+
+void SAL_CALL PathSettings::changesOccurred(const css::util::ChangesEvent& aEvent)
+{
+ sal_Int32 c = aEvent.Changes.getLength();
+ sal_Int32 i = 0;
+ bool bUpdateDescriptor = false;
+
+ for (i=0; i<c; ++i)
+ {
+ const css::util::ElementChange& aChange = aEvent.Changes[i];
+
+ OUString sChanged;
+ aChange.Accessor >>= sChanged;
+
+ OUString sPath = ::utl::extractFirstFromConfigurationPath(sChanged);
+ if (!sPath.isEmpty())
+ {
+ PathSettings::EChangeOp eOp = impl_updatePath(sPath, true);
+ if (
+ (eOp == PathSettings::E_ADDED ) ||
+ (eOp == PathSettings::E_REMOVED)
+ )
+ bUpdateDescriptor = true;
+ }
+ }
+
+ if (bUpdateDescriptor)
+ impl_rebuildPropertyDescriptor();
+}
+
+void SAL_CALL PathSettings::disposing(const css::lang::EventObject& aSource)
+{
+ osl::MutexGuard g(cppu::WeakComponentImplHelperBase::rBHelper.rMutex);
+
+ if (aSource.Source == m_xCfgNew)
+ m_xCfgNew.clear();
+}
+
+OUString PathSettings::getStringProperty(const OUString& p1)
+{
+ css::uno::Any a = ::cppu::OPropertySetHelper::getPropertyValue(p1);
+ OUString s;
+ a >>= s;
+ return s;
+}
+
+void PathSettings::setStringProperty(const OUString& p1, const OUString& p2)
+{
+ ::cppu::OPropertySetHelper::setPropertyValue(p1, css::uno::Any(p2));
+}
+
+void PathSettings::impl_readAll()
+{
+ try
+ {
+ // TODO think about me
+ css::uno::Reference< css::container::XNameAccess > xCfg = fa_getCfgNew();
+ css::uno::Sequence< OUString > lPaths = xCfg->getElementNames();
+
+ sal_Int32 c = lPaths.getLength();
+ for (sal_Int32 i = 0; i < c; ++i)
+ {
+ const OUString& sPath = lPaths[i];
+ impl_updatePath(sPath, false);
+ }
+ }
+ catch(const css::uno::RuntimeException& )
+ {
+ }
+
+ impl_rebuildPropertyDescriptor();
+}
+
+// NO substitution here ! It's done outside ...
+std::vector<OUString> PathSettings::impl_readOldFormat(const OUString& sPath)
+{
+ css::uno::Reference< css::container::XNameAccess > xCfg( fa_getCfgOld() );
+ std::vector<OUString> aPathVal;
+
+ if( xCfg->hasByName(sPath) )
+ {
+ css::uno::Any aVal( xCfg->getByName(sPath) );
+
+ OUString sStringVal;
+ css::uno::Sequence< OUString > lStringListVal;
+
+ if (aVal >>= sStringVal)
+ {
+ aPathVal.push_back(sStringVal);
+ }
+ else if (aVal >>= lStringListVal)
+ {
+ aPathVal = comphelper::sequenceToContainer<std::vector<OUString>>(lStringListVal);
+ }
+ }
+
+ return aPathVal;
+}
+
+// NO substitution here ! It's done outside ...
+PathSettings::PathInfo PathSettings::impl_readNewFormat(const OUString& sPath)
+{
+ css::uno::Reference< css::container::XNameAccess > xCfg = fa_getCfgNew();
+
+ // get access to the "queried" path
+ css::uno::Reference< css::container::XNameAccess > xPath;
+ xCfg->getByName(sPath) >>= xPath;
+
+ PathSettings::PathInfo aPathVal;
+
+ // read internal path list
+ css::uno::Reference< css::container::XNameAccess > xIPath;
+ xPath->getByName("InternalPaths") >>= xIPath;
+ aPathVal.lInternalPaths = comphelper::sequenceToContainer<std::vector<OUString>>(xIPath->getElementNames());
+
+ // read user defined path list
+ css::uno::Sequence<OUString> vTmpUserPathsSeq;
+ xPath->getByName(CFGPROP_USERPATHS) >>= vTmpUserPathsSeq;
+ aPathVal.lUserPaths = comphelper::sequenceToContainer<std::vector<OUString>>(vTmpUserPathsSeq);
+
+ // read the writeable path
+ xPath->getByName(CFGPROP_WRITEPATH) >>= aPathVal.sWritePath;
+
+ // avoid duplicates, by removing the writeable path from
+ // the user defined path list if it happens to be there too
+ std::vector<OUString>::iterator aI = std::find(aPathVal.lUserPaths.begin(), aPathVal.lUserPaths.end(), aPathVal.sWritePath);
+ if (aI != aPathVal.lUserPaths.end())
+ aPathVal.lUserPaths.erase(aI);
+
+ // read state props
+ xPath->getByName("IsSinglePath") >>= aPathVal.bIsSinglePath;
+
+ // analyze finalized/mandatory states
+ aPathVal.bIsReadonly = false;
+ css::uno::Reference< css::beans::XProperty > xInfo(xPath, css::uno::UNO_QUERY);
+ if (xInfo.is())
+ {
+ css::beans::Property aInfo = xInfo->getAsProperty();
+ bool bFinalized = ((aInfo.Attributes & css::beans::PropertyAttribute::READONLY ) == css::beans::PropertyAttribute::READONLY );
+
+ // Note: 'till we support finalized/mandatory on our API more in detail we handle
+ // all states simple as READONLY! But because all really needed paths are "mandatory" by default
+ // we have to handle "finalized" as the real "readonly" indicator.
+ aPathVal.bIsReadonly = bFinalized;
+ }
+
+ return aPathVal;
+}
+
+void PathSettings::impl_storePath(const PathSettings::PathInfo& aPath)
+{
+ css::uno::Reference< css::container::XNameAccess > xCfgNew = fa_getCfgNew();
+ css::uno::Reference< css::container::XNameAccess > xCfgOld = fa_getCfgOld();
+
+ // try to replace path-parts with well known and supported variables.
+ // So an office can be moved easily to another location without losing
+ // its related paths.
+ PathInfo aResubstPath(aPath);
+ impl_subst(aResubstPath, true);
+
+ // update new configuration
+ if (! aResubstPath.bIsSinglePath)
+ {
+ ::comphelper::ConfigurationHelper::writeRelativeKey(xCfgNew,
+ aResubstPath.sPathName,
+ CFGPROP_USERPATHS,
+ css::uno::Any(comphelper::containerToSequence(aResubstPath.lUserPaths)));
+ }
+
+ ::comphelper::ConfigurationHelper::writeRelativeKey(xCfgNew,
+ aResubstPath.sPathName,
+ CFGPROP_WRITEPATH,
+ css::uno::Any(aResubstPath.sWritePath));
+
+ ::comphelper::ConfigurationHelper::flush(xCfgNew);
+
+ // remove the whole path from the old configuration!
+ // Otherwise we can't make sure that the diff between new and old configuration
+ // on loading time really represents a user setting!!!
+
+ // Check if the given path exists inside the old configuration.
+ // Because our new configuration knows more than the list of old paths ... !
+ if (xCfgOld->hasByName(aResubstPath.sPathName))
+ {
+ css::uno::Reference< css::beans::XPropertySet > xProps(xCfgOld, css::uno::UNO_QUERY_THROW);
+ xProps->setPropertyValue(aResubstPath.sPathName, css::uno::Any());
+ ::comphelper::ConfigurationHelper::flush(xCfgOld);
+ }
+}
+
+void PathSettings::impl_mergeOldUserPaths( PathSettings::PathInfo& rPath,
+ const std::vector<OUString>& lOld )
+{
+ for (auto const& old : lOld)
+ {
+ if (rPath.bIsSinglePath)
+ {
+ SAL_WARN_IF(lOld.size()>1, "fwk", "PathSettings::impl_mergeOldUserPaths(): Single path has more than one path value inside old configuration (Common.xcu)!");
+ if ( rPath.sWritePath != old )
+ rPath.sWritePath = old;
+ }
+ else
+ {
+ if (
+ ( std::find(rPath.lInternalPaths.begin(), rPath.lInternalPaths.end(), old) == rPath.lInternalPaths.end()) &&
+ ( std::find(rPath.lUserPaths.begin(), rPath.lUserPaths.end(), old) == rPath.lUserPaths.end() ) &&
+ ( rPath.sWritePath != old )
+ )
+ rPath.lUserPaths.push_back(old);
+ }
+ }
+}
+
+PathSettings::EChangeOp PathSettings::impl_updatePath(const OUString& sPath ,
+ bool bNotifyListener)
+{
+ // SAFE ->
+ osl::MutexGuard g(cppu::WeakComponentImplHelperBase::rBHelper.rMutex);
+
+ PathSettings::PathInfo* pPathOld = nullptr;
+ PathSettings::PathInfo* pPathNew = nullptr;
+ PathSettings::EChangeOp eOp = PathSettings::E_UNDEFINED;
+ PathSettings::PathInfo aPath;
+
+ try
+ {
+ aPath = impl_readNewFormat(sPath);
+ aPath.sPathName = sPath;
+ // replace all might existing variables with real values
+ // Do it before these old paths will be compared against the
+ // new path configuration. Otherwise some strings uses different variables ... but substitution
+ // will produce strings with same content (because some variables are redundant!)
+ impl_subst(aPath, false);
+ }
+ catch(const css::uno::RuntimeException&)
+ { throw; }
+ catch(const css::container::NoSuchElementException&)
+ { eOp = PathSettings::E_REMOVED; }
+ catch(const css::uno::Exception&)
+ { throw; }
+
+ try
+ {
+ // migration of old user defined values on demand
+ // can be disabled for a new major
+ std::vector<OUString> lOldVals = impl_readOldFormat(sPath);
+ // replace all might existing variables with real values
+ // Do it before these old paths will be compared against the
+ // new path configuration. Otherwise some strings uses different variables ... but substitution
+ // will produce strings with same content (because some variables are redundant!)
+ impl_subst(lOldVals, fa_getSubstitution(), false);
+ impl_mergeOldUserPaths(aPath, lOldVals);
+ }
+ catch(const css::uno::RuntimeException&)
+ { throw; }
+ // Normal(!) exceptions can be ignored!
+ // E.g. in case an addon installs a new path, which was not well known for an OOo 1.x installation
+ // we can't find a value for it inside the "old" configuration. So a NoSuchElementException
+ // will be normal .-)
+ catch(const css::uno::Exception&)
+ {}
+
+ PathSettings::PathHash::iterator pPath = m_lPaths.find(sPath);
+ if (eOp == PathSettings::E_UNDEFINED)
+ {
+ if (pPath != m_lPaths.end())
+ eOp = PathSettings::E_CHANGED;
+ else
+ eOp = PathSettings::E_ADDED;
+ }
+
+ switch(eOp)
+ {
+ case PathSettings::E_ADDED :
+ {
+ if (bNotifyListener)
+ {
+ pPathOld = nullptr;
+ pPathNew = &aPath;
+ impl_notifyPropListener(sPath, pPathOld, pPathNew);
+ }
+ m_lPaths[sPath] = aPath;
+ }
+ break;
+
+ case PathSettings::E_CHANGED :
+ {
+ if (bNotifyListener)
+ {
+ pPathOld = &(pPath->second);
+ pPathNew = &aPath;
+ impl_notifyPropListener(sPath, pPathOld, pPathNew);
+ }
+ m_lPaths[sPath] = aPath;
+ }
+ break;
+
+ case PathSettings::E_REMOVED :
+ {
+ if (pPath != m_lPaths.end())
+ {
+ if (bNotifyListener)
+ {
+ pPathOld = &(pPath->second);
+ pPathNew = nullptr;
+ impl_notifyPropListener(sPath, pPathOld, pPathNew);
+ }
+ m_lPaths.erase(pPath);
+ }
+ }
+ break;
+
+ default: // to let compiler be happy
+ break;
+ }
+
+ return eOp;
+}
+
+css::uno::Sequence< sal_Int32 > PathSettings::impl_mapPathName2IDList(std::u16string_view sPath)
+{
+ OUString sInternalProp = OUString::Concat(sPath)+POSTFIX_INTERNAL_PATHS;
+ OUString sUserProp = OUString::Concat(sPath)+POSTFIX_USER_PATHS;
+ OUString sWriteProp = OUString::Concat(sPath)+POSTFIX_WRITE_PATH;
+
+ // Attention: The default set of IDs is fix and must follow these schema.
+ // Otherwise the outside code ant work for new added properties.
+ // Why?
+ // The outside code must fire N events for every changed property.
+ // And the knowing about packaging of variables of the structure PathInfo
+ // follow these group IDs! But if such ID is not in the range of [0..IDGROUP_COUNT]
+ // the outside can't determine the right group ... and can not fire the right events .-)
+
+ css::uno::Sequence<sal_Int32> lIDs{ IDGROUP_OLDSTYLE, IDGROUP_INTERNAL_PATHS,
+ IDGROUP_USER_PATHS, IDGROUP_WRITE_PATH };
+ assert(lIDs.getLength() == IDGROUP_COUNT);
+ auto plIDs = lIDs.getArray();
+
+ sal_Int32 c = m_lPropDesc.getLength();
+ sal_Int32 i = 0;
+ for (i=0; i<c; ++i)
+ {
+ const css::beans::Property& rProp = m_lPropDesc[i];
+
+ if (rProp.Name == sPath)
+ plIDs[IDGROUP_OLDSTYLE] = rProp.Handle;
+ else
+ if (rProp.Name == sInternalProp)
+ plIDs[IDGROUP_INTERNAL_PATHS] = rProp.Handle;
+ else
+ if (rProp.Name == sUserProp)
+ plIDs[IDGROUP_USER_PATHS] = rProp.Handle;
+ else
+ if (rProp.Name == sWriteProp)
+ plIDs[IDGROUP_WRITE_PATH] = rProp.Handle;
+ }
+
+ return lIDs;
+}
+
+void PathSettings::impl_notifyPropListener( std::u16string_view sPath,
+ const PathSettings::PathInfo* pPathOld,
+ const PathSettings::PathInfo* pPathNew)
+{
+ css::uno::Sequence< sal_Int32 > lHandles(1);
+ auto plHandles = lHandles.getArray();
+ css::uno::Sequence< css::uno::Any > lOldVals(1);
+ auto plOldVals = lOldVals.getArray();
+ css::uno::Sequence< css::uno::Any > lNewVals(1);
+ auto plNewVals = lNewVals.getArray();
+
+ css::uno::Sequence< sal_Int32 > lIDs = impl_mapPathName2IDList(sPath);
+ sal_Int32 c = lIDs.getLength();
+ sal_Int32 i = 0;
+ sal_Int32 nMaxID = m_lPropDesc.getLength()-1;
+ for (i=0; i<c; ++i)
+ {
+ sal_Int32 nID = lIDs[i];
+
+ if (
+ (nID < 0 ) ||
+ (nID > nMaxID)
+ )
+ continue;
+
+ plHandles[0] = nID;
+ switch(impl_getPropGroup(nID))
+ {
+ case IDGROUP_OLDSTYLE :
+ {
+ if (pPathOld)
+ {
+ OUString sVal = impl_convertPath2OldStyle(*pPathOld);
+ plOldVals[0] <<= sVal;
+ }
+ if (pPathNew)
+ {
+ OUString sVal = impl_convertPath2OldStyle(*pPathNew);
+ plNewVals[0] <<= sVal;
+ }
+ }
+ break;
+
+ case IDGROUP_INTERNAL_PATHS :
+ {
+ if (pPathOld)
+ plOldVals[0] <<= comphelper::containerToSequence(pPathOld->lInternalPaths);
+ if (pPathNew)
+ plNewVals[0] <<= comphelper::containerToSequence(pPathNew->lInternalPaths);
+ }
+ break;
+
+ case IDGROUP_USER_PATHS :
+ {
+ if (pPathOld)
+ plOldVals[0] <<= comphelper::containerToSequence(pPathOld->lUserPaths);
+ if (pPathNew)
+ plNewVals[0] <<= comphelper::containerToSequence(pPathNew->lUserPaths);
+ }
+ break;
+
+ case IDGROUP_WRITE_PATH :
+ {
+ if (pPathOld)
+ plOldVals[0] <<= pPathOld->sWritePath;
+ if (pPathNew)
+ plNewVals[0] <<= pPathNew->sWritePath;
+ }
+ break;
+ }
+
+ fire(plHandles,
+ plNewVals,
+ plOldVals,
+ 1,
+ false);
+ }
+}
+
+void PathSettings::impl_subst(std::vector<OUString>& lVals ,
+ const css::uno::Reference< css::util::XStringSubstitution >& xSubst ,
+ bool bReSubst)
+{
+ for (auto & old : lVals)
+ {
+ OUString sNew;
+ if (bReSubst)
+ sNew = xSubst->reSubstituteVariables(old);
+ else
+ sNew = xSubst->substituteVariables(old, false);
+
+ old = sNew;
+ }
+}
+
+void PathSettings::impl_subst(PathSettings::PathInfo& aPath ,
+ bool bReSubst)
+{
+ css::uno::Reference< css::util::XStringSubstitution > xSubst = fa_getSubstitution();
+
+ impl_subst(aPath.lInternalPaths, xSubst, bReSubst);
+ impl_subst(aPath.lUserPaths , xSubst, bReSubst);
+ if (bReSubst)
+ aPath.sWritePath = xSubst->reSubstituteVariables(aPath.sWritePath);
+ else
+ aPath.sWritePath = xSubst->substituteVariables(aPath.sWritePath, false);
+}
+
+OUString PathSettings::impl_convertPath2OldStyle(const PathSettings::PathInfo& rPath) const
+{
+ OUStringBuffer sPathVal(256);
+
+ for (auto const& internalPath : rPath.lInternalPaths)
+ {
+ if (sPathVal.getLength())
+ sPathVal.append(";");
+ sPathVal.append(internalPath);
+ }
+ for (auto const& userPath : rPath.lUserPaths)
+ {
+ if (sPathVal.getLength())
+ sPathVal.append(";");
+ sPathVal.append(userPath);
+ }
+ if (!rPath.sWritePath.isEmpty())
+ {
+ if (sPathVal.getLength())
+ sPathVal.append(";");
+ sPathVal.append(rPath.sWritePath);
+ }
+
+ return sPathVal.makeStringAndClear();
+}
+
+std::vector<OUString> PathSettings::impl_convertOldStyle2Path(std::u16string_view sOldStylePath) const
+{
+ std::vector<OUString> lList;
+ sal_Int32 nToken = 0;
+ do
+ {
+ OUString sToken( o3tl::getToken(sOldStylePath, 0, ';', nToken) );
+ if (!sToken.isEmpty())
+ lList.push_back(sToken);
+ }
+ while(nToken >= 0);
+
+ return lList;
+}
+
+void PathSettings::impl_purgeKnownPaths(PathSettings::PathInfo& rPath,
+ std::vector<OUString>& lList)
+{
+ // Erase items in the internal path list from lList.
+ // Also erase items in the internal path list from the user path list.
+ for (auto const& internalPath : rPath.lInternalPaths)
+ {
+ std::vector<OUString>::iterator pItem = std::find(lList.begin(), lList.end(), internalPath);
+ if (pItem != lList.end())
+ lList.erase(pItem);
+ pItem = std::find(rPath.lUserPaths.begin(), rPath.lUserPaths.end(), internalPath);
+ if (pItem != rPath.lUserPaths.end())
+ rPath.lUserPaths.erase(pItem);
+ }
+
+ // Erase items not in lList from the user path list.
+ rPath.lUserPaths.erase(std::remove_if(rPath.lUserPaths.begin(), rPath.lUserPaths.end(),
+ [&lList](const OUString& rItem) {
+ return std::find(lList.begin(), lList.end(), rItem) == lList.end();
+ }),
+ rPath.lUserPaths.end());
+
+ // Erase items in the user path list from lList.
+ for (auto const& userPath : rPath.lUserPaths)
+ {
+ std::vector<OUString>::iterator pItem = std::find(lList.begin(), lList.end(), userPath);
+ if (pItem != lList.end())
+ lList.erase(pItem);
+ }
+
+ // Erase the write path from lList
+ std::vector<OUString>::iterator pItem = std::find(lList.begin(), lList.end(), rPath.sWritePath);
+ if (pItem != lList.end())
+ lList.erase(pItem);
+}
+
+void PathSettings::impl_rebuildPropertyDescriptor()
+{
+ // SAFE ->
+ osl::MutexGuard g(cppu::WeakComponentImplHelperBase::rBHelper.rMutex);
+
+ sal_Int32 c = static_cast<sal_Int32>(m_lPaths.size());
+ sal_Int32 i = 0;
+ m_lPropDesc.realloc(c*IDGROUP_COUNT);
+ auto plPropDesc = m_lPropDesc.getArray();
+
+ for (auto const& path : m_lPaths)
+ {
+ const PathSettings::PathInfo& rPath = path.second;
+ css::beans::Property* pProp = nullptr;
+
+ pProp = &(plPropDesc[i]);
+ pProp->Name = rPath.sPathName;
+ pProp->Handle = i;
+ pProp->Type = cppu::UnoType<OUString>::get();
+ pProp->Attributes = css::beans::PropertyAttribute::BOUND;
+ if (rPath.bIsReadonly)
+ pProp->Attributes |= css::beans::PropertyAttribute::READONLY;
+ ++i;
+
+ pProp = &(plPropDesc[i]);
+ pProp->Name = rPath.sPathName+POSTFIX_INTERNAL_PATHS;
+ pProp->Handle = i;
+ pProp->Type = cppu::UnoType<css::uno::Sequence< OUString >>::get();
+ pProp->Attributes = css::beans::PropertyAttribute::BOUND |
+ css::beans::PropertyAttribute::READONLY;
+ ++i;
+
+ pProp = &(plPropDesc[i]);
+ pProp->Name = rPath.sPathName+POSTFIX_USER_PATHS;
+ pProp->Handle = i;
+ pProp->Type = cppu::UnoType<css::uno::Sequence< OUString >>::get();
+ pProp->Attributes = css::beans::PropertyAttribute::BOUND;
+ if (rPath.bIsReadonly)
+ pProp->Attributes |= css::beans::PropertyAttribute::READONLY;
+ ++i;
+
+ pProp = &(plPropDesc[i]);
+ pProp->Name = rPath.sPathName+POSTFIX_WRITE_PATH;
+ pProp->Handle = i;
+ pProp->Type = cppu::UnoType<OUString>::get();
+ pProp->Attributes = css::beans::PropertyAttribute::BOUND;
+ if (rPath.bIsReadonly)
+ pProp->Attributes |= css::beans::PropertyAttribute::READONLY;
+ ++i;
+ }
+
+ m_pPropHelp.reset(new ::cppu::OPropertyArrayHelper(m_lPropDesc, false)); // false => not sorted ... must be done inside helper
+
+ // <- SAFE
+}
+
+css::uno::Any PathSettings::impl_getPathValue(sal_Int32 nID) const
+{
+ const PathSettings::PathInfo* pPath = impl_getPathAccessConst(nID);
+ if (! pPath)
+ throw css::lang::IllegalArgumentException();
+
+ css::uno::Any aVal;
+ switch(impl_getPropGroup(nID))
+ {
+ case IDGROUP_OLDSTYLE :
+ {
+ OUString sVal = impl_convertPath2OldStyle(*pPath);
+ aVal <<= sVal;
+ }
+ break;
+
+ case IDGROUP_INTERNAL_PATHS :
+ {
+ aVal <<= comphelper::containerToSequence(pPath->lInternalPaths);
+ }
+ break;
+
+ case IDGROUP_USER_PATHS :
+ {
+ aVal <<= comphelper::containerToSequence(pPath->lUserPaths);
+ }
+ break;
+
+ case IDGROUP_WRITE_PATH :
+ {
+ aVal <<= pPath->sWritePath;
+ }
+ break;
+ }
+
+ return aVal;
+}
+
+void PathSettings::impl_setPathValue( sal_Int32 nID ,
+ const css::uno::Any& aVal)
+{
+ PathSettings::PathInfo* pOrgPath = impl_getPathAccess(nID);
+ if (! pOrgPath)
+ throw css::container::NoSuchElementException();
+
+ // We work on a copied path ... so we can be sure that errors during this operation
+ // does not make our internal cache invalid .-)
+ PathSettings::PathInfo aChangePath(*pOrgPath);
+
+ switch(impl_getPropGroup(nID))
+ {
+ case IDGROUP_OLDSTYLE :
+ {
+ OUString sVal;
+ aVal >>= sVal;
+ std::vector<OUString> lList = impl_convertOldStyle2Path(sVal);
+ impl_subst(lList, fa_getSubstitution(), false);
+ impl_purgeKnownPaths(aChangePath, lList);
+ if (! impl_isValidPath(lList))
+ throw css::lang::IllegalArgumentException();
+
+ if (aChangePath.bIsSinglePath)
+ {
+ SAL_WARN_IF(lList.size()>1, "fwk", "PathSettings::impl_setPathValue(): You try to set more than path value for a defined SINGLE_PATH!");
+ if ( !lList.empty() )
+ aChangePath.sWritePath = *(lList.begin());
+ else
+ aChangePath.sWritePath.clear();
+ }
+ else
+ {
+ for (auto const& elem : lList)
+ {
+ aChangePath.lUserPaths.push_back(elem);
+ }
+ }
+ }
+ break;
+
+ case IDGROUP_INTERNAL_PATHS :
+ {
+ if (aChangePath.bIsSinglePath)
+ {
+ throw css::uno::Exception(
+ "The path '" + aChangePath.sPathName
+ + "' is defined as SINGLE_PATH. It's sub set of internal paths can't be set.",
+ static_cast< ::cppu::OWeakObject* >(this));
+ }
+
+ css::uno::Sequence<OUString> lTmpList;
+ aVal >>= lTmpList;
+ std::vector<OUString> lList = comphelper::sequenceToContainer<std::vector<OUString>>(lTmpList);
+ if (! impl_isValidPath(lList))
+ throw css::lang::IllegalArgumentException();
+ aChangePath.lInternalPaths = lList;
+ }
+ break;
+
+ case IDGROUP_USER_PATHS :
+ {
+ if (aChangePath.bIsSinglePath)
+ {
+ throw css::uno::Exception(
+ "The path '" + aChangePath.sPathName
+ + "' is defined as SINGLE_PATH. It's sub set of internal paths can't be set.",
+ static_cast< ::cppu::OWeakObject* >(this));
+ }
+
+ css::uno::Sequence<OUString> lTmpList;
+ aVal >>= lTmpList;
+ std::vector<OUString> lList = comphelper::sequenceToContainer<std::vector<OUString>>(lTmpList);
+ if (! impl_isValidPath(lList))
+ throw css::lang::IllegalArgumentException();
+ aChangePath.lUserPaths = lList;
+ }
+ break;
+
+ case IDGROUP_WRITE_PATH :
+ {
+ OUString sVal;
+ aVal >>= sVal;
+ if (! impl_isValidPath(sVal))
+ throw css::lang::IllegalArgumentException();
+ aChangePath.sWritePath = sVal;
+ }
+ break;
+ }
+
+ // TODO check if path has at least one path value set
+ // At least it depends from the feature using this path, if an empty path list is allowed.
+
+ // first we should try to store the changed (copied!) path ...
+ // In case an error occurs on saving time an exception is thrown ...
+ // If no exception occurs we can update our internal cache (means
+ // we can overwrite pOrgPath !
+ impl_storePath(aChangePath);
+ *pOrgPath = std::move(aChangePath);
+}
+
+bool PathSettings::impl_isValidPath(const std::vector<OUString>& lPath) const
+{
+ for (auto const& path : lPath)
+ {
+ if (! impl_isValidPath(path))
+ return false;
+ }
+
+ return true;
+}
+
+bool PathSettings::impl_isValidPath(std::u16string_view sPath) const
+{
+ // allow empty path to reset a path.
+// idea by LLA to support empty paths
+// if (sPath.getLength() == 0)
+// {
+// return sal_True;
+// }
+
+ return (! INetURLObject(sPath).HasError());
+}
+
+OUString impl_extractBaseFromPropName(const OUString& sPropName)
+{
+ sal_Int32 i = sPropName.indexOf(POSTFIX_INTERNAL_PATHS);
+ if (i > -1)
+ return sPropName.copy(0, i);
+ i = sPropName.indexOf(POSTFIX_USER_PATHS);
+ if (i > -1)
+ return sPropName.copy(0, i);
+ i = sPropName.indexOf(POSTFIX_WRITE_PATH);
+ if (i > -1)
+ return sPropName.copy(0, i);
+
+ return sPropName;
+}
+
+PathSettings::PathInfo* PathSettings::impl_getPathAccess(sal_Int32 nHandle)
+{
+ // SAFE ->
+ osl::MutexGuard g(cppu::WeakComponentImplHelperBase::rBHelper.rMutex);
+
+ if (nHandle > (m_lPropDesc.getLength()-1))
+ return nullptr;
+
+ const css::beans::Property& rProp = m_lPropDesc[nHandle];
+ OUString sProp = impl_extractBaseFromPropName(rProp.Name);
+ PathSettings::PathHash::iterator rPath = m_lPaths.find(sProp);
+
+ if (rPath != m_lPaths.end())
+ return &(rPath->second);
+
+ return nullptr;
+ // <- SAFE
+}
+
+const PathSettings::PathInfo* PathSettings::impl_getPathAccessConst(sal_Int32 nHandle) const
+{
+ // SAFE ->
+ osl::MutexGuard g(cppu::WeakComponentImplHelperBase::rBHelper.rMutex);
+
+ if (nHandle > (m_lPropDesc.getLength()-1))
+ return nullptr;
+
+ const css::beans::Property& rProp = m_lPropDesc[nHandle];
+ OUString sProp = impl_extractBaseFromPropName(rProp.Name);
+ PathSettings::PathHash::const_iterator rPath = m_lPaths.find(sProp);
+
+ if (rPath != m_lPaths.end())
+ return &(rPath->second);
+
+ return nullptr;
+ // <- SAFE
+}
+
+sal_Bool SAL_CALL PathSettings::convertFastPropertyValue( css::uno::Any& aConvertedValue,
+ css::uno::Any& aOldValue ,
+ sal_Int32 nHandle ,
+ const css::uno::Any& aValue )
+{
+ // throws NoSuchElementException !
+ css::uno::Any aCurrentVal = impl_getPathValue(nHandle);
+
+ return PropHelper::willPropertyBeChanged(
+ aCurrentVal,
+ aValue,
+ aOldValue,
+ aConvertedValue);
+}
+
+void SAL_CALL PathSettings::setFastPropertyValue_NoBroadcast( sal_Int32 nHandle,
+ const css::uno::Any& aValue )
+{
+ // throws NoSuchElement- and IllegalArgumentException !
+ impl_setPathValue(nHandle, aValue);
+}
+
+void SAL_CALL PathSettings::getFastPropertyValue(css::uno::Any& aValue ,
+ sal_Int32 nHandle) const
+{
+ aValue = impl_getPathValue(nHandle);
+}
+
+::cppu::IPropertyArrayHelper& SAL_CALL PathSettings::getInfoHelper()
+{
+ return *m_pPropHelp;
+}
+
+css::uno::Reference< css::beans::XPropertySetInfo > SAL_CALL PathSettings::getPropertySetInfo()
+{
+ return ::cppu::OPropertySetHelper::createPropertySetInfo(getInfoHelper());
+}
+
+css::uno::Reference< css::util::XStringSubstitution > PathSettings::fa_getSubstitution()
+{
+ css::uno::Reference< css::util::XStringSubstitution > xSubst;
+ { // SAFE ->
+ osl::MutexGuard g(cppu::WeakComponentImplHelperBase::rBHelper.rMutex);
+ xSubst = m_xSubstitution;
+ }
+
+ if (! xSubst.is())
+ {
+ // create the needed substitution service.
+ // We must replace all used variables inside read path values.
+ // In case we can't do so... the whole office can't work really.
+ // That's why it seems to be OK to throw a RuntimeException then.
+ xSubst = css::util::PathSubstitution::create(m_xContext);
+
+ { // SAFE ->
+ osl::MutexGuard g(cppu::WeakComponentImplHelperBase::rBHelper.rMutex);
+ m_xSubstitution = xSubst;
+ }
+ }
+
+ return xSubst;
+}
+
+css::uno::Reference< css::container::XNameAccess > PathSettings::fa_getCfgOld()
+{
+ css::uno::Reference< css::container::XNameAccess > xCfg;
+ { // SAFE ->
+ osl::MutexGuard g(cppu::WeakComponentImplHelperBase::rBHelper.rMutex);
+ xCfg = m_xCfgOld;
+ } // <- SAFE
+
+ if (! xCfg.is())
+ {
+ xCfg.set( ::comphelper::ConfigurationHelper::openConfig(
+ m_xContext,
+ "org.openoffice.Office.Common/Path/Current",
+ ::comphelper::EConfigurationModes::Standard), // not readonly! Sometimes we need write access there !!!
+ css::uno::UNO_QUERY_THROW);
+
+ { // SAFE ->
+ osl::MutexGuard g(cppu::WeakComponentImplHelperBase::rBHelper.rMutex);
+ m_xCfgOld = xCfg;
+ }
+ }
+
+ return xCfg;
+}
+
+css::uno::Reference< css::container::XNameAccess > PathSettings::fa_getCfgNew()
+{
+ css::uno::Reference< css::container::XNameAccess > xCfg;
+ { // SAFE ->
+ osl::MutexGuard g(cppu::WeakComponentImplHelperBase::rBHelper.rMutex);
+ xCfg = m_xCfgNew;
+ } // <- SAFE
+
+ if (! xCfg.is())
+ {
+ xCfg.set( ::comphelper::ConfigurationHelper::openConfig(
+ m_xContext,
+ "org.openoffice.Office.Paths/Paths",
+ ::comphelper::EConfigurationModes::Standard),
+ css::uno::UNO_QUERY_THROW);
+
+ { // SAFE ->
+ osl::MutexGuard g(cppu::WeakComponentImplHelperBase::rBHelper.rMutex);
+ m_xCfgNew = xCfg;
+ m_xCfgNewListener = new WeakChangesListener(this);
+ }
+
+ css::uno::Reference< css::util::XChangesNotifier > xBroadcaster(xCfg, css::uno::UNO_QUERY_THROW);
+ xBroadcaster->addChangesListener(m_xCfgNewListener);
+ }
+
+ return xCfg;
+}
+
+}
+
+extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface *
+com_sun_star_comp_framework_PathSettings_get_implementation(
+ css::uno::XComponentContext *context,
+ css::uno::Sequence<css::uno::Any> const &)
+{
+ rtl::Reference<PathSettings> xPathSettings = new PathSettings(context);
+ // fill cache
+ xPathSettings->impl_readAll();
+
+ return cppu::acquire(xPathSettings.get());
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/framework/source/services/sessionlistener.cxx b/framework/source/services/sessionlistener.cxx
new file mode 100644
index 000000000..7ba5c82de
--- /dev/null
+++ b/framework/source/services/sessionlistener.cxx
@@ -0,0 +1,413 @@
+/* -*- 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/types.h>
+#include <sal/log.hxx>
+
+#include <framework/desktop.hxx>
+
+#include <tools/diagnose_ex.h>
+#include <com/sun/star/beans/NamedValue.hpp>
+#include <com/sun/star/beans/PropertyValue.hpp>
+#include <com/sun/star/frame/theAutoRecovery.hpp>
+#include <com/sun/star/frame/Desktop.hpp>
+#include <com/sun/star/frame/FeatureStateEvent.hpp>
+#include <com/sun/star/frame/XDispatch.hpp>
+#include <com/sun/star/frame/XSessionManagerListener2.hpp>
+#include <com/sun/star/frame/XSessionManagerClient.hpp>
+#include <com/sun/star/frame/XStatusListener.hpp>
+#include <com/sun/star/lang/EventObject.hpp>
+#include <com/sun/star/lang/XInitialization.hpp>
+#include <com/sun/star/util/URLTransformer.hpp>
+#include <com/sun/star/util/XURLTransformer.hpp>
+#include <com/sun/star/util/URL.hpp>
+#include <cppuhelper/implbase.hxx>
+#include <cppuhelper/supportsservice.hxx>
+
+#include <com/sun/star/uno/Any.hxx>
+#include <com/sun/star/uno/Sequence.hxx>
+#include <utility>
+
+using namespace css;
+using namespace com::sun::star::uno;
+using namespace com::sun::star::util;
+using namespace com::sun::star::beans;
+using namespace framework;
+
+namespace {
+
+/// @HTML
+/** @short implements flat/deep detection of file/stream formats and provides
+ further read/write access to the global office type configuration.
+
+ @descr Using of this class makes it possible to get information about the
+ format type of a given URL or stream. The returned internal type name
+ can be used to get more information about this format. Further this
+ class provides full access to the configuration data and following
+ implementations will support some special query modes.
+
+ @docdate 10.03.2003 by as96863
+
+ @todo <ul>
+ <li>implementation of query mode</li>
+ <li>simple restore mechanism of last consistent cache state,
+ if flush failed</li>
+ </ul>
+ */
+typedef cppu::WeakImplHelper<
+ css::lang::XInitialization,
+ css::frame::XSessionManagerListener2,
+ css::frame::XStatusListener,
+ css::lang::XServiceInfo> SessionListener_BASE;
+
+class SessionListener : public SessionListener_BASE
+{
+private:
+ osl::Mutex m_aMutex;
+
+ /** reference to the uno service manager, which created this service.
+ It can be used to create own needed helper services. */
+ css::uno::Reference< css::uno::XComponentContext > m_xContext;
+
+ css::uno::Reference< css::frame::XSessionManagerClient > m_rSessionManager;
+
+ // restore handling
+ bool m_bRestored;
+
+ bool m_bSessionStoreRequested;
+
+ bool m_bAllowUserInteractionOnQuit;
+ bool m_bTerminated;
+
+ // in case of synchronous call the caller should do saveDone() call himself!
+ void StoreSession( bool bAsync );
+
+ // let session quietly close the documents, remove lock files, store configuration and etc.
+ void QuitSessionQuietly();
+
+public:
+ explicit SessionListener(css::uno::Reference< css::uno::XComponentContext > xContext);
+
+ virtual ~SessionListener() override;
+
+ virtual OUString SAL_CALL getImplementationName() override
+ {
+ return "com.sun.star.comp.frame.SessionListener";
+ }
+
+ virtual sal_Bool SAL_CALL supportsService(OUString const & ServiceName) override
+ {
+ return cppu::supportsService(this, ServiceName);
+ }
+
+ virtual css::uno::Sequence<OUString> SAL_CALL getSupportedServiceNames() override
+ {
+ return {"com.sun.star.frame.SessionListener"};
+ }
+
+ virtual void SAL_CALL disposing(const css::lang::EventObject&) override;
+
+ // XInitialization
+ virtual void SAL_CALL initialize(const css::uno::Sequence< css::uno::Any >& args) override;
+
+ // XSessionManagerListener
+ virtual void SAL_CALL doSave( sal_Bool bShutdown, sal_Bool bCancelable ) override;
+ virtual void SAL_CALL approveInteraction( sal_Bool bInteractionGranted ) override;
+ virtual void SAL_CALL shutdownCanceled() override;
+ virtual sal_Bool SAL_CALL doRestore() override;
+
+ // XSessionManagerListener2
+ virtual void SAL_CALL doQuit() override;
+
+ // XStatusListener
+ virtual void SAL_CALL statusChanged(const css::frame::FeatureStateEvent& event) override;
+};
+
+SessionListener::SessionListener(css::uno::Reference< css::uno::XComponentContext > rxContext )
+ : m_xContext(std::move( rxContext ))
+ , m_bRestored( false )
+ , m_bSessionStoreRequested( false )
+ , m_bAllowUserInteractionOnQuit( false )
+ , m_bTerminated( false )
+{
+ SAL_INFO("fwk.session", "SessionListener::SessionListener");
+}
+
+SessionListener::~SessionListener()
+{
+ SAL_INFO("fwk.session", "SessionListener::~SessionListener");
+ if (m_rSessionManager.is())
+ {
+ css::uno::Reference< XSessionManagerListener> me(this);
+ m_rSessionManager->removeSessionManagerListener(me);
+ }
+}
+
+void SessionListener::StoreSession( bool bAsync )
+{
+ SAL_INFO("fwk.session", "SessionListener::StoreSession");
+ osl::MutexGuard g(m_aMutex);
+ try
+ {
+ // xd create SERVICENAME_AUTORECOVERY -> frame::XDispatch
+ // xd->dispatch("vnd.sun.star.autorecovery:/doSessionSave, async=bAsync
+ // on stop event m_rSessionManager->saveDone(this); in case of asynchronous call
+ // in case of synchronous call the caller should do saveDone() call himself!
+
+ css::uno::Reference< frame::XDispatch > xDispatch = css::frame::theAutoRecovery::get( m_xContext );
+ css::uno::Reference< XURLTransformer > xURLTransformer = URLTransformer::create( m_xContext );
+ URL aURL;
+ aURL.Complete = "vnd.sun.star.autorecovery:/doSessionSave";
+ xURLTransformer->parseStrict(aURL);
+
+ // in case of asynchronous call the notification will trigger saveDone()
+ if ( bAsync )
+ xDispatch->addStatusListener(this, aURL);
+
+ Sequence< PropertyValue > args{ PropertyValue("DispatchAsynchron",-1,Any(bAsync),
+ PropertyState_DIRECT_VALUE) };
+ xDispatch->dispatch(aURL, args);
+ } catch (const css::uno::Exception&) {
+ TOOLS_WARN_EXCEPTION("fwk.session", "");
+ // save failed, but tell manager to go on if we haven't yet dispatched the request
+ // in case of synchronous saving the notification is done by the caller
+ if ( bAsync && m_rSessionManager.is() )
+ m_rSessionManager->saveDone(this);
+ }
+}
+
+void SessionListener::QuitSessionQuietly()
+{
+ SAL_INFO("fwk.session", "SessionListener::QuitSessionQuietly");
+ osl::MutexGuard g(m_aMutex);
+ try
+ {
+ // xd create SERVICENAME_AUTORECOVERY -> frame::XDispatch
+ // xd->dispatch("vnd.sun.star.autorecovery:/doSessionQuietQuit, async=false
+ // it is done synchronously to avoid conflict with normal quit process
+
+ css::uno::Reference< frame::XDispatch > xDispatch = css::frame::theAutoRecovery::get( m_xContext );
+ css::uno::Reference< XURLTransformer > xURLTransformer = URLTransformer::create( m_xContext );
+ URL aURL;
+ aURL.Complete = "vnd.sun.star.autorecovery:/doSessionQuietQuit";
+ xURLTransformer->parseStrict(aURL);
+
+ Sequence< PropertyValue > args{ PropertyValue("DispatchAsynchron",-1,Any(false),
+ PropertyState_DIRECT_VALUE) };
+ xDispatch->dispatch(aURL, args);
+ } catch (const css::uno::Exception&) {
+ TOOLS_WARN_EXCEPTION("fwk.session", "");
+ }
+}
+
+void SAL_CALL SessionListener::disposing(const css::lang::EventObject& Source)
+{
+ SAL_INFO("fwk.session", "SessionListener::disposing");
+ if (Source.Source == m_rSessionManager) {
+ m_rSessionManager.clear();
+ }
+}
+
+void SAL_CALL SessionListener::initialize(const Sequence< Any >& args)
+{
+ SAL_INFO("fwk.session", "SessionListener::initialize");
+
+ OUString aSMgr("com.sun.star.frame.SessionManagerClient");
+ if ( (args.getLength() == 1) && (args[0] >>= m_bAllowUserInteractionOnQuit) )
+ ;// do nothing
+ else if (args.hasElements())
+ {
+ NamedValue v;
+ for (const Any& rArg : args)
+ {
+ if (rArg >>= v)
+ {
+ if ( v.Name == "SessionManagerName" )
+ v.Value >>= aSMgr;
+ else if ( v.Name == "SessionManager" )
+ v.Value >>= m_rSessionManager;
+ else if ( v.Name == "AllowUserInteractionOnQuit" )
+ v.Value >>= m_bAllowUserInteractionOnQuit;
+ }
+ }
+ }
+
+ SAL_INFO("fwk.session.debug", " m_bAllowUserInteractionOnQuit = " << (m_bAllowUserInteractionOnQuit ? "true" : "false"));
+ if (!m_rSessionManager.is())
+ m_rSessionManager = css::uno::Reference< frame::XSessionManagerClient >
+ (m_xContext->getServiceManager()->createInstanceWithContext(aSMgr, m_xContext), UNO_QUERY);
+
+ if (m_rSessionManager.is())
+ {
+ m_rSessionManager->addSessionManagerListener(this);
+ }
+}
+
+void SAL_CALL SessionListener::statusChanged(const frame::FeatureStateEvent& event)
+{
+ SAL_INFO("fwk.session", "SessionListener::statusChanged");
+
+ SAL_INFO("fwk.session.debug", " ev.Feature = " << event.FeatureURL.Complete <<
+ ", ev.Descript = " << event.FeatureDescriptor);
+ if ( event.FeatureURL.Complete == "vnd.sun.star.autorecovery:/doSessionRestore" )
+ {
+ if (event.FeatureDescriptor == "update")
+ m_bRestored = true; // a document was restored
+
+ }
+ else if ( event.FeatureURL.Complete == "vnd.sun.star.autorecovery:/doAutoSave" )
+ { // the "doSessionSave" was never set, look to framework/source/services/autorecovery.cxx
+ // it always testing but never setting (enum AutoRecovery::E_SESSION_SAVE)
+ if (event.FeatureDescriptor == "update")
+ {
+ if (m_rSessionManager.is())
+ m_rSessionManager->saveDone(this); // done with save
+ }
+ }
+}
+
+sal_Bool SAL_CALL SessionListener::doRestore()
+{
+ SAL_INFO("fwk.session", "SessionListener::doRestore");
+ osl::MutexGuard g(m_aMutex);
+ m_bRestored = false;
+ try {
+ css::uno::Reference< frame::XDispatch > xDispatch = css::frame::theAutoRecovery::get( m_xContext );
+
+ URL aURL;
+ aURL.Complete = "vnd.sun.star.autorecovery:/doSessionRestore";
+ css::uno::Reference< XURLTransformer > xURLTransformer(URLTransformer::create(m_xContext));
+ xURLTransformer->parseStrict(aURL);
+ Sequence< PropertyValue > args;
+ xDispatch->addStatusListener(this, aURL);
+ xDispatch->dispatch(aURL, args);
+ m_bRestored = true;
+
+ } catch (const css::uno::Exception&) {
+ TOOLS_WARN_EXCEPTION("fwk.session", "");
+ }
+
+ return m_bRestored;
+}
+
+void SAL_CALL SessionListener::doSave( sal_Bool bShutdown, sal_Bool /*bCancelable*/ )
+{
+ SAL_INFO("fwk.session", "SessionListener::doSave");
+
+ SAL_INFO("fwk.session.debug", " m_bAllowUserInteractionOnQuit = " << (m_bAllowUserInteractionOnQuit ? "true" : "false") <<
+ ", bShutdown = " << (bShutdown ? "true" : "false"));
+ if (bShutdown)
+ {
+ m_bSessionStoreRequested = true; // there is no need to protect it with mutex
+ if ( m_bAllowUserInteractionOnQuit && m_rSessionManager.is() )
+ m_rSessionManager->queryInteraction( static_cast< css::frame::XSessionManagerListener* >( this ) );
+ else
+ StoreSession( true );
+ }
+ // we don't have anything to do so tell the session manager we're done
+ else if( m_rSessionManager.is() )
+ m_rSessionManager->saveDone( this );
+}
+
+void SAL_CALL SessionListener::approveInteraction( sal_Bool bInteractionGranted )
+{
+ SAL_INFO("fwk.session", "SessionListener::approveInteraction");
+ // do AutoSave as the first step
+ osl::MutexGuard g(m_aMutex);
+
+ if ( bInteractionGranted )
+ {
+ // close the office documents in normal way
+ try
+ {
+ // first of all let the session be stored to be sure that we lose no information
+ StoreSession( false );
+
+ css::uno::Reference< css::frame::XDesktop2 > xDesktop = css::frame::Desktop::create( m_xContext );
+ // honestly: how many implementations of XDesktop will we ever have?
+ // so casting this directly to the implementation
+ Desktop* pDesktop(dynamic_cast<Desktop*>(xDesktop.get()));
+ if(pDesktop)
+ {
+ SAL_INFO("fwk.session", " XDesktop is a framework::Desktop -- good.");
+ m_bTerminated = pDesktop->terminateQuickstarterToo();
+ }
+ else
+ {
+ SAL_WARN("fwk.session", " XDesktop is not a framework::Desktop -- this should never happen.");
+ m_bTerminated = xDesktop->terminate();
+ }
+
+ if ( m_rSessionManager.is() )
+ {
+ // false means that the application closing has been cancelled
+ if ( !m_bTerminated )
+ m_rSessionManager->cancelShutdown();
+ else
+ m_rSessionManager->interactionDone( this );
+ }
+ }
+ catch( const css::uno::Exception& )
+ {
+ StoreSession( true );
+ m_rSessionManager->interactionDone( this );
+ }
+
+ if ( m_rSessionManager.is() && m_bTerminated )
+ m_rSessionManager->saveDone(this);
+ }
+ else
+ {
+ StoreSession( true );
+ }
+}
+
+void SessionListener::shutdownCanceled()
+{
+ SAL_INFO("fwk.session", "SessionListener::shutdownCanceled");
+ // set the state back
+ m_bSessionStoreRequested = false; // there is no need to protect it with mutex
+
+ if ( m_rSessionManager.is() )
+ m_rSessionManager->saveDone(this);
+}
+
+void SessionListener::doQuit()
+{
+ SAL_INFO("fwk.session", "SessionListener::doQuit");
+ if ( m_bSessionStoreRequested && !m_bTerminated )
+ {
+ // let the session be closed quietly in this case
+ QuitSessionQuietly();
+ }
+}
+
+}
+
+extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface *
+com_sun_star_comp_frame_SessionListener_get_implementation(
+ css::uno::XComponentContext *context,
+ css::uno::Sequence<css::uno::Any> const &)
+{
+ SAL_INFO("fwk.session", "com_sun_star_comp_frame_SessionListener_get_implementation");
+
+ return cppu::acquire(new SessionListener(context));
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/framework/source/services/substitutepathvars.cxx b/framework/source/services/substitutepathvars.cxx
new file mode 100644
index 000000000..b57523306
--- /dev/null
+++ b/framework/source/services/substitutepathvars.cxx
@@ -0,0 +1,695 @@
+/* -*- 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_folders.h>
+
+#include <comphelper/lok.hxx>
+#include <comphelper/compbase.hxx>
+#include <cppuhelper/supportsservice.hxx>
+
+#include <unotools/bootstrap.hxx>
+#include <unotools/configmgr.hxx>
+#include <osl/file.hxx>
+#include <osl/security.hxx>
+#include <osl/thread.hxx>
+#include <i18nlangtag/languagetag.hxx>
+#include <tools/urlobj.hxx>
+#include <rtl/ustrbuf.hxx>
+#include <rtl/bootstrap.hxx>
+
+#include <officecfg/Office/Paths.hxx>
+
+#include <com/sun/star/lang/XServiceInfo.hpp>
+#include <com/sun/star/container/NoSuchElementException.hpp>
+#include <com/sun/star/container/XHierarchicalNameAccess.hpp>
+#include <com/sun/star/container/XNameAccess.hpp>
+#include <com/sun/star/util/XStringSubstitution.hpp>
+
+#include <unordered_map>
+
+using namespace com::sun::star::uno;
+using namespace com::sun::star::container;
+
+namespace {
+
+enum PreDefVariable
+{
+ PREDEFVAR_INST,
+ PREDEFVAR_PROG,
+ PREDEFVAR_USER,
+ PREDEFVAR_WORK,
+ PREDEFVAR_HOME,
+ PREDEFVAR_TEMP,
+ PREDEFVAR_PATH,
+ PREDEFVAR_USERNAME,
+ PREDEFVAR_LANGID,
+ PREDEFVAR_VLANG,
+ PREDEFVAR_INSTPATH,
+ PREDEFVAR_PROGPATH,
+ PREDEFVAR_USERPATH,
+ PREDEFVAR_INSTURL,
+ PREDEFVAR_PROGURL,
+ PREDEFVAR_USERURL,
+ PREDEFVAR_WORKDIRURL,
+ // New variable of hierarchy service (#i32656#)
+ PREDEFVAR_BASEINSTURL,
+ PREDEFVAR_USERDATAURL,
+ PREDEFVAR_BRANDBASEURL,
+ PREDEFVAR_COUNT
+};
+
+struct FixedVariable
+{
+ const char* pVarName;
+ bool bAbsPath;
+};
+
+// Table with all fixed/predefined variables supported.
+const FixedVariable aFixedVarTable[PREDEFVAR_COUNT] =
+{
+ { "$(inst)", true }, // PREDEFVAR_INST
+ { "$(prog)", true }, // PREDEFVAR_PROG
+ { "$(user)", true }, // PREDEFVAR_USER
+ { "$(work)", true }, // PREDEFVAR_WORK, special variable
+ // (transient)
+ { "$(home)", true }, // PREDEFVAR_HOME
+ { "$(temp)", true }, // PREDEFVAR_TEMP
+ { "$(path)", true }, // PREDEFVAR_PATH
+ { "$(username)", false }, // PREDEFVAR_USERNAME
+ { "$(langid)", false }, // PREDEFVAR_LANGID
+ { "$(vlang)", false }, // PREDEFVAR_VLANG
+ { "$(instpath)", true }, // PREDEFVAR_INSTPATH
+ { "$(progpath)", true }, // PREDEFVAR_PROGPATH
+ { "$(userpath)", true }, // PREDEFVAR_USERPATH
+ { "$(insturl)", true }, // PREDEFVAR_INSTURL
+ { "$(progurl)", true }, // PREDEFVAR_PROGURL
+ { "$(userurl)", true }, // PREDEFVAR_USERURL
+ { "$(workdirurl)", true }, // PREDEFVAR_WORKDIRURL, special variable
+ // (transient) and don't use for
+ // resubstitution
+ { "$(baseinsturl)", true }, // PREDEFVAR_BASEINSTURL
+ { "$(userdataurl)", true }, // PREDEFVAR_USERDATAURL
+ { "$(brandbaseurl)", true } // PREDEFVAR_BRANDBASEURL
+};
+
+struct PredefinedPathVariables
+{
+ // Predefined variables supported by substitute variables
+ LanguageType m_eLanguageType; // Language type of Office
+ OUString m_FixedVar[ PREDEFVAR_COUNT ]; // Variable value access by PreDefVariable
+ OUString m_FixedVarNames[ PREDEFVAR_COUNT ]; // Variable name access by PreDefVariable
+};
+
+struct ReSubstFixedVarOrder
+{
+ sal_Int32 nVarValueLength;
+ PreDefVariable eVariable;
+
+ bool operator< ( const ReSubstFixedVarOrder& aFixedVarOrder ) const
+ {
+ // Reverse operator< to have high to low ordering
+ return ( nVarValueLength > aFixedVarOrder.nVarValueLength );
+ }
+};
+
+typedef comphelper::WeakComponentImplHelper<
+ css::util::XStringSubstitution,
+ css::lang::XServiceInfo > SubstitutePathVariables_BASE;
+
+class SubstitutePathVariables : public SubstitutePathVariables_BASE
+{
+public:
+ explicit SubstitutePathVariables();
+
+ virtual OUString SAL_CALL getImplementationName() override
+ {
+ return "com.sun.star.comp.framework.PathSubstitution";
+ }
+
+ virtual sal_Bool SAL_CALL supportsService(OUString const & ServiceName) override
+ {
+ return cppu::supportsService(this, ServiceName);
+ }
+
+ virtual css::uno::Sequence<OUString> SAL_CALL getSupportedServiceNames() override
+ {
+ return {"com.sun.star.util.PathSubstitution"};
+ }
+
+ // XStringSubstitution
+ virtual OUString SAL_CALL substituteVariables( const OUString& aText, sal_Bool bSubstRequired ) override;
+ virtual OUString SAL_CALL reSubstituteVariables( const OUString& aText ) override;
+ virtual OUString SAL_CALL getSubstituteVariableValue( const OUString& variable ) override;
+
+protected:
+ void SetPredefinedPathVariables();
+
+ // Special case (transient) values can change during runtime!
+ // Don't store them in the pre defined struct
+ OUString GetWorkPath() const;
+ OUString GetWorkVariableValue() const;
+ OUString GetPathVariableValue() const;
+
+ OUString GetHomeVariableValue() const;
+
+ // XStringSubstitution implementation methods
+ /// @throws css::container::NoSuchElementException
+ /// @throws css::uno::RuntimeException
+ OUString impl_substituteVariable( const OUString& aText, bool bSustRequired );
+ /// @throws css::uno::RuntimeException
+ OUString impl_reSubstituteVariables( const OUString& aText );
+ /// @throws css::container::NoSuchElementException
+ /// @throws css::uno::RuntimeException
+ OUString const & impl_getSubstituteVariableValue( const OUString& variable );
+
+private:
+ typedef std::unordered_map<OUString, PreDefVariable>
+ VarNameToIndexMap;
+
+ VarNameToIndexMap m_aPreDefVarMap; // Mapping from pre-def variable names to enum for array access
+ PredefinedPathVariables m_aPreDefVars; // All predefined variables
+ std::vector<ReSubstFixedVarOrder> m_aReSubstFixedVarOrder; // To speed up resubstitution fixed variables (order for lookup)
+};
+
+SubstitutePathVariables::SubstitutePathVariables()
+{
+ SetPredefinedPathVariables();
+
+ // Init the predefined/fixed variable to index hash map
+ for ( int i = 0; i < PREDEFVAR_COUNT; i++ )
+ {
+ // Store variable name into struct of predefined/fixed variables
+ m_aPreDefVars.m_FixedVarNames[i] = OUString::createFromAscii( aFixedVarTable[i].pVarName );
+
+ // Create hash map entry
+ m_aPreDefVarMap.emplace( m_aPreDefVars.m_FixedVarNames[i], PreDefVariable(i) );
+ }
+
+ // Sort predefined/fixed variable to path length
+ for ( int i = 0; i < PREDEFVAR_COUNT; i++ )
+ {
+ if (( i != PREDEFVAR_WORKDIRURL ) && ( i != PREDEFVAR_PATH ))
+ {
+ // Special path variables, don't include into automatic resubstitution search!
+ // $(workdirurl) is not allowed to resubstitute! This variable is the value of path settings entry
+ // and it could be possible that it will be resubstituted by itself!!
+ // Example: WORK_PATH=c:\test, $(workdirurl)=WORK_PATH => WORK_PATH=$(workdirurl) and this cannot be substituted!
+ ReSubstFixedVarOrder aFixedVar;
+ aFixedVar.eVariable = PreDefVariable(i);
+ aFixedVar.nVarValueLength = m_aPreDefVars.m_FixedVar[static_cast<sal_Int32>(aFixedVar.eVariable)].getLength();
+ m_aReSubstFixedVarOrder.push_back( aFixedVar );
+ }
+ }
+ sort(m_aReSubstFixedVarOrder.begin(),m_aReSubstFixedVarOrder.end());
+}
+
+// XStringSubstitution
+OUString SAL_CALL SubstitutePathVariables::substituteVariables( const OUString& aText, sal_Bool bSubstRequired )
+{
+ std::unique_lock g(m_aMutex);
+ return impl_substituteVariable( aText, bSubstRequired );
+}
+
+OUString SAL_CALL SubstitutePathVariables::reSubstituteVariables( const OUString& aText )
+{
+ std::unique_lock g(m_aMutex);
+ return impl_reSubstituteVariables( aText );
+}
+
+OUString SAL_CALL SubstitutePathVariables::getSubstituteVariableValue( const OUString& aVariable )
+{
+ std::unique_lock g(m_aMutex);
+ return impl_getSubstituteVariableValue( aVariable );
+}
+
+OUString SubstitutePathVariables::GetWorkPath() const
+{
+ OUString aWorkPath;
+ css::uno::Reference< css::container::XHierarchicalNameAccess > xPaths(officecfg::Office::Paths::Paths::get(), css::uno::UNO_QUERY_THROW);
+ if (!(xPaths->getByHierarchicalName("['Work']/WritePath") >>= aWorkPath))
+ // fallback in case config layer does not return a usable work dir value.
+ aWorkPath = GetWorkVariableValue();
+
+ return aWorkPath;
+}
+
+OUString SubstitutePathVariables::GetWorkVariableValue() const
+{
+ OUString aWorkPath;
+ std::optional<OUString> x(officecfg::Office::Paths::Variables::Work::get());
+ if (!x)
+ {
+ // fallback to $HOME in case platform dependent config layer does not return
+ // a usable work dir value.
+ osl::Security aSecurity;
+ aSecurity.getHomeDir( aWorkPath );
+ }
+ else
+ aWorkPath = *x;
+ return aWorkPath;
+}
+
+OUString SubstitutePathVariables::GetHomeVariableValue() const
+{
+ osl::Security aSecurity;
+ OUString aHomePath;
+
+ aSecurity.getHomeDir( aHomePath );
+ return aHomePath;
+}
+
+OUString SubstitutePathVariables::GetPathVariableValue() const
+{
+ OUString aRetStr;
+ const char* pEnv = getenv( "PATH" );
+
+ if ( pEnv )
+ {
+ const int PATH_EXTEND_FACTOR = 200;
+ OUString aTmp;
+ OUString aPathList( pEnv, strlen( pEnv ), osl_getThreadTextEncoding() );
+ OUStringBuffer aPathStrBuffer( aPathList.getLength() * PATH_EXTEND_FACTOR / 100 );
+
+ bool bAppendSep = false;
+ sal_Int32 nToken = 0;
+ do
+ {
+ OUString sToken = aPathList.getToken(0, SAL_PATHSEPARATOR, nToken);
+ if (!sToken.isEmpty() &&
+ osl::FileBase::getFileURLFromSystemPath( sToken, aTmp ) ==
+ osl::FileBase::RC::E_None )
+ {
+ if ( bAppendSep )
+ aPathStrBuffer.append( ";" ); // Office uses ';' as path separator
+ aPathStrBuffer.append( aTmp );
+ bAppendSep = true;
+ }
+ }
+ while(nToken>=0);
+
+ aRetStr = aPathStrBuffer.makeStringAndClear();
+ }
+
+ return aRetStr;
+}
+
+OUString SubstitutePathVariables::impl_substituteVariable( const OUString& rText, bool bSubstRequired )
+{
+ // This is maximal recursive depth supported!
+ const sal_Int32 nMaxRecursiveDepth = 8;
+
+ OUString aWorkText = rText;
+ OUString aResult;
+
+ // Use vector with strings to detect endless recursions!
+ std::vector< OUString > aEndlessRecursiveDetector;
+
+ // Search for first occurrence of "$(...".
+ sal_Int32 nDepth = 0;
+ bool bSubstitutionCompleted = false;
+ sal_Int32 nPosition = aWorkText.indexOf( "$(" );
+ sal_Int32 nLength = 0; // = count of letters from "$(" to ")" in string
+ bool bVarNotSubstituted = false;
+
+ // Have we found any variable like "$(...)"?
+ if ( nPosition != -1 )
+ {
+ // Yes; Get length of found variable.
+ // If no ")" was found - nLength is set to 0 by default! see before.
+ sal_Int32 nEndPosition = aWorkText.indexOf( ')', nPosition );
+ if ( nEndPosition != -1 )
+ nLength = nEndPosition - nPosition + 1;
+ }
+
+ // Is there something to replace ?
+ bool bWorkRetrieved = false;
+ bool bWorkDirURLRetrieved = false;
+ while (nDepth < nMaxRecursiveDepth)
+ {
+ while ( ( nPosition != -1 ) && ( nLength > 3 ) ) // "$(" ")"
+ {
+ // YES; Get the next variable for replace.
+ sal_Int32 nReplaceLength = 0;
+ OUString aReplacement;
+ OUString aSubString = aWorkText.copy( nPosition, nLength );
+
+ // Path variables are not case sensitive!
+ OUString aSubVarString = aSubString.toAsciiLowerCase();
+ VarNameToIndexMap::const_iterator pNTOIIter = m_aPreDefVarMap.find( aSubVarString );
+ if ( pNTOIIter != m_aPreDefVarMap.end() )
+ {
+ // Fixed/Predefined variable found
+ PreDefVariable nIndex = pNTOIIter->second;
+
+ // Determine variable value and length from array/table
+ if ( nIndex == PREDEFVAR_WORK && !bWorkRetrieved )
+ {
+ // Transient value, retrieve it again
+ m_aPreDefVars.m_FixedVar[ nIndex ] = GetWorkVariableValue();
+ bWorkRetrieved = true;
+ }
+ else if ( nIndex == PREDEFVAR_WORKDIRURL && !bWorkDirURLRetrieved )
+ {
+ // Transient value, retrieve it again
+ m_aPreDefVars.m_FixedVar[ nIndex ] = GetWorkPath();
+ bWorkDirURLRetrieved = true;
+ }
+
+ // Check preconditions to substitute path variables.
+ // 1. A path variable can only be substituted if it follows a ';'!
+ // 2. It's located exactly at the start of the string being substituted!
+ if (( aFixedVarTable[ int( nIndex ) ].bAbsPath && (( nPosition == 0 ) || (( nPosition > 0 ) && ( aWorkText[nPosition-1] == ';')))) ||
+ ( !aFixedVarTable[ int( nIndex ) ].bAbsPath ))
+ {
+ aReplacement = m_aPreDefVars.m_FixedVar[ nIndex ];
+ nReplaceLength = nLength;
+ }
+ }
+
+ // Have we found something to replace?
+ if ( nReplaceLength > 0 )
+ {
+ // Yes ... then do it.
+ aWorkText = aWorkText.replaceAt( nPosition, nReplaceLength, aReplacement );
+ }
+ else
+ {
+ // Variable not known
+ bVarNotSubstituted = true;
+ nPosition += nLength;
+ }
+
+ // Step after replaced text! If no text was replaced (unknown variable!),
+ // length of aReplacement is 0 ... and we don't step then.
+ nPosition += aReplacement.getLength();
+
+ // We must control index in string before call something at OUString!
+ // The OUString-implementation don't do it for us :-( but the result is not defined otherwise.
+ if ( nPosition + 1 > aWorkText.getLength() )
+ {
+ // Position is out of range. Break loop!
+ nPosition = -1;
+ nLength = 0;
+ }
+ else
+ {
+ // Else; Position is valid. Search for next variable to replace.
+ nPosition = aWorkText.indexOf( "$(", nPosition );
+ // Have we found any variable like "$(...)"?
+ if ( nPosition != -1 )
+ {
+ // Yes; Get length of found variable. If no ")" was found - nLength must set to 0!
+ nLength = 0;
+ sal_Int32 nEndPosition = aWorkText.indexOf( ')', nPosition );
+ if ( nEndPosition != -1 )
+ nLength = nEndPosition - nPosition + 1;
+ }
+ }
+ }
+
+ nPosition = aWorkText.indexOf( "$(" );
+ if ( nPosition == -1 )
+ {
+ bSubstitutionCompleted = true;
+ break; // All variables are substituted
+ }
+ else
+ {
+ // Check for recursion
+ const sal_uInt32 nCount = aEndlessRecursiveDetector.size();
+ for ( sal_uInt32 i=0; i < nCount; i++ )
+ {
+ if ( aEndlessRecursiveDetector[i] == aWorkText )
+ {
+ if ( bVarNotSubstituted )
+ break; // Not all variables could be substituted!
+ else
+ {
+ nDepth = nMaxRecursiveDepth;
+ break; // Recursion detected!
+ }
+ }
+ }
+
+ aEndlessRecursiveDetector.push_back( aWorkText );
+
+ // Initialize values for next
+ sal_Int32 nEndPosition = aWorkText.indexOf( ')', nPosition );
+ if ( nEndPosition != -1 )
+ nLength = nEndPosition - nPosition + 1;
+ bVarNotSubstituted = false;
+ ++nDepth;
+ }
+ }
+
+ // Fill return value with result
+ if ( bSubstitutionCompleted )
+ {
+ // Substitution successful!
+ aResult = aWorkText;
+ }
+ else
+ {
+ // Substitution not successful!
+ if ( nDepth == nMaxRecursiveDepth )
+ {
+ // recursion depth reached!
+ if ( bSubstRequired )
+ {
+ throw NoSuchElementException( "Endless recursion detected. Cannot substitute variables!", static_cast<cppu::OWeakObject *>(this) );
+ }
+ aResult = rText;
+ }
+ else
+ {
+ // variable in text but unknown!
+ if ( bSubstRequired )
+ {
+ throw NoSuchElementException( "Unknown variable found!", static_cast<cppu::OWeakObject *>(this) );
+ }
+ aResult = aWorkText;
+ }
+ }
+
+ return aResult;
+}
+
+OUString SubstitutePathVariables::impl_reSubstituteVariables( const OUString& rURL )
+{
+ OUString aURL;
+
+ INetURLObject aUrl( rURL );
+ if ( !aUrl.HasError() )
+ aURL = aUrl.GetMainURL( INetURLObject::DecodeMechanism::NONE );
+ else
+ {
+ // Convert a system path to a UCB compliant URL before resubstitution
+ OUString aTemp;
+ if ( osl::FileBase::getFileURLFromSystemPath( rURL, aTemp ) == osl::FileBase::E_None )
+ {
+ aURL = INetURLObject( aTemp ).GetMainURL( INetURLObject::DecodeMechanism::NONE );
+ if( aURL.isEmpty() )
+ return rURL;
+ }
+ else
+ {
+ // rURL is not a valid URL nor a osl system path. Give up and return error!
+ return rURL;
+ }
+ }
+
+ // Get transient predefined path variable $(work) value before starting resubstitution
+ m_aPreDefVars.m_FixedVar[ PREDEFVAR_WORK ] = GetWorkVariableValue();
+
+ for (;;)
+ {
+ bool bVariableFound = false;
+
+ for (auto const & i: m_aReSubstFixedVarOrder)
+ {
+ OUString aValue = m_aPreDefVars.m_FixedVar[i.eVariable];
+ sal_Int32 nPos = aURL.indexOf( aValue );
+ if ( nPos >= 0 )
+ {
+ bool bMatch = true;
+ if ( !aFixedVarTable[i.eVariable].bAbsPath )
+ {
+ // Special path variables as they can occur in the middle of a path. Only match if they
+ // describe a whole directory and not only a substring of a directory!
+ // (Ideally, all substitutions should stick to syntactical
+ // boundaries within the given URL, like not covering only
+ // part of a URL path segment; however, at least when saving
+ // an Impress document, one URL that is passed in is of the
+ // form <file:///.../share/palette%3Bfile:///.../user/
+ // config/standard.sob>, re-substituted to
+ // <$(inst)/share/palette%3B$(user)/config/standard.sob>.)
+ const sal_Unicode* pStr = aURL.getStr();
+
+ if ( nPos > 0 )
+ bMatch = ( aURL[ nPos-1 ] == '/' );
+
+ if ( bMatch )
+ {
+ if ( nPos + aValue.getLength() < aURL.getLength() )
+ bMatch = ( pStr[ nPos + aValue.getLength() ] == '/' );
+ }
+ }
+
+ if ( bMatch )
+ {
+ aURL = aURL.replaceAt(
+ nPos, aValue.getLength(),
+ m_aPreDefVars.m_FixedVarNames[i.eVariable]);
+ bVariableFound = true; // Resubstitution not finished yet!
+ break;
+ }
+ }
+ }
+
+ if ( !bVariableFound )
+ {
+ return aURL;
+ }
+ }
+}
+
+// This method support both request schemes "$("<varname>")" or "<varname>".
+OUString const & SubstitutePathVariables::impl_getSubstituteVariableValue( const OUString& rVariable )
+{
+ OUString aVariable;
+
+ sal_Int32 nPos = rVariable.indexOf( "$(" );
+ if ( nPos == -1 )
+ {
+ // Prepare variable name before hash map access
+ aVariable = "$(" + rVariable + ")";
+ }
+
+ VarNameToIndexMap::const_iterator pNTOIIter = m_aPreDefVarMap.find( ( nPos == -1 ) ? aVariable : rVariable );
+
+ // Fixed/Predefined variable
+ if ( pNTOIIter == m_aPreDefVarMap.end() )
+ {
+ throw NoSuchElementException("Unknown variable!", static_cast<cppu::OWeakObject *>(this));
+ }
+ PreDefVariable nIndex = pNTOIIter->second;
+ return m_aPreDefVars.m_FixedVar[static_cast<sal_Int32>(nIndex)];
+}
+
+void SubstitutePathVariables::SetPredefinedPathVariables()
+{
+
+ m_aPreDefVars.m_FixedVar[PREDEFVAR_BRANDBASEURL] = "$BRAND_BASE_DIR";
+ rtl::Bootstrap::expandMacros(
+ m_aPreDefVars.m_FixedVar[PREDEFVAR_BRANDBASEURL]);
+
+ // Get inspath and userpath from bootstrap mechanism in every case as file URL
+ ::utl::Bootstrap::PathStatus aState;
+ OUString sVal;
+
+ aState = utl::Bootstrap::locateUserData( sVal );
+ //There can be the valid case that there is no user installation.
+ //TODO: Is that still the case? (With OOo 3.4, "unopkg sync" was run as part
+ // of the setup. Then no user installation was required.)
+ //Therefore we do not assert here.
+ // It's not possible to detect when an empty value would actually be used.
+ // (note: getenv is a hack to detect if we're running in a unit test)
+ // Also, it's okay to have an empty user installation path in case of LOK
+ if (aState == ::utl::Bootstrap::PATH_EXISTS || getenv("SRC_ROOT") ||
+ (comphelper::LibreOfficeKit::isActive() && aState == ::utl::Bootstrap::PATH_VALID))
+ {
+ m_aPreDefVars.m_FixedVar[ PREDEFVAR_USERPATH ] = sVal;
+ }
+
+ // Set $(inst), $(instpath), $(insturl)
+ m_aPreDefVars.m_FixedVar[ PREDEFVAR_INSTPATH ] = m_aPreDefVars.m_FixedVar[PREDEFVAR_BRANDBASEURL];
+ m_aPreDefVars.m_FixedVar[ PREDEFVAR_INSTURL ] = m_aPreDefVars.m_FixedVar[ PREDEFVAR_INSTPATH ];
+ m_aPreDefVars.m_FixedVar[ PREDEFVAR_INST ] = m_aPreDefVars.m_FixedVar[ PREDEFVAR_INSTPATH ];
+ // New variable of hierarchy service (#i32656#)
+ m_aPreDefVars.m_FixedVar[ PREDEFVAR_BASEINSTURL ]= m_aPreDefVars.m_FixedVar[ PREDEFVAR_INSTPATH ];
+
+ // Set $(user), $(userpath), $(userurl)
+ m_aPreDefVars.m_FixedVar[ PREDEFVAR_USERURL ] = m_aPreDefVars.m_FixedVar[ PREDEFVAR_USERPATH ];
+ m_aPreDefVars.m_FixedVar[ PREDEFVAR_USER ] = m_aPreDefVars.m_FixedVar[ PREDEFVAR_USERPATH ];
+ // New variable of hierarchy service (#i32656#)
+ m_aPreDefVars.m_FixedVar[ PREDEFVAR_USERDATAURL ]= m_aPreDefVars.m_FixedVar[ PREDEFVAR_USERPATH ];
+
+ // Detect the program directory
+ // Set $(prog), $(progpath), $(progurl)
+ INetURLObject aProgObj(
+ m_aPreDefVars.m_FixedVar[PREDEFVAR_BRANDBASEURL] );
+ if ( !aProgObj.HasError() && aProgObj.insertName( u"" LIBO_BIN_FOLDER ) )
+ {
+ m_aPreDefVars.m_FixedVar[ PREDEFVAR_PROGPATH ] = aProgObj.GetMainURL(INetURLObject::DecodeMechanism::NONE);
+ m_aPreDefVars.m_FixedVar[ PREDEFVAR_PROGURL ] = m_aPreDefVars.m_FixedVar[ PREDEFVAR_PROGPATH ];
+ m_aPreDefVars.m_FixedVar[ PREDEFVAR_PROG ] = m_aPreDefVars.m_FixedVar[ PREDEFVAR_PROGPATH ];
+ }
+
+ // Set $(username)
+ OUString aSystemUser;
+ ::osl::Security aSecurity;
+ aSecurity.getUserName( aSystemUser, false );
+ m_aPreDefVars.m_FixedVar[ PREDEFVAR_USERNAME ] = aSystemUser;
+
+ // Detect the language type of the current office
+ m_aPreDefVars.m_eLanguageType = LANGUAGE_ENGLISH_US;
+ OUString aLocaleStr( utl::ConfigManager::getUILocale() );
+ m_aPreDefVars.m_eLanguageType = LanguageTag::convertToLanguageTypeWithFallback( aLocaleStr );
+ // We used to have an else branch here with a SAL_WARN, but that
+ // always fired in some unit tests when this code was built with
+ // debug=t, so it seems fairly pointless, especially as
+ // m_aPreDefVars.m_eLanguageType has been initialized to a
+ // default value above anyway.
+
+ // Set $(vlang)
+ m_aPreDefVars.m_FixedVar[ PREDEFVAR_VLANG ] = aLocaleStr;
+
+ // Set $(langid)
+ m_aPreDefVars.m_FixedVar[ PREDEFVAR_LANGID ] = OUString::number( static_cast<sal_uInt16>(m_aPreDefVars.m_eLanguageType) );
+
+ // Set the other pre defined path variables
+ // Set $(work)
+ m_aPreDefVars.m_FixedVar[ PREDEFVAR_WORK ] = GetWorkVariableValue();
+ m_aPreDefVars.m_FixedVar[ PREDEFVAR_HOME ] = GetHomeVariableValue();
+
+ // Set $(workdirurl) this is the value of the path PATH_WORK which doesn't make sense
+ // anymore because the path settings service has this value! It can deliver this value more
+ // quickly than the substitution service!
+ m_aPreDefVars.m_FixedVar[ PREDEFVAR_WORKDIRURL ] = GetWorkPath();
+
+ // Set $(path) variable
+ m_aPreDefVars.m_FixedVar[ PREDEFVAR_PATH ] = GetPathVariableValue();
+
+ // Set $(temp)
+ OUString aTmp;
+ osl::FileBase::getTempDirURL( aTmp );
+ m_aPreDefVars.m_FixedVar[ PREDEFVAR_TEMP ] = aTmp;
+}
+
+}
+
+extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface *
+com_sun_star_comp_framework_PathSubstitution_get_implementation(
+ css::uno::XComponentContext *,
+ css::uno::Sequence<css::uno::Any> const &)
+{
+ return cppu::acquire(new SubstitutePathVariables());
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/framework/source/services/taskcreatorsrv.cxx b/framework/source/services/taskcreatorsrv.cxx
new file mode 100644
index 000000000..12e7c2c34
--- /dev/null
+++ b/framework/source/services/taskcreatorsrv.cxx
@@ -0,0 +1,357 @@
+/* -*- 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 <helper/persistentwindowstate.hxx>
+#include <helper/tagwindowasmodified.hxx>
+#include <helper/titlebarupdate.hxx>
+#include <loadenv/targethelper.hxx>
+#include <taskcreatordefs.hxx>
+
+#include <com/sun/star/frame/Frame.hpp>
+#include <com/sun/star/frame/XFrame2.hpp>
+#include <com/sun/star/frame/XDesktop.hpp>
+#include <com/sun/star/awt/Rectangle.hpp>
+#include <com/sun/star/awt/Toolkit.hpp>
+#include <com/sun/star/awt/WindowDescriptor.hpp>
+#include <com/sun/star/awt/WindowAttribute.hpp>
+#include <com/sun/star/awt/VclWindowPeerAttribute.hpp>
+#include <com/sun/star/lang/XServiceInfo.hpp>
+#include <com/sun/star/lang/XSingleServiceFactory.hpp>
+
+#include <comphelper/sequenceashashmap.hxx>
+#include <comphelper/compbase.hxx>
+#include <cppuhelper/supportsservice.hxx>
+#include <svtools/colorcfg.hxx>
+#include <toolkit/helper/vclunohelper.hxx>
+#include <utility>
+#include <vcl/svapp.hxx>
+#include <vcl/window.hxx>
+
+using namespace framework;
+
+namespace {
+
+typedef comphelper::WeakComponentImplHelper<
+ css::lang::XServiceInfo,
+ css::lang::XSingleServiceFactory> TaskCreatorService_BASE;
+
+class TaskCreatorService : public TaskCreatorService_BASE
+{
+private:
+
+ /** @short the global uno service manager.
+ @descr Must be used to create own needed services.
+ */
+ css::uno::Reference< css::uno::XComponentContext > m_xContext;
+
+public:
+
+ explicit TaskCreatorService(css::uno::Reference< css::uno::XComponentContext > xContext);
+
+ virtual OUString SAL_CALL getImplementationName() override
+ {
+ return "com.sun.star.comp.framework.TaskCreator";
+ }
+
+ virtual sal_Bool SAL_CALL supportsService(OUString const & ServiceName) override
+ {
+ return cppu::supportsService(this, ServiceName);
+ }
+
+ virtual css::uno::Sequence<OUString> SAL_CALL getSupportedServiceNames() override
+ {
+ return {"com.sun.star.frame.TaskCreator"};
+ }
+
+ // XSingleServiceFactory
+ virtual css::uno::Reference< css::uno::XInterface > SAL_CALL createInstance() override;
+
+ virtual css::uno::Reference< css::uno::XInterface > SAL_CALL createInstanceWithArguments(const css::uno::Sequence< css::uno::Any >& lArguments) override;
+
+private:
+
+ css::uno::Reference< css::awt::XWindow > implts_createContainerWindow( const css::uno::Reference< css::awt::XWindow >& xParentWindow ,
+ const css::awt::Rectangle& aPosSize ,
+ bool bTopWindow );
+
+ void implts_applyDocStyleToWindow(const css::uno::Reference< css::awt::XWindow >& xWindow) const;
+
+ css::uno::Reference< css::frame::XFrame2 > implts_createFrame( const css::uno::Reference< css::frame::XFrame >& xParentFrame ,
+ const css::uno::Reference< css::awt::XWindow >& xContainerWindow ,
+ const OUString& sName );
+
+ void implts_establishWindowStateListener( const css::uno::Reference< css::frame::XFrame2 >& xFrame );
+ void implts_establishTitleBarUpdate( const css::uno::Reference< css::frame::XFrame2 >& xFrame );
+
+ void implts_establishDocModifyListener( const css::uno::Reference< css::frame::XFrame2 >& xFrame );
+
+ OUString impl_filterNames( const OUString& sName );
+};
+
+TaskCreatorService::TaskCreatorService(css::uno::Reference< css::uno::XComponentContext > xContext)
+ : m_xContext (std::move(xContext ))
+{
+}
+
+css::uno::Reference< css::uno::XInterface > SAL_CALL TaskCreatorService::createInstance()
+{
+ return createInstanceWithArguments(css::uno::Sequence< css::uno::Any >());
+}
+
+css::uno::Reference< css::uno::XInterface > SAL_CALL TaskCreatorService::createInstanceWithArguments(const css::uno::Sequence< css::uno::Any >& lArguments)
+{
+ ::comphelper::SequenceAsHashMap lArgs(lArguments);
+
+ css::uno::Reference< css::frame::XFrame > xParentFrame = lArgs.getUnpackedValueOrDefault(ARGUMENT_PARENTFRAME , css::uno::Reference< css::frame::XFrame >());
+ OUString sFrameName = lArgs.getUnpackedValueOrDefault(ARGUMENT_FRAMENAME , OUString() );
+ bool bVisible = lArgs.getUnpackedValueOrDefault(ARGUMENT_MAKEVISIBLE , false );
+ bool bCreateTopWindow = lArgs.getUnpackedValueOrDefault(ARGUMENT_CREATETOPWINDOW , true );
+ // only possize=[0,0,0,0] triggers default handling of vcl !
+ css::awt::Rectangle aPosSize = lArgs.getUnpackedValueOrDefault(ARGUMENT_POSSIZE , css::awt::Rectangle(0, 0, 0, 0) );
+ css::uno::Reference< css::awt::XWindow > xContainerWindow = lArgs.getUnpackedValueOrDefault(ARGUMENT_CONTAINERWINDOW , css::uno::Reference< css::awt::XWindow >() );
+ bool bSupportPersistentWindowState = lArgs.getUnpackedValueOrDefault(ARGUMENT_SUPPORTPERSISTENTWINDOWSTATE , false );
+ bool bEnableTitleBarUpdate = lArgs.getUnpackedValueOrDefault(ARGUMENT_ENABLE_TITLEBARUPDATE , true );
+ // If the frame is explicitly requested to be hidden.
+ bool bHidden = lArgs.getUnpackedValueOrDefault("HiddenForConversion", false);
+
+ // We use FrameName property to set it as API name of the new created frame later.
+ // But those frame names must be different from the set of special target names as e.g. _blank, _self etcpp !
+ OUString sRightName = impl_filterNames(sFrameName);
+
+ // if no external frame window was given ... create a new one.
+ if ( ! xContainerWindow.is())
+ {
+ css::uno::Reference< css::awt::XWindow > xParentWindow;
+ if (xParentFrame.is())
+ xParentWindow = xParentFrame->getContainerWindow();
+
+ // Parent has no own window ...
+ // So we have to create a top level window always !
+ if ( ! xParentWindow.is())
+ bCreateTopWindow = true;
+
+ xContainerWindow = implts_createContainerWindow(xParentWindow, aPosSize, bCreateTopWindow);
+ }
+
+ // #i53630#
+ // Mark all document windows as "special ones", so VCL can bind
+ // special features to it. Because VCL doesn't know anything about documents ...
+ // Note: Doing so it's no longer supported, that e.g. our wizards can use findFrame(_blank)
+ // to create it's previous frames. They must do it manually by using WindowDescriptor+Toolkit!
+ css::uno::Reference< css::frame::XDesktop > xDesktop(xParentFrame, css::uno::UNO_QUERY);
+ bool bTopLevelDocumentWindow = (
+ sRightName.isEmpty() &&
+ (
+ (! xParentFrame.is() ) ||
+ ( xDesktop.is() )
+ )
+ );
+ if (bTopLevelDocumentWindow)
+ implts_applyDocStyleToWindow(xContainerWindow);
+ //------------------->
+
+ // create the new frame
+ VclPtr<vcl::Window> pContainerWindow = VCLUnoHelper::GetWindow(xContainerWindow);
+ if (pContainerWindow && bHidden)
+ {
+ WindowExtendedStyle eStyle = pContainerWindow->GetExtendedStyle();
+ eStyle |= WindowExtendedStyle::DocHidden;
+ pContainerWindow->SetExtendedStyle(eStyle);
+ }
+ css::uno::Reference< css::frame::XFrame2 > xFrame = implts_createFrame(xParentFrame, xContainerWindow, sRightName);
+
+ // special freature:
+ // A special listener will restore pos/size states in case
+ // a component was loaded into the frame first time.
+ if (bSupportPersistentWindowState)
+ implts_establishWindowStateListener(xFrame);
+
+ // special feature: On Mac we need tagging the window in case
+ // the underlying model was modified.
+ // VCL will ignore our calls in case different platform then Mac
+ // is used ...
+ if (bTopLevelDocumentWindow)
+ implts_establishDocModifyListener (xFrame);
+
+ // special freature:
+ // A special listener will update title bar (text and icon)
+ // if component of frame will be changed.
+ if (bEnableTitleBarUpdate)
+ implts_establishTitleBarUpdate(xFrame);
+
+ // Make it visible directly here...
+ // if it's required from outside.
+ if (bVisible)
+ xContainerWindow->setVisible(bVisible);
+
+ return css::uno::Reference< css::uno::XInterface >(xFrame, css::uno::UNO_QUERY_THROW);
+}
+
+void TaskCreatorService::implts_applyDocStyleToWindow(const css::uno::Reference< css::awt::XWindow >& xWindow) const
+{
+ // SYNCHRONIZED ->
+ SolarMutexGuard aSolarGuard;
+ VclPtr<vcl::Window> pVCLWindow = VCLUnoHelper::GetWindow(xWindow);
+ if (pVCLWindow)
+ pVCLWindow->SetExtendedStyle(WindowExtendedStyle::Document);
+ // <- SYNCHRONIZED
+}
+
+css::uno::Reference< css::awt::XWindow > TaskCreatorService::implts_createContainerWindow( const css::uno::Reference< css::awt::XWindow >& xParentWindow ,
+ const css::awt::Rectangle& aPosSize ,
+ bool bTopWindow )
+{
+ // get toolkit to create task container window
+ css::uno::Reference< css::awt::XToolkit2 > xToolkit = css::awt::Toolkit::create( m_xContext );
+
+ // Check if child frames can be created really. We need at least a valid window at the parent frame ...
+ css::uno::Reference< css::awt::XWindowPeer > xParentWindowPeer;
+ if ( ! bTopWindow)
+ {
+ if ( ! xParentWindow.is())
+ bTopWindow = false;
+ else
+ xParentWindowPeer.set(xParentWindow, css::uno::UNO_QUERY_THROW);
+ }
+
+ // describe window properties.
+ css::awt::WindowDescriptor aDescriptor;
+ if (bTopWindow)
+ {
+ aDescriptor.Type = css::awt::WindowClass_TOP;
+ aDescriptor.WindowServiceName = "window";
+ aDescriptor.ParentIndex = -1;
+ aDescriptor.Parent.clear();
+ aDescriptor.Bounds = aPosSize;
+ aDescriptor.WindowAttributes = css::awt::WindowAttribute::BORDER |
+ css::awt::WindowAttribute::MOVEABLE |
+ css::awt::WindowAttribute::SIZEABLE |
+ css::awt::WindowAttribute::CLOSEABLE |
+ css::awt::VclWindowPeerAttribute::CLIPCHILDREN;
+ }
+ else
+ {
+ aDescriptor.Type = css::awt::WindowClass_TOP;
+ aDescriptor.WindowServiceName = "dockingwindow";
+ aDescriptor.ParentIndex = 1;
+ aDescriptor.Parent = xParentWindowPeer;
+ aDescriptor.Bounds = aPosSize;
+ aDescriptor.WindowAttributes = css::awt::VclWindowPeerAttribute::CLIPCHILDREN;
+ }
+
+ // create a new blank container window and get access to parent container to append new created task.
+ css::uno::Reference< css::awt::XWindowPeer > xPeer = xToolkit->createWindow( aDescriptor );
+ css::uno::Reference< css::awt::XWindow > xWindow ( xPeer, css::uno::UNO_QUERY_THROW );
+
+ sal_Int32 nBackground = 0xffffffff;
+
+ if (bTopWindow)
+ {
+ try
+ {
+ nBackground = sal_Int32(::svtools::ColorConfig().GetColorValue(::svtools::APPBACKGROUND).nColor);
+ }
+ catch (const css::uno::Exception &)
+ {
+ // Ignore
+ }
+ }
+ xPeer->setBackground(nBackground);
+
+ return xWindow;
+}
+
+css::uno::Reference< css::frame::XFrame2 > TaskCreatorService::implts_createFrame( const css::uno::Reference< css::frame::XFrame >& xParentFrame ,
+ const css::uno::Reference< css::awt::XWindow >& xContainerWindow,
+ const OUString& sName )
+{
+ // create new frame.
+ css::uno::Reference< css::frame::XFrame2 > xNewFrame = css::frame::Frame::create( m_xContext );
+
+ // Set window on frame.
+ // Do it before calling any other interface methods ...
+ // The new created frame must be initialized before you can do anything else there.
+ xNewFrame->initialize( xContainerWindow );
+
+ // Put frame to the frame tree.
+ // Note: The property creator/parent will be set on the new putted frame automatically ... by the parent container.
+ if (xParentFrame.is())
+ {
+ css::uno::Reference< css::frame::XFramesSupplier > xSupplier (xParentFrame, css::uno::UNO_QUERY_THROW);
+ css::uno::Reference< css::frame::XFrames > xContainer = xSupplier->getFrames();
+ xContainer->append( css::uno::Reference<css::frame::XFrame>(xNewFrame, css::uno::UNO_QUERY_THROW) );
+ }
+
+ // Set it's API name (if there is one from outside)
+ if (!sName.isEmpty())
+ xNewFrame->setName( sName );
+
+ return xNewFrame;
+}
+
+void TaskCreatorService::implts_establishWindowStateListener( const css::uno::Reference< css::frame::XFrame2 >& xFrame )
+{
+ // Special feature: It's allowed for frames using a top level window only!
+ // We must create a special listener service and couple it with the new created task frame.
+ // He will restore or save the window state of it ...
+ // See used classes for further information too.
+ rtl::Reference<PersistentWindowState> pPersistentStateHandler = new PersistentWindowState( m_xContext );
+
+ css::uno::Sequence< css::uno::Any > lInitData{ css::uno::Any(xFrame) };
+ pPersistentStateHandler->initialize(lInitData);
+}
+
+void TaskCreatorService::implts_establishDocModifyListener( const css::uno::Reference< css::frame::XFrame2 >& xFrame )
+{
+ // Special feature: It's allowed for frames using a top level window only!
+ // We must create a special listener service and couple it with the new created task frame.
+ // It will tag the window as modified if the underlying model was modified ...
+ rtl::Reference<TagWindowAsModified> pTag = new TagWindowAsModified();
+
+ css::uno::Sequence< css::uno::Any > lInitData{ css::uno::Any(xFrame) };
+ pTag->initialize(lInitData);
+}
+
+void TaskCreatorService::implts_establishTitleBarUpdate( const css::uno::Reference< css::frame::XFrame2 >& xFrame )
+{
+ rtl::Reference<TitleBarUpdate> pHelper = new TitleBarUpdate (m_xContext);
+
+ css::uno::Sequence< css::uno::Any > lInitData{ css::uno::Any(xFrame) };
+ pHelper->initialize(lInitData);
+}
+
+OUString TaskCreatorService::impl_filterNames( const OUString& sName )
+{
+ OUString sFiltered;
+ if (TargetHelper::isValidNameForFrame(sName))
+ sFiltered = sName;
+ return sFiltered;
+}
+
+}
+
+extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface *
+com_sun_star_comp_framework_TaskCreator_get_implementation(
+ css::uno::XComponentContext *context,
+ css::uno::Sequence<css::uno::Any> const &)
+{
+ return cppu::acquire(new TaskCreatorService(context));
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/framework/source/services/uriabbreviation.cxx b/framework/source/services/uriabbreviation.cxx
new file mode 100644
index 000000000..6faa0c8e3
--- /dev/null
+++ b/framework/source/services/uriabbreviation.cxx
@@ -0,0 +1,75 @@
+/* -*- 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 <services/uriabbreviation.hxx>
+
+#include <sal/config.h>
+
+#include <tools/urlobj.hxx>
+#include <cppuhelper/supportsservice.hxx>
+
+// framework namespace
+namespace framework
+{
+
+// XInterface, XTypeProvider, XServiceInfo
+
+OUString SAL_CALL UriAbbreviation::getImplementationName()
+{
+ return "com.sun.star.comp.framework.UriAbbreviation";
+}
+
+sal_Bool SAL_CALL UriAbbreviation::supportsService( const OUString& sServiceName )
+{
+ return cppu::supportsService(this, sServiceName);
+}
+
+css::uno::Sequence< OUString > SAL_CALL UriAbbreviation::getSupportedServiceNames()
+{
+ return { "com.sun.star.util.UriAbbreviation" };
+}
+
+UriAbbreviation::UriAbbreviation(css::uno::Reference< css::uno::XComponentContext > const & )
+{
+}
+
+// css::util::XStringAbbreviation:
+OUString SAL_CALL UriAbbreviation::abbreviateString(const css::uno::Reference< css::util::XStringWidth > & xStringWidth, ::sal_Int32 nWidth, const OUString & aString)
+{
+ OUString aResult( aString );
+ if ( xStringWidth.is() )
+ {
+ // Use INetURLObject to abbreviate URLs
+ INetURLObject aURL( aString );
+ aResult = aURL.getAbbreviated( xStringWidth, nWidth, INetURLObject::DecodeMechanism::Unambiguous );
+ }
+
+ return aResult;
+}
+
+} // namespace framework
+
+extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface*
+framework_UriAbbreviation_get_implementation(
+ css::uno::XComponentContext* context, css::uno::Sequence<css::uno::Any> const& )
+{
+ return cppu::acquire(new framework::UriAbbreviation(context));
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/framework/source/services/urltransformer.cxx b/framework/source/services/urltransformer.cxx
new file mode 100644
index 000000000..b4fd3de16
--- /dev/null
+++ b/framework/source/services/urltransformer.cxx
@@ -0,0 +1,310 @@
+/* -*- 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 <cppuhelper/implbase.hxx>
+#include <cppuhelper/supportsservice.hxx>
+#include <tools/urlobj.hxx>
+#include <rtl/ustrbuf.hxx>
+
+#include <com/sun/star/util/XURLTransformer.hpp>
+#include <com/sun/star/util/URL.hpp>
+#include <com/sun/star/lang/XServiceInfo.hpp>
+#include <com/sun/star/uno/XComponentContext.hpp>
+
+namespace {
+
+class URLTransformer : public ::cppu::WeakImplHelper< css::util::XURLTransformer, css::lang::XServiceInfo>
+{
+public:
+ URLTransformer() {}
+
+ virtual OUString SAL_CALL getImplementationName() override
+ {
+ return "com.sun.star.comp.framework.URLTransformer";
+ }
+
+ virtual sal_Bool SAL_CALL supportsService(OUString const & ServiceName) override
+ {
+ return cppu::supportsService(this, ServiceName);
+ }
+
+ virtual css::uno::Sequence<OUString> SAL_CALL getSupportedServiceNames() override
+ {
+ return {"com.sun.star.util.URLTransformer"};
+ }
+
+ virtual sal_Bool SAL_CALL parseStrict( css::util::URL& aURL ) override;
+
+ virtual sal_Bool SAL_CALL parseSmart( css::util::URL& aURL, const OUString& sSmartProtocol ) override;
+
+ virtual sal_Bool SAL_CALL assemble( css::util::URL& aURL ) override;
+
+ virtual OUString SAL_CALL getPresentation( const css::util::URL& aURL, sal_Bool bWithPassword ) override;
+};
+
+void lcl_ParserHelper(INetURLObject& _rParser, css::util::URL& _rURL,bool _bUseIntern)
+{
+ // Get all information about this URL.
+ _rURL.Protocol = INetURLObject::GetScheme( _rParser.GetProtocol() );
+ _rURL.User = _rParser.GetUser ( INetURLObject::DecodeMechanism::WithCharset );
+ _rURL.Password = _rParser.GetPass ( INetURLObject::DecodeMechanism::WithCharset );
+ _rURL.Server = _rParser.GetHost ( INetURLObject::DecodeMechanism::WithCharset );
+ _rURL.Port = static_cast<sal_Int16>(_rParser.GetPort());
+
+ sal_Int32 nCount = _rParser.getSegmentCount( false );
+ if ( nCount > 0 )
+ {
+ // Don't add last segment as it is the name!
+ --nCount;
+
+ OUStringBuffer aPath(128);
+ for ( sal_Int32 nIndex = 0; nIndex < nCount; nIndex++ )
+ {
+ aPath.append( '/');
+ aPath.append( _rParser.getName( nIndex, false, INetURLObject::DecodeMechanism::NONE ));
+ }
+
+ if ( nCount > 0 )
+ aPath.append( '/' ); // final slash!
+
+ _rURL.Path = aPath.makeStringAndClear();
+ _rURL.Name = _rParser.getName( INetURLObject::LAST_SEGMENT, false, INetURLObject::DecodeMechanism::NONE );
+ }
+ else
+ {
+ _rURL.Path = _rParser.GetURLPath( INetURLObject::DecodeMechanism::NONE );
+ _rURL.Name = _rParser.GetLastName();
+ }
+
+ _rURL.Arguments = _rParser.GetParam();
+ _rURL.Mark = _rParser.GetMark( INetURLObject::DecodeMechanism::WithCharset );
+
+ // INetURLObject supports only an intelligent method of parsing URL's. So write
+ // back Complete to have a valid encoded URL in all cases!
+ _rURL.Complete = _rParser.GetMainURL( INetURLObject::DecodeMechanism::NONE );
+ if ( _bUseIntern )
+ _rURL.Complete = _rURL.Complete.intern();
+
+ _rParser.SetMark( u"" );
+ _rParser.SetParam( u"" );
+
+ _rURL.Main = _rParser.GetMainURL( INetURLObject::DecodeMechanism::NONE );
+}
+
+// XURLTransformer
+sal_Bool SAL_CALL URLTransformer::parseStrict( css::util::URL& aURL )
+{
+ // Safe impossible cases.
+ if ( aURL.Complete.isEmpty() )
+ {
+ return false;
+ }
+ // Try to extract the protocol
+ sal_Int32 nURLIndex = aURL.Complete.indexOf( ':' );
+ if ( nURLIndex <= 1 )
+ return false;
+
+ std::u16string_view aProtocol = aURL.Complete.subView( 0, nURLIndex+1 );
+
+ // If INetURLObject knows this protocol let it parse
+ if ( INetURLObject::CompareProtocolScheme( aProtocol ) != INetProtocol::NotValid )
+ {
+ // Initialize parser with given URL.
+ INetURLObject aParser( aURL.Complete );
+
+ // Get all information about this URL.
+ INetProtocol eINetProt = aParser.GetProtocol();
+ if ( eINetProt == INetProtocol::NotValid )
+ {
+ return false;
+ }
+ else if ( !aParser.HasError() )
+ {
+ lcl_ParserHelper(aParser,aURL,false);
+ // Return "URL is parsed".
+ return true;
+ }
+ }
+ else
+ {
+ // Minimal support for unknown protocols. This is mandatory to support the "Protocol Handlers" implemented
+ // in framework!
+ aURL.Protocol = aProtocol;
+ aURL.Main = aURL.Complete;
+ aURL.Path = aURL.Complete.copy( nURLIndex+1 );
+
+ // Return "URL is parsed".
+ return true;
+ }
+
+ return false;
+}
+
+// XURLTransformer
+
+sal_Bool SAL_CALL URLTransformer::parseSmart( css::util::URL& aURL,
+ const OUString& sSmartProtocol )
+{
+ // Safe impossible cases.
+ if ( aURL.Complete.isEmpty() )
+ {
+ return false;
+ }
+
+ // Initialize parser with given URL.
+ INetURLObject aParser;
+
+ aParser.SetSmartProtocol( INetURLObject::CompareProtocolScheme( sSmartProtocol ));
+ bool bOk = aParser.SetSmartURL( aURL.Complete );
+ if ( bOk )
+ {
+ lcl_ParserHelper(aParser,aURL,true);
+ // Return "URL is parsed".
+ return true;
+ }
+ else
+ {
+ // Minimal support for unknown protocols. This is mandatory to support the "Protocol Handlers" implemented
+ // in framework!
+ if ( INetURLObject::CompareProtocolScheme( sSmartProtocol ) == INetProtocol::NotValid )
+ {
+ // Try to extract the protocol
+ sal_Int32 nIndex = aURL.Complete.indexOf( ':' );
+ if ( nIndex > 1 )
+ {
+ OUString aProtocol = aURL.Complete.copy( 0, nIndex+1 );
+
+ // If INetURLObject knows this protocol something is wrong as detected before =>
+ // give up and return false!
+ if ( INetURLObject::CompareProtocolScheme( aProtocol ) != INetProtocol::NotValid )
+ return false;
+ else
+ aURL.Protocol = aProtocol;
+ }
+ else
+ return false;
+
+ aURL.Main = aURL.Complete;
+ aURL.Path = aURL.Complete.copy( nIndex+1 );
+ return true;
+ }
+ else
+ return false;
+ }
+}
+
+// XURLTransformer
+sal_Bool SAL_CALL URLTransformer::assemble( css::util::URL& aURL )
+{
+ // Initialize parser.
+ INetURLObject aParser;
+
+ if ( INetURLObject::CompareProtocolScheme( aURL.Protocol ) != INetProtocol::NotValid )
+ {
+ OUStringBuffer aCompletePath( aURL.Path );
+
+ // Concat the name if it is provided, just support a final slash
+ if ( !aURL.Name.isEmpty() )
+ {
+ sal_Int32 nIndex = aURL.Path.lastIndexOf( '/' );
+ if ( nIndex == ( aURL.Path.getLength() -1 ))
+ aCompletePath.append( aURL.Name );
+ else
+ {
+ aCompletePath.append( '/' );
+ aCompletePath.append( aURL.Name );
+ }
+ }
+
+ bool bResult = aParser.ConcatData(
+ INetURLObject::CompareProtocolScheme( aURL.Protocol ) ,
+ aURL.User ,
+ aURL.Password ,
+ aURL.Server ,
+ aURL.Port ,
+ aCompletePath.makeStringAndClear() );
+
+ if ( !bResult )
+ return false;
+
+ // First parse URL WITHOUT ...
+ aURL.Main = aParser.GetMainURL( INetURLObject::DecodeMechanism::NONE );
+ // ...and then WITH parameter and mark.
+ aParser.SetParam( aURL.Arguments);
+ aParser.SetMark ( aURL.Mark, INetURLObject::EncodeMechanism::All );
+ aURL.Complete = aParser.GetMainURL( INetURLObject::DecodeMechanism::NONE );
+
+ // Return "URL is assembled".
+ return true;
+ }
+ else if ( !aURL.Protocol.isEmpty() )
+ {
+ // Minimal support for unknown protocols
+ aURL.Complete = aURL.Protocol + aURL.Path;
+ aURL.Main = aURL.Complete;
+ return true;
+ }
+
+ return false;
+}
+
+// XURLTransformer
+
+OUString SAL_CALL URLTransformer::getPresentation( const css::util::URL& aURL,
+ sal_Bool bWithPassword )
+{
+ // Safe impossible cases.
+ if ( aURL.Complete.isEmpty() )
+ {
+ return OUString();
+ }
+
+ // Check given URL
+ css::util::URL aTestURL = aURL;
+ bool bParseResult = parseSmart( aTestURL, aTestURL.Protocol );
+ if ( bParseResult )
+ {
+ if ( !bWithPassword && !aTestURL.Password.isEmpty() )
+ {
+ // Exchange password text with other placeholder string
+ aTestURL.Password = "<******>";
+ assemble( aTestURL );
+ }
+
+ // Convert internal URLs to "praesentation"-URLs!
+ OUString sPraesentationURL;
+ INetURLObject::translateToExternal( aTestURL.Complete, sPraesentationURL, INetURLObject::DecodeMechanism::Unambiguous );
+
+ return sPraesentationURL;
+ }
+ else
+ return OUString();
+}
+
+}
+
+extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface *
+com_sun_star_comp_framework_URLTransformer_get_implementation(
+ css::uno::XComponentContext *,
+ css::uno::Sequence<css::uno::Any> const &)
+{
+ return cppu::acquire(new URLTransformer());
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */