compilerplugins/clang/badstatics.cxx                   |    4 
 include/sfx2/docfile.hxx                               |   12 
 offapi/UnoApi_offapi.mk                                |    2 
 offapi/com/sun/star/document/ReadOnlyOpenRequest.idl   |   51 +
 offapi/com/sun/star/document/ReloadEditableRequest.idl |   50 +
 sfx2/source/doc/docfile.cxx                            |  517 ++++++++++++++++-
 sfx2/source/view/viewfrm.cxx                           |   15 
 uui/Library_uui.mk                                     |    2 
 uui/inc/strings.hrc                                    |   21 
 uui/source/alreadyopen.cxx                             |    1 
 uui/source/iahndl-locking.cxx                          |  104 +++
 uui/source/iahndl.cxx                                  |    6 
 uui/source/iahndl.hxx                                  |    6 
 uui/source/lockcorrupt.cxx                             |    1 
 uui/source/lockfailed.cxx                              |    1 
 uui/source/openlocked.cxx                              |    1 
 uui/source/readonlyopen.cxx                            |   38 +
 uui/source/readonlyopen.hxx                            |   35 +
 uui/source/reloadeditable.cxx                          |   37 +
 uui/source/reloadeditable.hxx                          |   35 +
 20 files changed, 919 insertions(+), 20 deletions(-)

New commits:
commit 95eb088802562b75f8b299908160145c7e88d763
Author:     Matt K <matt...@gmail.com>
AuthorDate: Fri Feb 26 10:24:38 2021 -0600
Commit:     Mike Kaganski <mike.kagan...@collabora.com>
CommitDate: Thu May 27 12:31:38 2021 +0200

    tdf#47065 Add new file open UI options and implement a new thread
    
    Add new UI options when opening a locked or non-writeable document
    to allow the user to be notified when such a document becomes editable
    .  If the user selects "Notify", then that document is added to a list
    of open documents to be checked by a thread every 60 seconds for
    read/write access and whether lock file is available/obtainable. If
    access is allowed for a document, then show UI dialog to the user
    asking to Reload that document.  If Reload is selected by the user
    then that document is reloaded with read/write access.  The checking
    thread is spawned only once no matter how many "Notify" documents
    there are.  The thread is spawned if not already running when a new
    "Notify" document is opened, and it terminates when all "Notify"
    documents have been closed or the application terminates.
    
    Also update badstatics clang plugin to ignore new global variables
    introduced.
    
    Change-Id: I7555ce6f5df79c2c87216e0129ef3b2883c7d921
    Reviewed-on: https://gerrit.libreoffice.org/c/core/+/111654
    Tested-by: Jenkins
    Reviewed-by: Mike Kaganski <mike.kagan...@collabora.com>

diff --git a/compilerplugins/clang/badstatics.cxx 
b/compilerplugins/clang/badstatics.cxx
index bb6241eafa5e..0856d8faac39 100644
--- a/compilerplugins/clang/badstatics.cxx
+++ b/compilerplugins/clang/badstatics.cxx
@@ -218,6 +218,10 @@ public:
                    // Windows-only extensions/source/scanner/scanwin.cxx, 
problematic
                    // Twain::mpThread -> ShimListenerThread::mxTopWindow 
released via Twain::Reset
                    // clearing mpThread
+                || name == "g_newReadOnlyDocs"
+                   // sfx2/source/doc/docfile.cxx, warning about map's key
+                || name == "g_existingReadOnlyDocs"
+                   // sfx2/source/doc/docfile.cxx, warning about map's key
                ) // these variables appear unproblematic
             {
                 return true;
diff --git a/include/sfx2/docfile.hxx b/include/sfx2/docfile.hxx
index ad316dd2fca8..83bcb91c5812 100644
--- a/include/sfx2/docfile.hxx
+++ b/include/sfx2/docfile.hxx
@@ -30,6 +30,7 @@
 #include <svl/itemset.hxx>
 #include <tools/link.hxx>
 #include <tools/stream.hxx>
+#include <mutex>
 
 namespace com::sun::star::beans { struct PropertyValue; }
 namespace com::sun::star::embed { class XStorage; }
@@ -53,6 +54,7 @@ class SfxMedium_Impl;
 class INetURLObject;
 class SfxFrame;
 class DateTime;
+struct ImplSVEvent;
 
 namespace weld
 {
@@ -93,6 +95,15 @@ public:
 
                         virtual ~SfxMedium() override;
 
+    DECL_STATIC_LINK(SfxMedium, ShowReloadEditableDialog, void*, void);
+    bool CheckCanGetLockfile() const;
+    void SetOriginallyReadOnly(bool val);
+    void AddToCheckEditableWorkerList();
+    void SetWorkerReloadEvent(ImplSVEvent* pEvent);
+    ImplSVEvent* GetWorkerReloadEvent() const;
+    std::shared_ptr<std::recursive_mutex> GetCheckEditableMutex() const;
+    void CancelCheckEditableEntry(bool bRemoveEvent = true);
+
     void                UseInteractionHandler( bool );
     css::uno::Reference< css::task::XInteractionHandler >
                         GetInteractionHandler( bool bGetAlways = false );
@@ -291,6 +302,7 @@ private:
                                             bool bIsLoading, bool bOwnLock, 
bool bHandleSysLocked);
     enum class MessageDlg { LockFileIgnore, LockFileCorrupt };
     bool                ShowLockFileProblemDialog(MessageDlg nWhichDlg);
+    bool ShowReadOnlyOpenDialog();
 
 };
 
