diff options
Diffstat (limited to 'framework/source/services')
-rw-r--r-- | framework/source/services/ContextChangeEventMultiplexer.cxx | 366 | ||||
-rw-r--r-- | framework/source/services/autorecovery.cxx | 4243 | ||||
-rw-r--r-- | framework/source/services/desktop.cxx | 1775 | ||||
-rw-r--r-- | framework/source/services/dispatchhelper.cxx | 218 | ||||
-rw-r--r-- | framework/source/services/frame.cxx | 3340 | ||||
-rw-r--r-- | framework/source/services/mediatypedetectionhelper.cxx | 92 | ||||
-rw-r--r-- | framework/source/services/modulemanager.cxx | 350 | ||||
-rw-r--r-- | framework/source/services/pathsettings.cxx | 1422 | ||||
-rw-r--r-- | framework/source/services/sessionlistener.cxx | 413 | ||||
-rw-r--r-- | framework/source/services/substitutepathvars.cxx | 695 | ||||
-rw-r--r-- | framework/source/services/taskcreatorsrv.cxx | 357 | ||||
-rw-r--r-- | framework/source/services/uriabbreviation.cxx | 75 | ||||
-rw-r--r-- | framework/source/services/urltransformer.cxx | 310 |
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: */ |