diff --git a/offapi/UnoApi_offapi.mk b/offapi/UnoApi_offapi.mk
index 4180c1194eff..01dd48280fea 100644
--- a/offapi/UnoApi_offapi.mk
+++ b/offapi/UnoApi_offapi.mk
@@ -2211,7 +2211,9 @@ $(eval $(call 
gb_UnoApi_add_idlfiles,offapi,com/sun/star/document,\
        NoSuchFilterRequest \
        OwnLockOnDocumentRequest \
        PrinterIndependentLayout \
+       ReadOnlyOpenRequest \
        RedlineDisplayType \
+       ReloadEditableRequest \
        UndoContextNotClosedException \
        UndoFailedException \
        UndoManagerEvent \
diff --git a/offapi/com/sun/star/document/ReadOnlyOpenRequest.idl 
b/offapi/com/sun/star/document/ReadOnlyOpenRequest.idl
new file mode 100644
index 000000000000..49a82b7016f9
--- /dev/null
+++ b/offapi/com/sun/star/document/ReadOnlyOpenRequest.idl
@@ -0,0 +1,51 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; 
fill-column: 100 -*- */
+/*
+ * 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 .
+ */
+#ifndef __com_sun_star_document_ReadOnlyOpenRequest_idl__
+#define __com_sun_star_document_ReadOnlyOpenRequest_idl__
+
+#include <com/sun/star/uno/Exception.idl>
+
+module com
+{
+    module sun
+    {
+        module star
+        {
+            module document
+            {
+                /** Is used for interaction handle to query user decision 
regarding whether to open
+                    a document read-only and whether to notify the user when 
the document becomes
+                    editable.
+
+                    @since LibreOffice 7.2
+                */
+                published exception ReadOnlyOpenRequest : 
::com::sun::star::uno::Exception
+                {
+                    /** The URL of the document that is open but was made 
editable.
+                    */
+                    string DocumentURL;
+                };
+            };
+        };
+    };
+};
+
+#endif
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s 
cinkeys+=0=break: */
diff --git a/offapi/com/sun/star/document/ReloadEditableRequest.idl 
b/offapi/com/sun/star/document/ReloadEditableRequest.idl
new file mode 100644
index 000000000000..f091bcf26f69
--- /dev/null
+++ b/offapi/com/sun/star/document/ReloadEditableRequest.idl
@@ -0,0 +1,50 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; 
fill-column: 100 -*- */
+/*
+ * 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 .
+ */
+#ifndef __com_sun_star_document_ReloadEditableRequest_idl__
+#define __com_sun_star_document_ReloadEditableRequest_idl__
+
+#include <com/sun/star/uno/Exception.idl>
+
+module com
+{
+    module sun
+    {
+        module star
+        {
+            module document
+            {
+                /** Is used for interaction handle to query user decision 
regarding reloading a
+                    document that was recently made editable.
+
+                    @since LibreOffice 7.2
+                */
+                published exception ReloadEditableRequest : 
::com::sun::star::uno::Exception
+                {
+                    /** The URL of the document that is open but was made 
editable.
+                    */
+                    string DocumentURL;
+                };
+            };
+        };
+    };
+};
+
+#endif
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s 
cinkeys+=0=break: */
diff --git a/sfx2/source/doc/docfile.cxx b/sfx2/source/doc/docfile.cxx
index 45aaad485f83..c7655e2c5f62 100644
--- a/sfx2/source/doc/docfile.cxx
+++ b/sfx2/source/doc/docfile.cxx
@@ -38,11 +38,15 @@
 #include <com/sun/star/document/LockFileIgnoreRequest.hpp>
 #include <com/sun/star/document/LockFileCorruptRequest.hpp>
 #include <com/sun/star/document/ChangedByOthersRequest.hpp>
+#include <com/sun/star/document/ReadOnlyOpenRequest.hpp>
+#include <com/sun/star/document/ReloadEditableRequest.hpp>
 #include <com/sun/star/embed/XTransactedObject.hpp>
 #include <com/sun/star/embed/ElementModes.hpp>
 #include <com/sun/star/embed/UseBackupException.hpp>
 #include <com/sun/star/embed/XOptimizedStorage.hpp>
+#include <com/sun/star/frame/Desktop.hpp>
 #include <com/sun/star/frame/XModel.hpp>
+#include <com/sun/star/frame/XTerminateListener.hpp>
 #include <com/sun/star/graphic/XGraphic.hpp>
 #include <com/sun/star/ucb/ContentCreationException.hpp>
 #include <com/sun/star/ucb/InteractiveIOException.hpp>
@@ -108,6 +112,7 @@
 
 #include <sfx2/app.hxx>
 #include <sfx2/frame.hxx>
+#include <sfx2/dispatch.hxx>
 #include <sfx2/fcontnr.hxx>
 #include <sfx2/docfilt.hxx>
 #include <sfx2/sfxsids.hrc>
@@ -120,6 +125,10 @@
 #include <tools/diagnose_ex.h>
 #include <unotools/fltrcfg.hxx>
 #include <sfx2/digitalsignatures.hxx>
+#include <sfx2/viewfrm.hxx>
+#include <comphelper/threadpool.hxx>
+#include <condition_variable>
+#include <comphelper/scopeguard.hxx>
 
 #include <com/sun/star/io/WrongFormatException.hpp>
 
@@ -133,6 +142,28 @@ using namespace ::com::sun::star::beans;
 using namespace ::com::sun::star::io;
 using namespace ::com::sun::star::security;
 
+namespace
+{
+
+struct ReadOnlyMediumEntry
+{
+    ReadOnlyMediumEntry(std::shared_ptr<std::recursive_mutex> pMutex,
+                        std::shared_ptr<bool> pIsDestructed)
+        : _pMutex(pMutex)
+        , _pIsDestructed(pIsDestructed)
+    {
+    }
+    std::shared_ptr<std::recursive_mutex> _pMutex;
+    std::shared_ptr<bool> _pIsDestructed;
+};
+
+}
+
+static std::mutex g_chkReadOnlyGlobalMutex;
+static bool g_bChkReadOnlyTaskRunning = false;
+static std::unordered_map<SfxMedium*, std::shared_ptr<ReadOnlyMediumEntry>> 
g_newReadOnlyDocs;
+static std::unordered_map<SfxMedium*, std::shared_ptr<ReadOnlyMediumEntry>> 
g_existingReadOnlyDocs;
+
 namespace {
 
 #if HAVE_FEATURE_MULTIUSER_ENVIRONMENT
@@ -232,8 +263,85 @@ bool IsFileMovable(const INetURLObject& rURL)
 #endif
 }
 
+class CheckReadOnlyTaskTerminateListener
+    : public ::cppu::WeakImplHelper<css::frame::XTerminateListener>
+{
+public:
+    // XEventListener
+    void SAL_CALL disposing(const css::lang::EventObject& Source) override;
+
+    // XTerminateListener
+    void SAL_CALL queryTermination(const css::lang::EventObject& aEvent) 
override;
+    void SAL_CALL notifyTermination(const css::lang::EventObject& aEvent) 
override;
+
+    bool bIsTerminated = false;
+    std::condition_variable mCond;
+    std::mutex mMutex;
+};
+
+class CheckReadOnlyTask : public comphelper::ThreadTask
+{
+public:
+    CheckReadOnlyTask(const std::shared_ptr<comphelper::ThreadTaskTag>& pTag);
+    ~CheckReadOnlyTask();
+
+    virtual void doWork() override;
+
+private:
+    rtl::Reference<CheckReadOnlyTaskTerminateListener> m_xListener;
+};
+
 } // anonymous namespace
 
+CheckReadOnlyTask::CheckReadOnlyTask(const 
std::shared_ptr<comphelper::ThreadTaskTag>& pTag)
+    : ThreadTask(pTag)
+    , m_xListener(new CheckReadOnlyTaskTerminateListener)
+{
+    Reference<css::frame::XDesktop> xDesktop
+        = 
css::frame::Desktop::create(comphelper::getProcessComponentContext());
+    if (xDesktop.is() && m_xListener != nullptr)
+    {
+        xDesktop->addTerminateListener(m_xListener);
+    }
+}
+
+CheckReadOnlyTask::~CheckReadOnlyTask()
+{
+    Reference<css::frame::XDesktop> xDesktop
+        = 
css::frame::Desktop::create(comphelper::getProcessComponentContext());
+    if (xDesktop.is() && m_xListener != nullptr)
+    {
+        std::unique_lock<std::mutex> lock(m_xListener->mMutex);
+        if (!m_xListener->bIsTerminated)
+        {
+            lock.unlock();
+            xDesktop->removeTerminateListener(m_xListener);
+        }
+    }
+}
+
+namespace
+{
+void SAL_CALL
+CheckReadOnlyTaskTerminateListener::disposing(const css::lang::EventObject& 
/*Source*/)
+{
+}
+
+void SAL_CALL
+CheckReadOnlyTaskTerminateListener::queryTermination(const 
css::lang::EventObject& /*aEvent*/)
+{
+}
+
+void SAL_CALL
+CheckReadOnlyTaskTerminateListener::notifyTermination(const 
css::lang::EventObject& /*aEvent*/)
+{
+    std::unique_lock<std::mutex> lock(mMutex);
+    bIsTerminated = true;
+    lock.unlock();
+    mCond.notify_one();
+}
+}
+
 class SfxMedium_Impl
 {
 public:
@@ -263,6 +371,7 @@ public:
     bool m_bInputStreamIsReadOnly:1;
     bool m_bInCheckIn:1;
     bool m_bDisableFileSync = false;
+    bool m_bNotifyWhenEditable = false;
 
     OUString m_aName;
     OUString m_aLogicName;
@@ -274,6 +383,10 @@ public:
     std::shared_ptr<const SfxFilter> m_pFilter;
     std::shared_ptr<const SfxFilter> m_pCustomFilter;
 
+    std::shared_ptr<std::recursive_mutex> m_pCheckEditableWorkerMutex;
+    std::shared_ptr<bool> m_pIsDestructed;
+    ImplSVEvent* m_pReloadEvent;
+
     std::unique_ptr<SvStream> m_pInStream;
     std::unique_ptr<SvStream> m_pOutStream;
 
@@ -320,7 +433,6 @@ public:
         { return !m_pFilter ? OUString() : m_pFilter->GetMimeType(); }
 };
 
-
 SfxMedium_Impl::SfxMedium_Impl() :
     m_nStorOpenMode(SFX_STREAM_READWRITE),
     m_eError(ERRCODE_NONE),
@@ -345,6 +457,7 @@ SfxMedium_Impl::SfxMedium_Impl() :
     m_bRemote(false),
     m_bInputStreamIsReadOnly(false),
     m_bInCheckIn(false),
+    m_pReloadEvent(nullptr),
     aExpireTime( DateTime( DateTime::SYSTEM ) + static_cast<sal_Int32>(10) ),
     nLastStorageError( ERRCODE_NONE ),
     m_nSignatureState( SignatureState::NOSIGNATURES )
@@ -359,6 +472,9 @@ SfxMedium_Impl::~SfxMedium_Impl()
 
     pTempFile.reset();
     m_pSet.reset();
+    std::unique_lock<std::recursive_mutex> chkEditLock;
+    if (m_pCheckEditableWorkerMutex != nullptr)
+        chkEditLock = 
std::unique_lock<std::recursive_mutex>(*m_pCheckEditableWorkerMutex);
     m_pURLObj.reset();
 }
 
@@ -994,6 +1110,7 @@ SfxMedium::ShowLockResult 
SfxMedium::ShowLockedDocumentDialog(const LockFileEntr
 
         xHandler->handle( xInteractionRequestImpl );
 
+        bool bOpenReadOnly = false;
         ::rtl::Reference< ::ucbhelper::InteractionContinuation > xSelected = 
xInteractionRequestImpl->getSelection();
         if ( uno::Reference< task::XInteractionAbort >( xSelected.get(), 
uno::UNO_QUERY ).is() )
         {
@@ -1018,15 +1135,26 @@ SfxMedium::ShowLockResult 
SfxMedium::ShowLockedDocumentDialog(const LockFileEntr
             // User decided to ignore the alien (stale?) lock file without 
filesystem lock
             nResult = ShowLockResult::Succeeded;
         }
-        else // if ( XSelected == aContinuations[1] )
+        else if (uno::Reference< task::XInteractionApprove >( xSelected.get(), 
uno::UNO_QUERY ).is())
+        {
+            bOpenReadOnly = true;
+        }
+        else // user selected "Notify"
+        {
+            pImpl->m_bNotifyWhenEditable = true;
+            AddToCheckEditableWorkerList();
+            bOpenReadOnly = true;
+        }
+
+        if (bOpenReadOnly)
         {
             // own lock on loading, user has selected to open readonly
             // own lock on saving, user has selected to open readonly
             // alien lock on loading, user has selected to retry saving
             // TODO/LATER: alien lock on saving, user has selected to retry 
saving
 
-            if ( bIsLoading )
-                GetItemSet()->Put( SfxBoolItem( SID_DOC_READONLY, true ) );
+            if (bIsLoading)
+                GetItemSet()->Put(SfxBoolItem(SID_DOC_READONLY, true));
             else
                 nResult = ShowLockResult::Try;
         }
@@ -1048,6 +1176,42 @@ SfxMedium::ShowLockResult 
SfxMedium::ShowLockedDocumentDialog(const LockFileEntr
     return nResult;
 }
 
+bool SfxMedium::ShowReadOnlyOpenDialog()
+{
+    uno::Reference<task::XInteractionHandler> xHandler = 
GetInteractionHandler();
+    if (xHandler.is())
+    {
+        OUString aDocumentURL
+            = 
GetURLObject().GetLastName(INetURLObject::DecodeMechanism::WithCharset);
+        ::rtl::Reference<::ucbhelper::InteractionRequest> 
xInteractionRequestImpl
+            = new 
::ucbhelper::InteractionRequest(uno::makeAny(document::ReadOnlyOpenRequest(
+                OUString(), uno::Reference<uno::XInterface>(), aDocumentURL)));
+        if (xInteractionRequestImpl != nullptr)
+        {
+            sal_Int32 nContinuations = 2;
+            uno::Sequence<uno::Reference<task::XInteractionContinuation>> 
aContinuations(
+                nContinuations);
+            aContinuations[0] = new 
::ucbhelper::InteractionAbort(xInteractionRequestImpl.get());
+            aContinuations[1] = new 
::ucbhelper::InteractionApprove(xInteractionRequestImpl.get());
+            xInteractionRequestImpl->setContinuations(aContinuations);
+            xHandler->handle(xInteractionRequestImpl);
+            ::rtl::Reference<::ucbhelper::InteractionContinuation> xSelected
+                = xInteractionRequestImpl->getSelection();
+            if (uno::Reference<task::XInteractionAbort>(xSelected.get(), 
uno::UNO_QUERY).is())
+            {
+                SetError(ERRCODE_ABORT);
+                return false;
+            }
+            else if 
(!uno::Reference<task::XInteractionApprove>(xSelected.get(), uno::UNO_QUERY)
+                         .is())
+                // user selected "Notify"
+                pImpl->m_bNotifyWhenEditable = true;
+
+            return true;
+        }
+    }
+    return false;
+}
 
 bool SfxMedium::ShowLockFileProblemDialog(MessageDlg nWhichDlg)
 {
@@ -1076,17 +1240,23 @@ bool SfxMedium::ShowLockFileProblemDialog(MessageDlg 
nWhichDlg)
         xHandler->handle(xIgnoreRequestImpl);
 
         ::rtl::Reference< ::ucbhelper::InteractionContinuation > xSelected = 
xIgnoreRequestImpl->getSelection();
-        bool bReadOnly = uno::Reference< task::XInteractionApprove 
>(xSelected.get(), uno::UNO_QUERY).is();
+        bool bReadOnly = true;
 
-        if (bReadOnly)
+        if (uno::Reference<task::XInteractionAbort>(xSelected.get(), 
uno::UNO_QUERY).is())
         {
-            GetItemSet()->Put(SfxBoolItem(SID_DOC_READONLY, true));
+            SetError(ERRCODE_ABORT);
+            bReadOnly = false;
         }
-        else
+        else if (!uno::Reference<task::XInteractionApprove>(xSelected.get(), 
uno::UNO_QUERY).is())
         {
-            SetError(ERRCODE_ABORT);
+            // user selected "Notify"
+            pImpl->m_bNotifyWhenEditable = true;
+            AddToCheckEditableWorkerList();
         }
 
+        if (bReadOnly)
+            GetItemSet()->Put(SfxBoolItem(SID_DOC_READONLY, true));
+
         return bReadOnly;
     }
 
@@ -1483,6 +1653,10 @@ SfxMedium::LockFileResult 
SfxMedium::LockOrigFileOnDemand(bool bLoading, bool bN
                     bResult = !bContentReadonly;
                 }
             }
+            else // read-only
+            {
+                AddToCheckEditableWorkerList();
+            }
         }
 
         if ( !bResult && GetError() == ERRCODE_NONE )
@@ -2775,7 +2949,13 @@ void SfxMedium::Init_Impl()
         {
             if ( aUrl.HasMark() )
             {
+                std::unique_lock<std::recursive_mutex> chkEditLock;
+                if (pImpl->m_pCheckEditableWorkerMutex != nullptr)
+                    chkEditLock = std::unique_lock<std::recursive_mutex>(
+                        *(pImpl->m_pCheckEditableWorkerMutex));
                 pImpl->m_aLogicName = aUrl.GetURLNoMark( 
INetURLObject::DecodeMechanism::NONE );
+                if (chkEditLock.owns_lock())
+                    chkEditLock.unlock();
                 GetItemSet()->Put( SfxStringItem( SID_JUMPMARK, aUrl.GetMark() 
) );
             }
 
@@ -2792,8 +2972,14 @@ void SfxMedium::Init_Impl()
 
     if ( pSalvageItem && !pSalvageItem->GetValue().isEmpty() )
     {
+        std::unique_lock<std::recursive_mutex> chkEditLock;
+        if (pImpl->m_pCheckEditableWorkerMutex != nullptr)
+            chkEditLock
+                = 
std::unique_lock<std::recursive_mutex>(*(pImpl->m_pCheckEditableWorkerMutex));
         pImpl->m_aLogicName = pSalvageItem->GetValue();
         pImpl->m_pURLObj.reset();
+        if (chkEditLock.owns_lock())
+            chkEditLock.unlock();
         pImpl->m_bSalvageMode = true;
     }
 
@@ -2829,7 +3015,12 @@ void SfxMedium::Init_Impl()
         if (item.getFileStatus(stat) == osl::FileBase::E_None
             && stat.isValid(osl_FileStatus_Mask_Attributes))
         {
-            if ((stat.getAttributes() & osl_File_Attribute_ReadOnly) != 0) {
+            if ((stat.getAttributes() & osl_File_Attribute_ReadOnly) != 0
+#if HAVE_FEATURE_MULTIUSER_ENVIRONMENT
+                && ShowReadOnlyOpenDialog()
+#endif
+                )
+            {
                 pImpl->m_bOriginallyReadOnly = true;
             }
         }
@@ -2880,7 +3071,6 @@ SfxMedium::GetInteractionHandler( bool bGetAlways )
     return pImpl->xInteraction;
 }
 
-
 void SfxMedium::SetFilter( const std::shared_ptr<const SfxFilter>& pFilter )
 {
     pImpl->m_pFilter = pFilter;
@@ -3127,8 +3317,13 @@ void SfxMedium::SetName( const OUString& aNameP, bool 
bSetOrigURL )
         pImpl->aOrigURL = pImpl->m_aLogicName;
     if( bSetOrigURL )
         pImpl->aOrigURL = aNameP;
+    std::unique_lock<std::recursive_mutex> chkEditLock;
+    if (pImpl->m_pCheckEditableWorkerMutex != nullptr)
+        chkEditLock = 
std::unique_lock<std::recursive_mutex>(*(pImpl->m_pCheckEditableWorkerMutex));
     pImpl->m_aLogicName = aNameP;
     pImpl->m_pURLObj.reset();
+    if (chkEditLock.owns_lock())
+        chkEditLock.unlock();
     pImpl->aContent = ::ucbhelper::Content();
     Init_Impl();
 }
@@ -3155,7 +3350,6 @@ void SfxMedium::SetPhysicalName_Impl( const OUString& 
rNameP )
     }
 }
 
-
 void SfxMedium::ReOpen()
 {
     bool bUseInteractionHandler = pImpl->bUseInteractionHandler;
@@ -3164,7 +3358,6 @@ void SfxMedium::ReOpen()
     pImpl->bUseInteractionHandler = bUseInteractionHandler;
 }
 
-
 void SfxMedium::CompleteReOpen()
 {
     // do not use temporary file for reopen and in case of success throw the 
temporary file away
@@ -3336,9 +3529,11 @@ SfxMedium::SfxMedium( const uno::Reference < 
embed::XStorage >& rStor, const OUS
         GetItemSet()->Put( *p );
 }
 
-
+// NOTE: should only be called on main thread
 SfxMedium::~SfxMedium()
 {
+    CancelCheckEditableEntry();
+
     // if there is a requirement to clean the backup this is the last 
possibility to do it
     ClearBackup_Impl();
 
@@ -3367,6 +3562,10 @@ const OUString& SfxMedium::GetName() const
 
 const INetURLObject& SfxMedium::GetURLObject() const
 {
+    std::unique_lock<std::recursive_mutex> chkEditLock;
+    if (pImpl->m_pCheckEditableWorkerMutex != nullptr)
+        chkEditLock = 
std::unique_lock<std::recursive_mutex>(*(pImpl->m_pCheckEditableWorkerMutex));
+
     if (!pImpl->m_pURLObj)
     {
         pImpl->m_pURLObj.reset( new INetURLObject( pImpl->m_aLogicName ) );
@@ -3589,6 +3788,11 @@ bool SfxMedium::IsOriginallyReadOnly() const
     return pImpl->m_bOriginallyReadOnly;
 }
 
+void SfxMedium::SetOriginallyReadOnly(bool val)
+{
+    pImpl->m_bOriginallyReadOnly = val;
+}
+
 bool SfxMedium::IsOriginallyLoadedReadOnly() const
 {
     return pImpl->m_bOriginallyLoadedReadOnly;
@@ -4302,4 +4506,289 @@ bool SfxMedium::IsInCheckIn( ) const
     return pImpl->m_bInCheckIn;
 }
 
+// should only be called on main thread
+std::shared_ptr<std::recursive_mutex> SfxMedium::GetCheckEditableMutex() const
+{
+    return pImpl->m_pCheckEditableWorkerMutex;
+}
+
+// should only be called while holding pImpl->m_pCheckEditableWorkerMutex
+void SfxMedium::SetWorkerReloadEvent(ImplSVEvent* pEvent)
+{
+    pImpl->m_pReloadEvent = pEvent;
+}
+
+// should only be called while holding pImpl->m_pCheckEditableWorkerMutex
+ImplSVEvent* SfxMedium::GetWorkerReloadEvent() const
+{
+    return pImpl->m_pReloadEvent;
+}
+
+// should only be called on main thread
+void SfxMedium::AddToCheckEditableWorkerList()
+{
+    if (!pImpl->m_bNotifyWhenEditable)
+        return;
+
+    CancelCheckEditableEntry();
+
+    if (pImpl->m_pCheckEditableWorkerMutex == nullptr)
+    {
+        pImpl->m_pCheckEditableWorkerMutex = 
std::make_shared<std::recursive_mutex>();
+        if (pImpl->m_pCheckEditableWorkerMutex == nullptr)
+            return;
+    }
+
+    pImpl->m_pIsDestructed = std::make_shared<bool>(false);
+    if (pImpl->m_pIsDestructed == nullptr)
+        return;
+
+    std::unique_lock<std::mutex> globalLock(g_chkReadOnlyGlobalMutex);
+    if (g_newReadOnlyDocs.find(this) == g_newReadOnlyDocs.end())
+    {
+        bool bAddNewEntry = false;
+        if (!g_bChkReadOnlyTaskRunning)
+        {
+            std::shared_ptr<comphelper::ThreadTaskTag> pTag
+                = comphelper::ThreadPool::createThreadTaskTag();
+            if (pTag != nullptr)
+            {
+                g_bChkReadOnlyTaskRunning = true;
+                bAddNewEntry = true;
+                comphelper::ThreadPool::getSharedOptimalPool().pushTask(
+                    std::make_unique<CheckReadOnlyTask>(pTag));
+            }
+        }
+        else
+            bAddNewEntry = true;
+
+        if (bAddNewEntry)
+        {
+            std::shared_ptr<ReadOnlyMediumEntry> newEntry = 
std::make_shared<ReadOnlyMediumEntry>(
+                pImpl->m_pCheckEditableWorkerMutex, pImpl->m_pIsDestructed);
+
+            if (newEntry != nullptr)
+            {
+                g_newReadOnlyDocs[this] = newEntry;
+            }
+        }
+    }
+}
+
+// should only be called on main thread
+void SfxMedium::CancelCheckEditableEntry(bool bRemoveEvent)
+{
+    if (pImpl->m_pCheckEditableWorkerMutex != nullptr)
+    {
+        std::unique_lock<std::recursive_mutex> 
lock(*(pImpl->m_pCheckEditableWorkerMutex));
+
+        if (pImpl->m_pReloadEvent != nullptr)
+        {
+            if (bRemoveEvent)
+                Application::RemoveUserEvent(pImpl->m_pReloadEvent);
+            // make sure destructor doesn't use a freed reference
+            // and reset the event so we can check again
+            pImpl->m_pReloadEvent = nullptr;
+        }
+
+        if (pImpl->m_pIsDestructed != nullptr)
+        {
+            *(pImpl->m_pIsDestructed) = true;
+            pImpl->m_pIsDestructed = nullptr;
+        }
+    }
+}
+
+/** callback function, which is triggered by worker thread after successfully 
checking if the file
+     is editable. Sent from <Application::PostUserEvent(..)>
+     Note: This method has to be run in the main thread.
+*/
+IMPL_STATIC_LINK(SfxMedium, ShowReloadEditableDialog, void*, p, void)
+{
+    SfxMedium* pMed = static_cast<SfxMedium*>(p);
+    if (pMed == nullptr)
+        return;
+
+    pMed->CancelCheckEditableEntry(false);
+
+    uno::Reference<task::XInteractionHandler> xHandler = 
pMed->GetInteractionHandler();
+    if (xHandler.is())
+    {
+        OUString aDocumentURL
+            = 
pMed->GetURLObject().GetLastName(INetURLObject::DecodeMechanism::WithCharset);
+        ::rtl::Reference<::ucbhelper::InteractionRequest> 
xInteractionRequestImpl
+            = new 
::ucbhelper::InteractionRequest(uno::makeAny(document::ReloadEditableRequest(
+                OUString(), uno::Reference<uno::XInterface>(), aDocumentURL)));
+        if (xInteractionRequestImpl != nullptr)
+        {
+            sal_Int32 nContinuations = 2;
+            uno::Sequence<uno::Reference<task::XInteractionContinuation>> 
aContinuations(
+                nContinuations);
+            aContinuations[0] = new 
::ucbhelper::InteractionAbort(xInteractionRequestImpl.get());
+            aContinuations[1] = new 
::ucbhelper::InteractionApprove(xInteractionRequestImpl.get());
+            xInteractionRequestImpl->setContinuations(aContinuations);
+            xHandler->handle(xInteractionRequestImpl);
+            ::rtl::Reference<::ucbhelper::InteractionContinuation> xSelected
+                = xInteractionRequestImpl->getSelection();
+            if (uno::Reference<task::XInteractionApprove>(xSelected.get(), 
uno::UNO_QUERY).is())
+            {
+                for (SfxViewFrame* pFrame = SfxViewFrame::GetFirst(); pFrame;
+                     pFrame = SfxViewFrame::GetNext(*pFrame))
+                {
+                    if (pFrame->GetObjectShell()->GetMedium() == pMed)
+                    {
+                        // special case to ensure view isn't set to read-only 
in
+                        // SfxViewFrame::ExecReload_Impl after reloading
+                        pMed->SetOriginallyReadOnly(false);
+                        pFrame->GetDispatcher()->Execute(SID_RELOAD);
+                        break;
+                    }
+                }
+            }
+        }
+    }
+}
+
+bool SfxMedium::CheckCanGetLockfile() const
+{
+#if !HAVE_FEATURE_MULTIUSER_ENVIRONMENT
+    bool bCanReload = true;
+#else
+    bool bCanReload = false;
+    ::svt::DocumentLockFile aLockFile(GetName());
+    LockFileEntry aData;
+    osl::DirectoryItem rItem;
+    auto nError1 = osl::DirectoryItem::get(aLockFile.GetURL(), rItem);
+    if (nError1 == osl::FileBase::E_None)
+    {
+        try
+        {
+            aData = aLockFile.GetLockData();
+        }
+        catch (const io::WrongFormatException&)
+        {
+            // we get empty or corrupt data
+            return false;
+        }
+        catch (const uno::Exception&)
+        {
+            // locked from other app
+            return false;
+        }
+        LockFileEntry aOwnData = svt::LockFileCommon::GenerateOwnEntry();
+        bool bOwnLock
+            = aOwnData[LockFileComponent::SYSUSERNAME] == 
aData[LockFileComponent::SYSUSERNAME];
+        if (bOwnLock
+            && aOwnData[LockFileComponent::LOCALHOST] == 
aData[LockFileComponent::LOCALHOST]
+            && aOwnData[LockFileComponent::USERURL] == 
aData[LockFileComponent::USERURL])
+        {
+            // this is own lock from the same installation, it could remain 
because of crash
+            bCanReload = true;
+        }
+    }
+    else if (nError1 == osl::FileBase::E_NOENT) // file doesn't exist
+    {
+        try
+        {
+            aLockFile.CreateOwnLockFile();
+            try
+            {
+                // TODO/LATER: A warning could be shown in case the file is 
not the own one
+                aLockFile.RemoveFile();
+            }
+            catch (const io::WrongFormatException&)
+            {
+                try
+                {
+                    // erase the empty or corrupt file
+                    aLockFile.RemoveFileDirectly();
+                }
+                catch (const uno::Exception&)
+                {
+                }
+            }
+            bCanReload = true;
+        }
+        catch (const uno::Exception&)
+        {
+        }
+    }
+#endif
+    return bCanReload;
+}
+
+// worker thread method, should only be one thread globally
+void CheckReadOnlyTask::doWork()
+{
+    if (m_xListener == nullptr)
+        return;
+
+    while (true)
+    {
+        std::unique_lock<std::mutex> termLock(m_xListener->mMutex);
+        if (m_xListener->mCond.wait_for(termLock, std::chrono::seconds(60),
+                                        [this] { return 
m_xListener->bIsTerminated; }))
+            // signalled, spurious wakeups should not be possible
+            return;
+
+        // must have timed-out
+        termLock.unlock();
+        std::unique_lock<std::mutex> globalLock(g_chkReadOnlyGlobalMutex);
+        for (const auto& [pMed, roEntry] : g_newReadOnlyDocs)
+        {
+            g_existingReadOnlyDocs[pMed] = roEntry;
+            g_newReadOnlyDocs.erase(pMed);
+        }
+        if (g_existingReadOnlyDocs.size() == 0)
+        {
+            g_bChkReadOnlyTaskRunning = false;
+            return;
+        }
+        globalLock.unlock();
+
+        bool bErase = false;
+        for (const auto& [pMed, roEntry] : g_existingReadOnlyDocs)
+        {
+            bErase = false;
+            comphelper::ScopeGuard g([&bErase, pMed = pMed]() {
+                if (bErase)
+                    g_existingReadOnlyDocs.erase(pMed);
+            });
+
+            if (pMed == nullptr || roEntry == nullptr || roEntry->_pMutex == 
nullptr
+                || roEntry->_pIsDestructed == nullptr)
+            {
+                bErase = true;
+                continue;
+            }
+
+            std::unique_lock<std::recursive_mutex> 
medLock(*(roEntry->_pMutex));
+            if (*(roEntry->_pIsDestructed) || pMed->GetWorkerReloadEvent() != 
nullptr)
+            {
+                bErase = true;
+            }
+            else
+            {
+                osl::File aFile(
+                    
pMed->GetURLObject().GetMainURL(INetURLObject::DecodeMechanism::WithCharset));
+                if (aFile.open(osl_File_OpenFlag_Write) != 
osl::FileBase::E_None)
+                    continue;
+
+                if (!pMed->CheckCanGetLockfile())
+                    continue;
+
+                bErase = true;
+
+                if (aFile.close() != osl::FileBase::E_None)
+                    continue;
+
+                // we can load, ask user
+                ImplSVEvent* pEvent = Application::PostUserEvent(
+                    LINK(nullptr, SfxMedium, ShowReloadEditableDialog), pMed);
+                pMed->SetWorkerReloadEvent(pEvent);
+            }
+        }
+    }
+}
+
 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sfx2/source/view/viewfrm.cxx b/sfx2/source/view/viewfrm.cxx
index d8d5bf69465a..be8ea298b978 100644
--- a/sfx2/source/view/viewfrm.cxx
+++ b/sfx2/source/view/viewfrm.cxx
@@ -315,6 +315,12 @@ void SfxViewFrame::ExecReload_Impl( SfxRequest& rReq )
 
             SfxMedium* pMed = pSh->GetMedium();
 
+            std::shared_ptr<std::recursive_mutex> pChkEditMutex = 
pMed->GetCheckEditableMutex();
+            std::unique_lock<std::recursive_mutex> chkEditLock;
+            if (pChkEditMutex != nullptr)
+                chkEditLock = 
std::unique_lock<std::recursive_mutex>(*pChkEditMutex);
+            pMed->CancelCheckEditableEntry();
+
             const SfxBoolItem* pItem = 
SfxItemSet::GetItem<SfxBoolItem>(pSh->GetMedium()->GetItemSet(), SID_VIEWONLY, 
false);
             if ( pItem && pItem->GetValue() )
             {
@@ -510,6 +516,8 @@ void SfxViewFrame::ExecReload_Impl( SfxRequest& rReq )
                             bOpenTemplate = RET_YES == nUserAnswer;
                             // Always reset this here to avoid infinite loop
                             bRetryIgnoringLock = RET_IGNORE == nUserAnswer;
+                            if (RET_CANCEL == nUserAnswer)
+                                pMed->AddToCheckEditableWorkerList();
                         }
                         else
                             bRetryIgnoringLock = false;
@@ -631,6 +639,12 @@ void SfxViewFrame::ExecReload_Impl( SfxRequest& rReq )
             if ( bDo )
             {
                 SfxMedium *pMedium = xOldObj->GetMedium();
+                std::shared_ptr<std::recursive_mutex> pChkEditMutex
+                    = pMedium->GetCheckEditableMutex();
+                std::unique_lock<std::recursive_mutex> chkEditLock;
+                if (pChkEditMutex != nullptr)
+                    chkEditLock = 
std::unique_lock<std::recursive_mutex>(*pChkEditMutex);
+                pMedium->CancelCheckEditableEntry();
 
                 bool bHandsOff =
                     ( pMedium->GetURLObject().GetProtocol() == 
INetProtocol::File && !xOldObj->IsDocShared() );
@@ -772,6 +786,7 @@ void SfxViewFrame::ExecReload_Impl( SfxRequest& rReq )
                 {
                     xNewObj->DoClose();
                     xNewObj = nullptr;
+                    pMedium->AddToCheckEditableWorkerList();
                 }
 
                 pNewSet.reset();
diff --git a/uui/Library_uui.mk b/uui/Library_uui.mk
index 4daad8403dcb..4f664ac20dba 100644
--- a/uui/Library_uui.mk
+++ b/uui/Library_uui.mk
@@ -62,6 +62,8 @@ $(eval $(call gb_Library_add_exception_objects,uui,\
        uui/source/openlocked \
        uui/source/passwordcontainer \
        uui/source/passworddlg \
+       uui/source/readonlyopen \
+       uui/source/reloadeditable \
        uui/source/requeststringresolver \
        uui/source/secmacrowarnings \
        uui/source/sslwarndlg \
diff --git a/uui/inc/strings.hrc b/uui/inc/strings.hrc
index 364175298db7..096d7ef977a1 100644
--- a/uui/inc/strings.hrc
+++ b/uui/inc/strings.hrc
@@ -30,8 +30,9 @@
 #define STR_PASSWORD_MISMATCH                   NC_("STR_PASSWORD_MISMATCH", 
"The confirmation password did not match the password. Set the password again 
by entering the same password in both boxes.")
 
 #define STR_ALREADYOPEN_TITLE                   NC_("STR_ALREADYOPEN_TITLE", 
"Document in Use")
-#define STR_ALREADYOPEN_MSG                     NC_("STR_ALREADYOPEN_MSG", 
"Document file '$(ARG1)' is locked for editing by yourself on a different 
system since $(ARG2)\n\nOpen document read-only, or ignore own file locking and 
open the document for editing.")
+#define STR_ALREADYOPEN_MSG                     NC_("STR_ALREADYOPEN_MSG", 
"Document file '$(ARG1)' is locked for editing by yourself on a different 
system since $(ARG2)\n\nOpen document read-only, or ignore own file locking and 
open the document for editing.\nSelect Notify to open read-only and get 
notified when the document becomes editable.")
 #define STR_ALREADYOPEN_READONLY_BTN            
NC_("STR_ALREADYOPEN_READONLY_BTN", "Open ~Read-Only")
+#define STR_ALREADYOPEN_READONLY_NOTIFY_BTN     
NC_("STR_ALREADYOPEN_READONLY_NOTIFY_BTN", "~Notify")
 #define STR_ALREADYOPEN_OPEN_BTN                
NC_("STR_ALREADYOPEN_OPEN_BTN", "~Open")
 #define STR_ALREADYOPEN_SAVE_MSG                
NC_("STR_ALREADYOPEN_SAVE_MSG", "Document file '$(ARG1)' is locked for editing 
by yourself on a different system since $(ARG2)\n\nClose document on other 
system and retry saving or ignore own file locking and save current document.")
 #define STR_ALREADYOPEN_RETRY_SAVE_BTN          
NC_("STR_ALREADYOPEN_RETRY_SAVE_BTN", "~Retry Saving")
@@ -42,13 +43,15 @@
 #define STR_WARNING_INCOMPLETE_ENCRYPTION_TITLE 
NC_("STR_WARNING_INCOMPLETE_ENCRYPTION_TITLE", "Non-Encrypted Streams")
 
 #define STR_LOCKFAILED_TITLE                    NC_("STR_LOCKFAILED_TITLE", 
"Document Could Not Be Locked")
-#define STR_LOCKFAILED_MSG                      NC_("STR_LOCKFAILED_MSG", "The 
lock file could not be created for exclusive access by %PRODUCTNAME, due to 
missing permission to create a lock file on that file location or lack of free 
disk space.")
+#define STR_LOCKFAILED_MSG                      NC_("STR_LOCKFAILED_MSG", "The 
lock file could not be created for exclusive access by %PRODUCTNAME, due to 
missing permission to create a lock file on that file location or lack of free 
disk space.\n\nSelect Notify to open read-only and get notified when the 
document becomes editable.")
 #define STR_LOCKFAILED_OPENREADONLY_BTN         
NC_("STR_LOCKFAILED_OPENREADONLY_BTN", "Open ~Read-Only")
+#define STR_LOCKFAILED_OPENREADONLY_NOTIFY_BTN  
NC_("STR_LOCKFAILED_OPENREADONLY_NOTIFY_BTN", "~Notify")
 
 #define STR_OPENLOCKED_TITLE                    NC_("STR_OPENLOCKED_TITLE", 
"Document in Use")
-#define STR_OPENLOCKED_MSG                      NC_("STR_OPENLOCKED_MSG", 
"Document file '$(ARG1)' is locked for editing by:\n\n$(ARG2)\n\nOpen document 
read-only or open a copy of the document for editing.$(ARG3)")
+#define STR_OPENLOCKED_MSG                      NC_("STR_OPENLOCKED_MSG", 
"Document file '$(ARG1)' is locked for editing by:\n\n$(ARG2)\n\nOpen document 
read-only or open a copy of the document for editing.\nSelect Notify to open 
read-only and get notified when the document becomes editable.$(ARG3)")
 #define STR_OPENLOCKED_ALLOWIGNORE_MSG          
NC_("STR_OPENLOCKED_ALLOWIGNORE_MSG", "\nYou may also ignore the file locking 
and open the document for editing.")
 #define STR_OPENLOCKED_OPENREADONLY_BTN         
NC_("STR_OPENLOCKED_OPENREADONLY_BTN", "Open ~Read-Only")
+#define STR_OPENLOCKED_OPENREADONLY_NOTIFY_BTN  
NC_("STR_OPENLOCKED_OPENREADONLY_NOTIFY_BTN", "~Notify")
 #define STR_OPENLOCKED_OPENCOPY_BTN             
NC_("STR_OPENLOCKED_OPENCOPY_BTN", "Open ~Copy")
 #define STR_UNKNOWNUSER                         NC_("STR_UNKNOWNUSER", 
"Unknown User")
 
@@ -73,7 +76,17 @@
 #define STR_ERROR_PASSWORDS_NOT_IDENTICAL       
NC_("STR_ERROR_PASSWORDS_NOT_IDENTICAL", "The password confirmation does not 
match.")
 
 #define STR_LOCKCORRUPT_TITLE                   NC_("STR_LOCKCORRUPT_TITLE", 
"Lock file is corrupted")
-#define STR_LOCKCORRUPT_MSG                     NC_("STR_LOCKCORRUPT_MSG", 
"The lock file is corrupted and probably empty. Opening the document read-only 
and closing it again removes the corrupted lock file.")
+#define STR_LOCKCORRUPT_MSG                     NC_("STR_LOCKCORRUPT_MSG", 
"The lock file is corrupted and probably empty. Opening the document read-only 
and closing it again removes the corrupted lock file.\n\nSelect Notify to open 
read-only and get notified when the document becomes editable.")
 #define STR_LOCKCORRUPT_OPENREADONLY_BTN        
NC_("STR_LOCKCORRUPT_OPENREADONLY_BTN", "Open ~Read-Only")
+#define STR_LOCKCORRUPT_OPENREADONLY_NOTIFY_BTN 
NC_("STR_LOCKCORRUPT_OPENREADONLY_NOTIFY_BTN", "~Notify")
+
+#define STR_RELOADEDITABLE_TITLE                
NC_("STR_RELOADEDITABLE_TITLE", "Document is now editable")
+#define STR_RELOADEDITABLE_MSG                  NC_("STR_RELOADEDITABLE_MSG", 
"Document file '$(ARG1)' is now editable \n\nReload this document for editing?")
+#define STR_RELOADEDITABLE_BTN                  NC_("STR_RELOADEDITABLE_BTN", 
"~Reload")
+
+#define STR_READONLYOPEN_TITLE                  NC_("STR_READONLYOPEN_TITLE", 
"Document is read-only")
+#define STR_READONLYOPEN_MSG                    NC_("STR_READONLYOPEN_MSG", 
"Document file '$(ARG1)' is read-only.\n\nOpen read-only or select Notify to 
open read-only and get notified when the document becomes editable.")
+#define STR_READONLYOPEN_BTN                    NC_("STR_READONLYOPEN_BTN", 
"Open ~Read-Only")
+#define STR_READONLYOPEN_NOTIFY_BTN             
NC_("STR_READONLYOPEN_NOTIFY_BTN", "~Notify")
 
 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/uui/source/alreadyopen.cxx b/uui/source/alreadyopen.cxx
index 2fe5dcbc424f..7c387d8b7a7c 100644
--- a/uui/source/alreadyopen.cxx
+++ b/uui/source/alreadyopen.cxx
@@ -35,6 +35,7 @@ AlreadyOpenQueryBox::AlreadyOpenQueryBox(weld::Window* 
pParent, const std::local
     else
     {
         m_xQueryBox->add_button(Translate::get(STR_ALREADYOPEN_READONLY_BTN, 
rLocale), RET_YES);
+        
m_xQueryBox->add_button(Translate::get(STR_ALREADYOPEN_READONLY_NOTIFY_BTN, 
rLocale), RET_RETRY);
         m_xQueryBox->add_button(Translate::get(STR_ALREADYOPEN_OPEN_BTN, 
rLocale), RET_NO);
     }
     m_xQueryBox->add_button(GetStandardText(StandardButtonType::Cancel), 
RET_CANCEL);
diff --git a/uui/source/iahndl-locking.cxx b/uui/source/iahndl-locking.cxx
index 52b9d2108347..23493c16053c 100644
--- a/uui/source/iahndl-locking.cxx
+++ b/uui/source/iahndl-locking.cxx
@@ -23,6 +23,8 @@
 #include <com/sun/star/document/LockFileIgnoreRequest.hpp>
 #include <com/sun/star/document/LockFileCorruptRequest.hpp>
 #include <com/sun/star/document/OwnLockOnDocumentRequest.hpp>
+#include <com/sun/star/document/ReadOnlyOpenRequest.hpp>
+#include <com/sun/star/document/ReloadEditableRequest.hpp>
 #include <com/sun/star/task/XInteractionApprove.hpp>
 #include <com/sun/star/task/XInteractionDisapprove.hpp>
 #include <com/sun/star/task/XInteractionAbort.hpp>
@@ -41,6 +43,8 @@
 #include "filechanged.hxx"
 #include "lockfailed.hxx"
 #include "lockcorrupt.hxx"
+#include "readonlyopen.hxx"
+#include "reloadeditable.hxx"
 
 #include "iahndl.hxx"
 
@@ -53,6 +57,66 @@ using namespace com::sun::star;
 
 namespace {
 
+void handleReadOnlyOpenRequest_(
+    weld::Window* pParent, const OUString& aDocumentURL,
+    uno::Sequence<uno::Reference<task::XInteractionContinuation>> const& 
rContinuations)
+{
+    uno::Reference<task::XInteractionApprove> xApprove;
+    uno::Reference<task::XInteractionAbort> xAbort;
+    getContinuations(rContinuations, &xApprove, &xAbort);
+
+    if (!xApprove.is() || !xAbort.is())
+        return;
+
+    SolarMutexGuard aGuard;
+    std::locale aResLocale = Translate::Create("uui");
+
+    OUString aMessage;
+    std::vector<OUString> aArguments;
+    aArguments.push_back(aDocumentURL);
+
+    aMessage = Translate::get(STR_READONLYOPEN_MSG, aResLocale);
+    aMessage = UUIInteractionHelper::replaceMessageWithArguments(aMessage, 
aArguments);
+
+    ReadOnlyOpenQueryBox aDialog(pParent, aResLocale, aMessage);
+    int nResult = aDialog.run();
+
+    if (nResult == RET_YES)
+        xApprove->select();
+    else if (nResult != RET_RETRY)
+        xAbort->select();
+}
+
+void handleReloadEditableRequest_(
+    weld::Window* pParent, const OUString& aDocumentURL,
+    uno::Sequence<uno::Reference<task::XInteractionContinuation>> const& 
rContinuations)
+{
+    uno::Reference<task::XInteractionApprove> xApprove;
+    uno::Reference<task::XInteractionAbort> xAbort;
+    getContinuations(rContinuations, &xApprove, &xAbort);
+
+    if (!xApprove.is() || !xAbort.is())
+        return;
+
+    SolarMutexGuard aGuard;
+    std::locale aResLocale = Translate::Create("uui");
+
+    OUString aMessage;
+    std::vector<OUString> aArguments;
+    aArguments.push_back(aDocumentURL);
+
+    aMessage = Translate::get(STR_RELOADEDITABLE_MSG, aResLocale);
+    aMessage = UUIInteractionHelper::replaceMessageWithArguments(aMessage, 
aArguments);
+
+    ReloadEditableQueryBox aDialog(pParent, aResLocale, aMessage);
+    int nResult = aDialog.run();
+
+    if (nResult == RET_YES)
+        xApprove->select();
+    else
+        xAbort->select();
+}
+
 void
 handleLockedDocumentRequest_(
     weld::Window * pParent,
@@ -132,7 +196,7 @@ handleLockedDocumentRequest_(
         xDisapprove->select();
     else if ( nResult == RET_IGNORE && xRetry.is() )
         xRetry->select();
-    else
+    else if ( nResult != RET_RETRY )
         xAbort->select();
 }
 
@@ -196,7 +260,7 @@ handleLockFileProblemRequest_(
 
     if ( nResult == RET_OK )
         xApprove->select();
-    else
+    else if ( nResult != RET_RETRY )
         xAbort->select();
 }
 
@@ -293,5 +357,41 @@ UUIInteractionHelper::handleLockFileProblemRequest(
     return false;
 }
 
+bool UUIInteractionHelper::handleReadOnlyOpenRequest(
+    uno::Reference<task::XInteractionRequest> const& rRequest)
+{
+    uno::Any aAnyRequest(rRequest->getRequest());
+
+    document::ReadOnlyOpenRequest aReadOnlyOpenRequest;
+    if (aAnyRequest >>= aReadOnlyOpenRequest)
+    {
+        uno::Reference<awt::XWindow> xParent = getParentXWindow();
+        handleReadOnlyOpenRequest_(Application::GetFrameWeld(xParent),
+                                     aReadOnlyOpenRequest.DocumentURL,
+                                     rRequest->getContinuations());
+        return true;
+    }
+
+    return false;
+}
+
+bool UUIInteractionHelper::handleReloadEditableRequest(
+    uno::Reference<task::XInteractionRequest> const& rRequest)
+{
+    uno::Any aAnyRequest(rRequest->getRequest());
+
+    document::ReloadEditableRequest aReloadEditableRequest;
+    if (aAnyRequest >>= aReloadEditableRequest)
+    {
+        uno::Reference<awt::XWindow> xParent = getParentXWindow();
+        handleReloadEditableRequest_(
+            Application::GetFrameWeld(xParent), 
aReloadEditableRequest.DocumentURL,
+            rRequest->getContinuations());
+        return true;
+    }
+
+    return false;
+}
+
 
 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/uui/source/iahndl.cxx b/uui/source/iahndl.cxx
index ec8cb02258db..2897a582c3e9 100644
--- a/uui/source/iahndl.cxx
+++ b/uui/source/iahndl.cxx
@@ -815,6 +815,12 @@ UUIInteractionHelper::handleRequest_impl(
             if ( handleLockFileProblemRequest( rRequest ) )
                 return true;
 
+            if ( handleReloadEditableRequest( rRequest ) )
+                return true;
+
+            if ( handleReadOnlyOpenRequest( rRequest ) )
+                return true;
+
             task::DocumentMacroConfirmationRequest aMacroConfirmRequest;
             if (aAnyRequest >>= aMacroConfirmRequest)
             {
diff --git a/uui/source/iahndl.hxx b/uui/source/iahndl.hxx
index ff6973245dad..8dc828a7dd5f 100644
--- a/uui/source/iahndl.hxx
+++ b/uui/source/iahndl.hxx
@@ -231,6 +231,12 @@ private:
     bool handleLockFileProblemRequest(
         css::uno::Reference< css::task::XInteractionRequest > const & 
rRequest);
 
+    bool handleReloadEditableRequest(
+        css::uno::Reference<css::task::XInteractionRequest> const& rRequest);
+
+    bool
+    
handleReadOnlyOpenRequest(css::uno::Reference<css::task::XInteractionRequest> 
const& rRequest);
+
     bool handleCustomRequest(
                 const css::uno::Reference< css::task::XInteractionRequest >& 
i_rRequest,
                 const OUString& i_rServiceName
diff --git a/uui/source/lockcorrupt.cxx b/uui/source/lockcorrupt.cxx
index 28e8e71f00c4..d3abee07a185 100644
--- a/uui/source/lockcorrupt.cxx
+++ b/uui/source/lockcorrupt.cxx
@@ -29,6 +29,7 @@ LockCorruptQueryBox::LockCorruptQueryBox(weld::Window* 
pParent, const std::local
 {
     m_xQueryBox->set_title(Translate::get(STR_LOCKCORRUPT_TITLE, rResLocale));
     m_xQueryBox->add_button(Translate::get(STR_LOCKCORRUPT_OPENREADONLY_BTN, 
rResLocale), RET_OK);
+    
m_xQueryBox->add_button(Translate::get(STR_LOCKCORRUPT_OPENREADONLY_NOTIFY_BTN, 
rResLocale), RET_RETRY);
     m_xQueryBox->add_button(GetStandardText(StandardButtonType::Cancel), 
RET_CANCEL);
     m_xQueryBox->set_default_response(RET_OK);
 }
diff --git a/uui/source/lockfailed.cxx b/uui/source/lockfailed.cxx
index 8254b19e509b..340cc9638806 100644
--- a/uui/source/lockfailed.cxx
+++ b/uui/source/lockfailed.cxx
@@ -29,6 +29,7 @@ LockFailedQueryBox::LockFailedQueryBox(weld::Window* pParent, 
const std::locale&
 {
     m_xQueryBox->set_title(Translate::get(STR_LOCKFAILED_TITLE, rLocale));
     m_xQueryBox->add_button(Translate::get(STR_LOCKFAILED_OPENREADONLY_BTN, 
rLocale), RET_OK);
+    
m_xQueryBox->add_button(Translate::get(STR_LOCKFAILED_OPENREADONLY_NOTIFY_BTN, 
rLocale), RET_RETRY);
     m_xQueryBox->add_button(GetStandardText(StandardButtonType::Cancel), 
RET_CANCEL);
     m_xQueryBox->set_default_response(RET_OK);
 }
diff --git a/uui/source/openlocked.cxx b/uui/source/openlocked.cxx
index fa2a4616c7c1..a0284b194b14 100644
--- a/uui/source/openlocked.cxx
+++ b/uui/source/openlocked.cxx
@@ -29,6 +29,7 @@ OpenLockedQueryBox::OpenLockedQueryBox(weld::Window* pParent, 
const std::locale&
 {
     m_xQueryBox->set_title(Translate::get(STR_OPENLOCKED_TITLE, rResLocale));
     m_xQueryBox->add_button(Translate::get(STR_OPENLOCKED_OPENREADONLY_BTN, 
rResLocale), RET_YES);
+    
m_xQueryBox->add_button(Translate::get(STR_OPENLOCKED_OPENREADONLY_NOTIFY_BTN, 
rResLocale), RET_RETRY);
     m_xQueryBox->add_button(Translate::get(STR_OPENLOCKED_OPENCOPY_BTN, 
rResLocale), RET_NO);
     if (bEnableOverride && 
officecfg::Office::Common::Misc::AllowOverrideLocking::get())
     {
diff --git a/uui/source/readonlyopen.cxx b/uui/source/readonlyopen.cxx
new file mode 100644
index 000000000000..72a3b989e079
--- /dev/null
+++ b/uui/source/readonlyopen.cxx
@@ -0,0 +1,38 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; 
fill-column: 100 -*- */
+/*
+ * 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 <strings.hrc>
+#include "readonlyopen.hxx"
+#include <officecfg/Office/Common.hxx>
+#include <unotools/resmgr.hxx>
+#include <vcl/stdtext.hxx>
+#include <vcl/svapp.hxx>
+
+ReadOnlyOpenQueryBox::ReadOnlyOpenQueryBox(weld::Window* pParent, const 
std::locale& rResLocale,
+                                           const OUString& rMessage)
+    : m_xQueryBox(Application::CreateMessageDialog(pParent, 
VclMessageType::Question,
+                                                   VclButtonsType::NONE, 
rMessage))
+{
+    m_xQueryBox->set_title(Translate::get(STR_READONLYOPEN_TITLE, rResLocale));
+    m_xQueryBox->add_button(Translate::get(STR_READONLYOPEN_BTN, rResLocale), 
RET_YES);
+    m_xQueryBox->add_button(Translate::get(STR_READONLYOPEN_NOTIFY_BTN, 
rResLocale), RET_RETRY);
+    m_xQueryBox->add_button(GetStandardText(StandardButtonType::Cancel), 
RET_CANCEL);
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s 
cinkeys+=0=break: */
diff --git a/uui/source/readonlyopen.hxx b/uui/source/readonlyopen.hxx
new file mode 100644
index 000000000000..08063d10be0e
--- /dev/null
+++ b/uui/source/readonlyopen.hxx
@@ -0,0 +1,35 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; 
fill-column: 100 -*- */
+/*
+ * 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 .
+ */
+
+#pragma once
+
+#include <vcl/weld.hxx>
+
+class ReadOnlyOpenQueryBox
+{
+private:
+    std::unique_ptr<weld::MessageDialog> m_xQueryBox;
+
+public:
+    ReadOnlyOpenQueryBox(weld::Window* pParent, const std::locale& rResLocale,
+                         const OUString& rMessage);
+    int run() { return m_xQueryBox->run(); }
+};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s 
cinkeys+=0=break: */
diff --git a/uui/source/reloadeditable.cxx b/uui/source/reloadeditable.cxx
new file mode 100644
index 000000000000..9dad2e183abf
--- /dev/null
+++ b/uui/source/reloadeditable.cxx
@@ -0,0 +1,37 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; 
fill-column: 100 -*- */
+/*
+ * 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 <strings.hrc>
+#include "reloadeditable.hxx"
+#include <officecfg/Office/Common.hxx>
+#include <unotools/resmgr.hxx>
+#include <vcl/stdtext.hxx>
+#include <vcl/svapp.hxx>
+
+ReloadEditableQueryBox::ReloadEditableQueryBox(weld::Window* pParent, const 
std::locale& rResLocale,
+                                               const OUString& rMessage)
+    : m_xQueryBox(Application::CreateMessageDialog(pParent, 
VclMessageType::Question,
+                                                   VclButtonsType::NONE, 
rMessage))
+{
+    m_xQueryBox->set_title(Translate::get(STR_RELOADEDITABLE_TITLE, 
rResLocale));
+    m_xQueryBox->add_button(Translate::get(STR_RELOADEDITABLE_BTN, 
rResLocale), RET_YES);
+    m_xQueryBox->add_button(GetStandardText(StandardButtonType::Cancel), 
RET_CANCEL);
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s 
cinkeys+=0=break: */
diff --git a/uui/source/reloadeditable.hxx b/uui/source/reloadeditable.hxx
new file mode 100644
index 000000000000..00a15f7ba89c
--- /dev/null
+++ b/uui/source/reloadeditable.hxx
@@ -0,0 +1,35 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; 
fill-column: 100 -*- */
+/*
+ * 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 .
+ */
+
+#pragma once
+
+#include <vcl/weld.hxx>
+
+class ReloadEditableQueryBox
+{
+private:
+    std::unique_ptr<weld::MessageDialog> m_xQueryBox;
+
+public:
+    ReloadEditableQueryBox(weld::Window* pParent, const std::locale& 
rResLocale,
+                           const OUString& rMessage);
+    int run() { return m_xQueryBox->run(); }
+};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s 
cinkeys+=0=break: */
_______________________________________________
Libreoffice-commits mailing list
libreoffice-comm...@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/libreoffice-commits

Reply via email to