/* -*- 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/.
 */
 
#include <sal/config.h>
 
#include <string>
#include <string_view>
#include <list>
#include <mutex>
 
#include <sfx2/lokcomponenthelpers.hxx>
#include <sfx2/lokhelper.hxx>
 
#include <com/sun/star/frame/Desktop.hpp>
#include <com/sun/star/ui/ContextChangeEventObject.hpp>
#include <com/sun/star/xml/crypto/SEInitializer.hpp>
#include <com/sun/star/xml/crypto/XCertificateCreator.hpp>
 
#include <comphelper/processfactory.hxx>
#include <o3tl/string_view.hxx>
#include <rtl/strbuf.hxx>
#include <vcl/lok.hxx>
#include <vcl/svapp.hxx>
#include <vcl/commandevent.hxx>
#include <vcl/window.hxx>
#include <sal/log.hxx>
#include <sfx2/app.hxx>
#include <sfx2/msg.hxx>
#include <sfx2/viewsh.hxx>
#include <sfx2/request.hxx>
#include <sfx2/sfxsids.hrc>
#include <sfx2/viewfrm.hxx>
#include <LibreOfficeKit/LibreOfficeKitEnums.h>
#include <comphelper/lok.hxx>
#include <sfx2/msgpool.hxx>
#include <comphelper/scopeguard.hxx>
#include <comphelper/base64.hxx>
#include <tools/json_writer.hxx>
#include <svl/cryptosign.hxx>
 
#include <boost/property_tree/json_parser.hpp>
 
using namespace com::sun::star;
 
namespace {
bool g_bSettingView(false);
 
/// Used to disable callbacks.
/// Needed to avoid recursion when switching views,
/// which can cause clients to invoke LOKit API and
/// implicitly set the view, which might cause an
/// infinite recursion if not detected and prevented.
class DisableCallbacks
{
public:
    DisableCallbacks()
    {
        assert(m_nDisabled >= 0 && "Expected non-negative DisabledCallbacks state when disabling.");
        ++m_nDisabled;
    }
 
    ~DisableCallbacks()
    {
        assert(m_nDisabled > 0 && "Expected positive DisabledCallbacks state when re-enabling.");
        --m_nDisabled;
    }
 
    static inline bool disabled()
    {
        return !comphelper::LibreOfficeKit::isActive() || m_nDisabled != 0;
    }
 
private:
    static int m_nDisabled;
};
 
int DisableCallbacks::m_nDisabled = 0;
}
 
namespace
{
LanguageTag g_defaultLanguageTag(u"en-US"_ustr, true);
LanguageTag g_loadLanguageTag(u"en-US"_ustr, true); //< The language used to load.
LOKDeviceFormFactor g_deviceFormFactor = LOKDeviceFormFactor::UNKNOWN;
bool g_isDefaultTimezoneSet = false;
OUString g_DefaultTimezone;
const std::size_t g_logNotifierCacheMaxSize = 50;
::std::list<::std::string> g_logNotifierCache;
}
 
int SfxLokHelper::createView(SfxViewFrame& rViewFrame, ViewShellDocId docId)
{
    assert(docId >= ViewShellDocId(0) && "Cannot createView for invalid (negative) DocId.");
 
    SfxViewShell::SetCurrentDocId(docId);
    SfxRequest aRequest(rViewFrame, SID_NEWWINDOW);
    rViewFrame.ExecView_Impl(aRequest);
    SfxViewShell* pViewShell = SfxViewShell::Current();
    if (pViewShell == nullptr)
        return -1;
 
    assert(pViewShell->GetDocId() == docId && "DocId must be already set!");
    return static_cast<sal_Int32>(pViewShell->GetViewShellId());
}
 
int SfxLokHelper::createView()
{
    // Assumes a single document, or at least that the
    // current view belongs to the document on which the
    // view will be created.
    SfxViewShell* pViewShell = SfxViewShell::Current();
    if (pViewShell == nullptr)
        return -1;
 
    return createView(pViewShell->GetViewFrame(), pViewShell->GetDocId());
}
 
std::unordered_map<OUString, css::uno::Reference<css::ui::XAcceleratorConfiguration>>& SfxLokHelper::getAcceleratorConfs()
{
    return SfxApplication::GetOrCreate()->GetAcceleratorConfs_Impl();
}
 
int SfxLokHelper::createView(int nDocId)
{
    const SfxApplication* pApp = SfxApplication::Get();
    if (pApp == nullptr)
        return -1;
 
    // Find a shell with the given DocId.
    const ViewShellDocId docId(nDocId);
    for (const SfxViewShell* pViewShell : pApp->GetViewShells_Impl())
    {
        if (pViewShell->GetDocId() == docId)
            return createView(pViewShell->GetViewFrame(), docId);
    }
 
    // No frame with nDocId found.
    return -1;
}
 
void SfxLokHelper::setEditMode(int nMode, vcl::ITiledRenderable* pDoc)
{
    DisableCallbacks dc;
    pDoc->setEditMode(nMode);
}
 
void SfxLokHelper::destroyView(int nId)
{
    const SfxApplication* pApp = SfxApplication::Get();
    if (pApp == nullptr)
        return;
 
    const ViewShellId nViewShellId(nId);
    std::vector<SfxViewShell*>& rViewArr = pApp->GetViewShells_Impl();
 
    for (SfxViewShell* pViewShell : rViewArr)
    {
        if (pViewShell->GetViewShellId() == nViewShellId)
        {
            pViewShell->SetLOKAccessibilityState(false);
            SfxViewFrame& rViewFrame = pViewShell->GetViewFrame();
            SfxRequest aRequest(rViewFrame, SID_CLOSEWIN);
            rViewFrame.Exec_Impl(aRequest);
            break;
        }
    }
}
 
bool SfxLokHelper::isSettingView()
{
    return g_bSettingView;
}
 
void SfxLokHelper::setView(int nId)
{
    g_bSettingView = true;
    comphelper::ScopeGuard g([] { g_bSettingView = false; });
 
    SfxApplication* pApp = SfxApplication::Get();
    if (pApp == nullptr)
        return;
 
    const ViewShellId nViewShellId(nId);
    std::vector<SfxViewShell*>& rViewArr = pApp->GetViewShells_Impl();
 
    const auto itViewShell = std::find_if(rViewArr.begin(), rViewArr.end(), [nViewShellId](SfxViewShell* pViewShell){ return pViewShell->GetViewShellId() == nViewShellId; });
    if (itViewShell == rViewArr.end())
        return;
 
    const SfxViewShell* pViewShell = *itViewShell;
    assert(pViewShell);
    DisableCallbacks dc;
 
    bool bIsCurrShell = (pViewShell == SfxViewShell::Current());
    if (bIsCurrShell && comphelper::LibreOfficeKit::getLanguageTag().getBcp47() == pViewShell->GetLOKLanguageTag().getBcp47())
        return;
 
    if (bIsCurrShell)
    {
        // If we wanted to set the SfxViewShell that is actually set, we could skip it.
        // But it looks like that the language can go wrong, so we have to fix that.
        // This can happen, when someone sets the language or SfxViewShell::Current() separately.
        SAL_WARN("lok", "LANGUAGE mismatch at setView! ... old (wrong) lang:"
                        << comphelper::LibreOfficeKit::getLanguageTag().getBcp47()
                        << " new lang:" << pViewShell->GetLOKLanguageTag().getBcp47());
    }
 
    // update the current LOK language and locale for the dialog tunneling
    comphelper::LibreOfficeKit::setLanguageTag(pViewShell->GetLOKLanguageTag());
    comphelper::LibreOfficeKit::setLocale(pViewShell->GetLOKLocale());
 
    if (bIsCurrShell)
        return;
 
    SfxViewFrame& rViewFrame = pViewShell->GetViewFrame();
    rViewFrame.MakeActive_Impl(false);
 
    // Make comphelper::dispatchCommand() find the correct frame.
    uno::Reference<frame::XFrame> xFrame = rViewFrame.GetFrame().GetFrameInterface();
    uno::Reference<frame::XDesktop2> xDesktop = frame::Desktop::create(comphelper::getProcessComponentContext());
    xDesktop->setActiveFrame(xFrame);
}
 
SfxViewShell* SfxLokHelper::getViewOfId(int nId)
{
    SfxApplication* pApp = SfxApplication::Get();
    if (pApp == nullptr)
        return nullptr;
 
    const ViewShellId nViewShellId(nId);
    std::vector<SfxViewShell*>& rViewArr = pApp->GetViewShells_Impl();
    for (SfxViewShell* pViewShell : rViewArr)
    {
        if (pViewShell->GetViewShellId() == nViewShellId)
            return pViewShell;
    }
 
    return nullptr;
}
 
int SfxLokHelper::getView(const SfxViewShell* pViewShell)
{
    if (!pViewShell)
        pViewShell = SfxViewShell::Current();
    // Still no valid view shell? Then no idea.
    if (!pViewShell)
        return -1;
 
    return static_cast<sal_Int32>(pViewShell->GetViewShellId());
}
 
std::size_t SfxLokHelper::getViewsCount(int nDocId)
{
    assert(nDocId != -1 && "Cannot getViewsCount for invalid DocId -1");
 
    SfxApplication* pApp = SfxApplication::Get();
    if (!pApp)
        return 0;
 
    const ViewShellDocId nCurrentDocId(nDocId);
    std::size_t n = 0;
    SfxViewShell* pViewShell = SfxViewShell::GetFirst();
    while (pViewShell)
    {
        if (pViewShell->GetDocId() == nCurrentDocId)
            n++;
        pViewShell = SfxViewShell::GetNext(*pViewShell);
    }
 
    return n;
}
 
bool SfxLokHelper::getViewIds(int nDocId, int* pArray, size_t nSize)
{
    assert(nDocId != -1 && "Cannot getViewsIds for invalid DocId -1");
 
    SfxApplication* pApp = SfxApplication::Get();
    if (!pApp)
        return false;
 
    const ViewShellDocId nCurrentDocId(nDocId);
    std::size_t n = 0;
    SfxViewShell* pViewShell = SfxViewShell::GetFirst();
    while (pViewShell)
    {
        if (pViewShell->GetDocId() == nCurrentDocId)
        {
            if (n == nSize)
                return false;
 
            pArray[n] = static_cast<sal_Int32>(pViewShell->GetViewShellId());
            n++;
        }
 
        pViewShell = SfxViewShell::GetNext(*pViewShell);
    }
 
    return true;
}
 
int SfxLokHelper::getDocumentIdOfView(int nViewId)
{
    SfxViewShell* pViewShell = SfxViewShell::GetFirst();
    while (pViewShell)
    {
        if (pViewShell->GetViewShellId() == ViewShellId(nViewId))
            return static_cast<int>(pViewShell->GetDocId());
        pViewShell = SfxViewShell::GetNext(*pViewShell);
    }
    return -1;
}
 
const LanguageTag & SfxLokHelper::getDefaultLanguage()
{
    return g_defaultLanguageTag;
}
 
void SfxLokHelper::setDefaultLanguage(const OUString& rBcp47LanguageTag)
{
    g_defaultLanguageTag = LanguageTag(rBcp47LanguageTag, true);
}
 
const LanguageTag& SfxLokHelper::getLoadLanguage() { return g_loadLanguageTag; }
 
void SfxLokHelper::setLoadLanguage(const OUString& rBcp47LanguageTag)
{
    g_loadLanguageTag = LanguageTag(rBcp47LanguageTag, true);
}
 
void SfxLokHelper::setViewLanguage(int nId, const OUString& rBcp47LanguageTag)
{
    std::vector<SfxViewShell*>& rViewArr = SfxGetpApp()->GetViewShells_Impl();
 
    for (SfxViewShell* pViewShell : rViewArr)
    {
        if (pViewShell->GetViewShellId() == ViewShellId(nId))
        {
            pViewShell->SetLOKLanguageTag(rBcp47LanguageTag);
            // sync also global getter if we are the current view
            bool bIsCurrShell = (pViewShell == SfxViewShell::Current());
            if (bIsCurrShell)
                comphelper::LibreOfficeKit::setLanguageTag(LanguageTag(rBcp47LanguageTag));
            return;
        }
    }
}
 
void SfxLokHelper::setViewReadOnly(int nId, bool readOnly)
{
    std::vector<SfxViewShell*>& rViewArr = SfxGetpApp()->GetViewShells_Impl();
 
    for (SfxViewShell* pViewShell : rViewArr)
    {
        if (pViewShell && pViewShell->GetViewShellId() == ViewShellId(nId))
        {
            LOK_INFO("lok.readonlyview", "SfxLokHelper::setViewReadOnly: view id: " << nId << ", readOnly: " << readOnly);
            pViewShell->SetLokReadOnlyView(readOnly);
            return;
        }
    }
}
 
void SfxLokHelper::setAllowChangeComments(int nId, bool allow)
{
    std::vector<SfxViewShell*>& rViewArr = SfxGetpApp()->GetViewShells_Impl();
 
    for (SfxViewShell* pViewShell : rViewArr)
    {
        if (pViewShell && pViewShell->GetViewShellId() == ViewShellId(nId))
        {
            LOK_INFO("lok.readonlyview", "SfxLokHelper::setAllowChangeComments: view id: " << nId << ", allow: " << allow);
            pViewShell->SetAllowChangeComments(allow);
            return;
        }
    }
}
 
void SfxLokHelper::setAccessibilityState(int nId, bool nEnabled)
{
    std::vector<SfxViewShell*>& rViewArr = SfxGetpApp()->GetViewShells_Impl();
 
    for (SfxViewShell* pViewShell : rViewArr)
    {
        if (pViewShell && pViewShell->GetViewShellId() == ViewShellId(nId))
        {
            LOK_INFO("lok.a11y", "SfxLokHelper::setAccessibilityState: view id: " << nId << ", nEnabled: " << nEnabled);
            pViewShell->SetLOKAccessibilityState(nEnabled);
            return;
        }
    }
}
 
void SfxLokHelper::setViewLocale(int nId, const OUString& rBcp47LanguageTag)
{
    std::vector<SfxViewShell*>& rViewArr = SfxGetpApp()->GetViewShells_Impl();
 
    for (SfxViewShell* pViewShell : rViewArr)
    {
        if (pViewShell->GetViewShellId() == ViewShellId(nId))
        {
            pViewShell->SetLOKLocale(rBcp47LanguageTag);
            return;
        }
    }
}
 
LOKDeviceFormFactor SfxLokHelper::getDeviceFormFactor()
{
    return g_deviceFormFactor;
}
 
void SfxLokHelper::setDeviceFormFactor(std::u16string_view rDeviceFormFactor)
{
    if (rDeviceFormFactor == u"desktop")
        g_deviceFormFactor = LOKDeviceFormFactor::DESKTOP;
    else if (rDeviceFormFactor == u"tablet")
        g_deviceFormFactor = LOKDeviceFormFactor::TABLET;
    else if (rDeviceFormFactor == u"mobile")
        g_deviceFormFactor = LOKDeviceFormFactor::MOBILE;
    else
        g_deviceFormFactor = LOKDeviceFormFactor::UNKNOWN;
}
 
void SfxLokHelper::setDefaultTimezone(bool isSet, const OUString& rTimezone)
{
    g_isDefaultTimezoneSet = isSet;
    g_DefaultTimezone = rTimezone;
}
 
std::pair<bool, OUString> SfxLokHelper::getDefaultTimezone()
{
    return { g_isDefaultTimezoneSet, g_DefaultTimezone };
}
 
void SfxLokHelper::setViewTimezone(int nId, bool isSet, const OUString& rTimezone)
{
    std::vector<SfxViewShell*>& rViewArr = SfxGetpApp()->GetViewShells_Impl();
 
    for (SfxViewShell* pViewShell : rViewArr)
    {
        if (pViewShell->GetViewShellId() == ViewShellId(nId))
        {
            pViewShell->SetLOKTimezone(isSet, rTimezone);
            return;
        }
    }
}
 
std::pair<bool, OUString> SfxLokHelper::getViewTimezone(int nId)
{
    std::vector<SfxViewShell*>& rViewArr = SfxGetpApp()->GetViewShells_Impl();
 
    for (SfxViewShell* pViewShell : rViewArr)
    {
        if (pViewShell->GetViewShellId() == ViewShellId(nId))
        {
            return pViewShell->GetLOKTimezone();
        }
    }
 
    return {};
}
 
/*
* Used for putting a whole JSON string into a string value
* e.g { key: "{JSON}" }
*/
static OString lcl_sanitizeJSONAsValue(const OString &rStr)
{
    if (rStr.getLength() < 1)
        return rStr;
    // FIXME: need an optimized 'escape' method for O[U]String.
    OStringBuffer aBuf(rStr.getLength() + 8);
    for (sal_Int32 i = 0; i < rStr.getLength(); ++i)
    {
        if (rStr[i] == '"' || rStr[i] == '\\')
            aBuf.append('\\');
 
        if (rStr[i] != '\n')
            aBuf.append(rStr[i]);
    }
    return aBuf.makeStringAndClear();
}
 
static OString lcl_generateJSON(const SfxViewShell* pView, const boost::property_tree::ptree& rTree)
{
    assert(pView != nullptr && "pView must be valid");
    boost::property_tree::ptree aMessageProps = rTree;
    aMessageProps.put("viewId", SfxLokHelper::getView(pView));
    aMessageProps.put("part", pView->getPart());
    aMessageProps.put("mode", pView->getEditMode());
    std::stringstream aStream;
    boost::property_tree::write_json(aStream, aMessageProps, false /* pretty */);
    return OString(o3tl::trim(aStream.str()));
}
 
static inline OString lcl_generateJSON(const SfxViewShell* pView, int nViewId, std::string_view rKey,
                                       const OString& rPayload)
{
    assert(pView != nullptr && "pView must be valid");
    return OString::Concat("{ \"viewId\": \"") + OString::number(nViewId)
           + "\", \"part\": \"" + OString::number(pView->getPart()) + "\", \"mode\": \""
           + OString::number(pView->getEditMode()) + "\", \"" + rKey + "\": \""
           + lcl_sanitizeJSONAsValue(rPayload) + "\" }";
}
 
static inline OString lcl_generateJSON(const SfxViewShell* pView, std::string_view rKey,
                                       const OString& rPayload)
{
    return lcl_generateJSON(pView, SfxLokHelper::getView(pView), rKey, rPayload);
}
 
void SfxLokHelper::notifyOtherView(const SfxViewShell* pThisView, SfxViewShell const* pOtherView,
                                   int nType, std::string_view rKey, const OString& rPayload)
{
    assert(pThisView != nullptr && "pThisView must be valid");
    if (DisableCallbacks::disabled())
        return;
 
    const OString aPayload = lcl_generateJSON(pThisView, rKey, rPayload);
    const int viewId = SfxLokHelper::getView(pThisView);
    pOtherView->libreOfficeKitViewCallbackWithViewId(nType, aPayload, viewId);
}
 
void SfxLokHelper::notifyOtherView(const SfxViewShell* pThisView, SfxViewShell const* pOtherView,
                                   int nType, const boost::property_tree::ptree& rTree)
{
    assert(pThisView != nullptr && "pThisView must be valid");
    if (DisableCallbacks::disabled() || !pOtherView)
        return;
 
    const int viewId = SfxLokHelper::getView(pThisView);
    pOtherView->libreOfficeKitViewCallbackWithViewId(nType, lcl_generateJSON(pThisView, rTree), viewId);
}
 
void SfxLokHelper::notifyOtherViews(const SfxViewShell* pThisView, int nType, std::string_view rKey,
                                    const OString& rPayload)
{
    assert(pThisView != nullptr && "pThisView must be valid");
    if (DisableCallbacks::disabled())
        return;
 
    // Cache the payload so we only have to generate it once, at most.
    OString aPayload;
    int viewId = -1;
 
    const ViewShellDocId nCurrentDocId = pThisView->GetDocId();
    SfxViewShell* pViewShell = SfxViewShell::GetFirst();
    while (pViewShell)
    {
        if (pViewShell != pThisView && nCurrentDocId == pViewShell->GetDocId())
        {
            // Payload is only dependent on pThisView.
            if (aPayload.isEmpty())
            {
                aPayload = lcl_generateJSON(pThisView, rKey, rPayload);
                viewId = SfxLokHelper::getView(pThisView);
            }
 
            pViewShell->libreOfficeKitViewCallbackWithViewId(nType, aPayload, viewId);
        }
 
        pViewShell = SfxViewShell::GetNext(*pViewShell);
    }
}
 
void SfxLokHelper::notifyOtherViews(const SfxViewShell* pThisView, int nType,
                                    const boost::property_tree::ptree& rTree)
{
    assert(pThisView != nullptr && "pThisView must be valid");
    if (!pThisView || DisableCallbacks::disabled())
        return;
 
    // Cache the payload so we only have to generate it once, at most.
    OString aPayload;
    int viewId = -1;
 
    const ViewShellDocId nCurrentDocId = pThisView->GetDocId();
    SfxViewShell* pViewShell = SfxViewShell::GetFirst();
    while (pViewShell)
    {
        if (pViewShell != pThisView && nCurrentDocId == pViewShell->GetDocId())
        {
            // Payload is only dependent on pThisView.
            if (aPayload.isEmpty())
            {
                aPayload = lcl_generateJSON(pThisView, rTree);
                viewId = SfxLokHelper::getView(pThisView);
            }
 
            pViewShell->libreOfficeKitViewCallbackWithViewId(nType, aPayload, viewId);
        }
 
        pViewShell = SfxViewShell::GetNext(*pViewShell);
    }
}
 
OString SfxLokHelper::makePayloadJSON(const SfxViewShell* pThisView, int nViewId, std::string_view rKey, const OString& rPayload)
{
    return lcl_generateJSON(pThisView, nViewId, rKey, rPayload);
}
 
namespace {
    OUString lcl_getNameForSlot(const SfxViewShell* pShell, sal_uInt16 nWhich)
    {
        if (pShell && pShell->GetFrame())
        {
            const SfxSlot* pSlot = SfxSlotPool::GetSlotPool(pShell->GetFrame()).GetSlot(nWhich);
            if (pSlot)
            {
                if (!pSlot->GetUnoName().isEmpty())
                {
                    return pSlot->GetCommand();
                }
            }
        }
 
        return u""_ustr;
    }
}
 
void SfxLokHelper::sendUnoStatus(const SfxViewShell* pShell, const SfxPoolItem* pItem)
{
    if (!pShell || !pItem || IsInvalidItem(pItem) || DisableCallbacks::disabled())
        return;
 
    boost::property_tree::ptree aItem = pItem->dumpAsJSON();
 
    if (aItem.count("state"))
    {
        OUString sCommand = lcl_getNameForSlot(pShell, pItem->Which());
        if (!sCommand.isEmpty())
            aItem.put("commandName", sCommand);
 
        std::stringstream aStream;
        boost::property_tree::write_json(aStream, aItem);
        pShell->libreOfficeKitViewCallback(LOK_CALLBACK_STATE_CHANGED, OString(aStream.str()));
    }
}
 
void SfxLokHelper::notifyViewRenderState(const SfxViewShell* pShell, vcl::ITiledRenderable* pDoc)
{
    pShell->libreOfficeKitViewCallback(LOK_CALLBACK_VIEW_RENDER_STATE, pDoc->getViewRenderState());
}
 
void SfxLokHelper::notifyWindow(const SfxViewShell* pThisView,
                                vcl::LOKWindowId nLOKWindowId,
                                std::u16string_view rAction,
                                const std::vector<vcl::LOKPayloadItem>& rPayload)
{
    assert(pThisView != nullptr && "pThisView must be valid");
 
    if (nLOKWindowId == 0 || DisableCallbacks::disabled())
        return;
 
    OStringBuffer aPayload =
        "{ \"id\": \"" + OString::number(nLOKWindowId) + "\""
        ", \"action\": \"" + OUStringToOString(rAction, RTL_TEXTENCODING_UTF8) + "\"";
 
    for (const auto& rItem: rPayload)
    {
        if (!rItem.first.isEmpty() && !rItem.second.isEmpty())
        {
            auto aFirst = rItem.first.replaceAll("\""_ostr, "\\\""_ostr);
            auto aSecond = rItem.second.replaceAll("\""_ostr, "\\\""_ostr);
            aPayload.append(", \"" + aFirst + "\": \"" + aSecond + "\"");
        }
    }
    aPayload.append('}');
 
    const OString s = aPayload.makeStringAndClear();
    pThisView->libreOfficeKitViewCallback(LOK_CALLBACK_WINDOW, s);
}
 
void SfxLokHelper::notifyInvalidation(SfxViewShell const* pThisView, tools::Rectangle const* pRect)
{
    // -1 means all parts
    const int nPart = comphelper::LibreOfficeKit::isPartInInvalidation() ? pThisView->getPart() : INT_MIN;
    SfxLokHelper::notifyInvalidation(pThisView, nPart, pRect);
}
 
void SfxLokHelper::notifyInvalidation(SfxViewShell const* pThisView, const int nInPart, tools::Rectangle const* pRect)
{
    if (DisableCallbacks::disabled())
        return;
 
    // -1 means all parts
    const int nPart = comphelper::LibreOfficeKit::isPartInInvalidation() ? nInPart : INT_MIN;
    const int nMode = pThisView->getEditMode();
    pThisView->libreOfficeKitViewInvalidateTilesCallback(pRect, nPart, nMode);
}
 
void SfxLokHelper::notifyDocumentSizeChanged(SfxViewShell const* pThisView, const OString& rPayload, vcl::ITiledRenderable* pDoc, bool bInvalidateAll)
{
    if (!pDoc || pDoc->isDisposed() || DisableCallbacks::disabled())
        return;
 
    if (bInvalidateAll)
    {
        for (int i = 0; i < pDoc->getParts(); ++i)
        {
            tools::Rectangle aRectangle(0, 0, 1000000000, 1000000000);
            const int nMode = pThisView->getEditMode();
            pThisView->libreOfficeKitViewInvalidateTilesCallback(&aRectangle, i, nMode);
        }
    }
    pThisView->libreOfficeKitViewCallback(LOK_CALLBACK_DOCUMENT_SIZE_CHANGED, rPayload);
}
 
void SfxLokHelper::notifyDocumentSizeChangedAllViews(vcl::ITiledRenderable* pDoc, bool bInvalidateAll)
{
    if (DisableCallbacks::disabled())
        return;
 
    // FIXME: Do we know whether it is the views for the document that is in the "current" view that has changed?
    const SfxViewShell* const pCurrentViewShell = SfxViewShell::Current();
    SfxViewShell* pViewShell = SfxViewShell::GetFirst();
    while (pViewShell)
    {
        // FIXME: What if SfxViewShell::Current() returned null?
        // Should we then do this for all views of all open documents
        // or not?
        if (pCurrentViewShell == nullptr || pViewShell->GetDocId() == pCurrentViewShell-> GetDocId())
        {
            SfxLokHelper::notifyDocumentSizeChanged(pViewShell, ""_ostr, pDoc, bInvalidateAll);
            bInvalidateAll = false; // we direct invalidations to all views anyway.
        }
        pViewShell = SfxViewShell::GetNext(*pViewShell);
    }
}
 
void SfxLokHelper::notifyPartSizeChangedAllViews(vcl::ITiledRenderable* pDoc, int nPart)
{
    if (DisableCallbacks::disabled())
        return;
 
    SfxViewShell* pViewShell = SfxViewShell::GetFirst();
    while (pViewShell)
    {
        if (// FIXME should really filter on pViewShell->GetDocId() too
            pViewShell->getPart() == nPart)
            SfxLokHelper::notifyDocumentSizeChanged(pViewShell, ""_ostr, pDoc, false);
        pViewShell = SfxViewShell::GetNext(*pViewShell);
    }
}
 
OString SfxLokHelper::makeVisCursorInvalidation(int nViewId, const OString& rRectangle,
    bool bMispelledWord, const OString& rHyperlink)
{
    if (comphelper::LibreOfficeKit::isViewIdForVisCursorInvalidation())
    {
        OString sHyperlink = rHyperlink.isEmpty() ? "{}"_ostr : rHyperlink;
        return OString::Concat("{ \"viewId\": \"") + OString::number(nViewId) +
            "\", \"rectangle\": \"" + rRectangle +
            "\", \"mispelledWord\": \"" +  OString::number(bMispelledWord ? 1 : 0) +
            "\", \"hyperlink\": " + sHyperlink + " }";
    }
    else
    {
        return rRectangle;
    }
}
 
void SfxLokHelper::notifyAllViews(int nType, const OString& rPayload)
{
    if (DisableCallbacks::disabled())
        return;
 
    const auto payload = rPayload.getStr();
    const SfxViewShell* const pCurrentViewShell = SfxViewShell::Current();
    if (!pCurrentViewShell)
        return;
    SfxViewShell* pViewShell = SfxViewShell::GetFirst();
    while (pViewShell)
    {
        if (pViewShell->GetDocId() == pCurrentViewShell->GetDocId())
            pViewShell->libreOfficeKitViewCallback(nType, payload);
        pViewShell = SfxViewShell::GetNext(*pViewShell);
    }
}
 
void SfxLokHelper::notifyContextChange(const css::ui::ContextChangeEventObject& rEvent)
{
    if (DisableCallbacks::disabled())
        return;
 
    SfxViewShell* pViewShell = SfxViewShell::Get({ rEvent.Source, css::uno::UNO_QUERY });
    if (!pViewShell)
        return;
 
    OUString aBuffer =
        rEvent.ApplicationName.replace(' ', '_') +
        " " +
        rEvent.ContextName.replace(' ', '_');
    pViewShell->libreOfficeKitViewCallback(LOK_CALLBACK_CONTEXT_CHANGED, aBuffer.toUtf8());
}
 
void SfxLokHelper::notifyLog(const std::ostringstream& stream)
{
    if (DisableCallbacks::disabled())
       return;
 
    SfxViewShell* pViewShell = SfxViewShell::Current();
    if (!pViewShell)
       return;
    if (pViewShell->getLibreOfficeKitViewCallback())
    {
        if (!g_logNotifierCache.empty())
        {
            for (const auto& msg : g_logNotifierCache)
            {
                pViewShell->libreOfficeKitViewCallback(LOK_CALLBACK_CORE_LOG, msg.c_str());
            }
            g_logNotifierCache.clear();
        }
        pViewShell->libreOfficeKitViewCallback(LOK_CALLBACK_CORE_LOG, stream.str().c_str());
    }
    else
    {
        while (g_logNotifierCache.size() >= g_logNotifierCacheMaxSize)
            g_logNotifierCache.pop_front();
        g_logNotifierCache.push_back(stream.str());
    }
}
 
namespace
{
std::string extractCertificateWithOffset(const std::string& certificate, size_t& rOffset)
{
    static constexpr std::string_view header("-----BEGIN CERTIFICATE-----");
    static constexpr std::string_view footer("-----END CERTIFICATE-----");
 
    std::string result;
 
    size_t pos1 = certificate.find(header, rOffset);
    if (pos1 == std::string::npos)
        return result;
 
    size_t pos2 = certificate.find(footer, pos1 + 1);
    if (pos2 == std::string::npos)
        return result;
 
    pos1 = pos1 + header.length();
    size_t len = pos2 - pos1;
 
    rOffset = pos2;
    return certificate.substr(pos1, len);
}
}
 
std::string SfxLokHelper::extractCertificate(const std::string & certificate)
{
    size_t nOffset = 0;
    return extractCertificateWithOffset(certificate, nOffset);
}
 
std::vector<std::string> SfxLokHelper::extractCertificates(const std::string& rCerts)
{
    std::vector<std::string> aRet;
    size_t nOffset = 0;
    while (true)
    {
        std::string aNext = extractCertificateWithOffset(rCerts, nOffset);
        if (aNext.empty())
        {
            break;
        }
 
        aRet.push_back(aNext);
    }
    return aRet;
}
 
namespace
{
std::string extractKey(const std::string & privateKey)
{
    static constexpr std::string_view header("-----BEGIN PRIVATE KEY-----");
    static constexpr std::string_view footer("-----END PRIVATE KEY-----");
 
    std::string result;
 
    size_t pos1 = privateKey.find(header);
    if (pos1 == std::string::npos)
        return result;
 
    size_t pos2 = privateKey.find(footer, pos1 + 1);
    if (pos2 == std::string::npos)
        return result;
 
    pos1 = pos1 + header.length();
    pos2 = pos2 - pos1;
 
    return privateKey.substr(pos1, pos2);
}
}
 
css::uno::Reference<css::security::XCertificate> SfxLokHelper::getSigningCertificate(const std::string& rCert, const std::string& rKey)
{
    const uno::Reference<uno::XComponentContext>& xContext = comphelper::getProcessComponentContext();
    uno::Reference<xml::crypto::XSEInitializer> xSEInitializer = xml::crypto::SEInitializer::create(xContext);
    uno::Reference<xml::crypto::XXMLSecurityContext> xSecurityContext = xSEInitializer->createSecurityContext(OUString());
    if (!xSecurityContext.is())
    {
        return {};
    }
 
    uno::Reference<xml::crypto::XSecurityEnvironment> xSecurityEnvironment = xSecurityContext->getSecurityEnvironment();
    uno::Reference<xml::crypto::XCertificateCreator> xCertificateCreator(xSecurityEnvironment, uno::UNO_QUERY);
 
    if (!xCertificateCreator.is())
    {
        return {};
    }
 
    uno::Sequence<sal_Int8> aCertificateSequence;
 
    std::string aCertificateBase64String = extractCertificate(rCert);
    if (!aCertificateBase64String.empty())
    {
        OUString aBase64OUString = OUString::createFromAscii(aCertificateBase64String);
        comphelper::Base64::decode(aCertificateSequence, aBase64OUString);
    }
    else
    {
        aCertificateSequence.realloc(rCert.size());
        std::copy(rCert.c_str(), rCert.c_str() + rCert.size(), aCertificateSequence.getArray());
    }
 
    uno::Sequence<sal_Int8> aPrivateKeySequence;
    std::string aPrivateKeyBase64String = extractKey(rKey);
    if (!aPrivateKeyBase64String.empty())
    {
        OUString aBase64OUString = OUString::createFromAscii(aPrivateKeyBase64String);
        comphelper::Base64::decode(aPrivateKeySequence, aBase64OUString);
    }
    else
    {
        aPrivateKeySequence.realloc(rKey.size());
        std::copy(rKey.c_str(), rKey.c_str() + rKey.size(), aPrivateKeySequence.getArray());
    }
 
    uno::Reference<security::XCertificate> xCertificate = xCertificateCreator->createDERCertificateWithPrivateKey(aCertificateSequence, aPrivateKeySequence);
    return xCertificate;
}
 
uno::Reference<security::XCertificate> SfxLokHelper::addCertificate(
    const css::uno::Reference<css::xml::crypto::XCertificateCreator>& xCertificateCreator,
    const css::uno::Sequence<sal_Int8>& rCert)
{
    // Trust arg is handled by CERT_DecodeTrustString(), see 'man certutil'.
    return xCertificateCreator->addDERCertificateToTheDatabase(rCert, u"TCu,Cu,Tu"_ustr);
}
 
void SfxLokHelper::addCertificates(const std::vector<std::string>& rCerts)
{
    const uno::Reference<uno::XComponentContext>& xContext = comphelper::getProcessComponentContext();
    uno::Reference<xml::crypto::XSEInitializer> xSEInitializer = xml::crypto::SEInitializer::create(xContext);
    uno::Reference<xml::crypto::XXMLSecurityContext> xSecurityContext = xSEInitializer->createSecurityContext(OUString());
    if (!xSecurityContext.is())
    {
        return;
    }
 
    uno::Reference<xml::crypto::XSecurityEnvironment> xSecurityEnvironment = xSecurityContext->getSecurityEnvironment();
    uno::Reference<xml::crypto::XCertificateCreator> xCertificateCreator(xSecurityEnvironment, uno::UNO_QUERY);
    if (!xCertificateCreator.is())
    {
        return;
    }
 
    for (const auto& rCert : rCerts)
    {
        uno::Sequence<sal_Int8> aCertificateSequence;
        OUString aBase64OUString = OUString::fromUtf8(rCert);
        comphelper::Base64::decode(aCertificateSequence, aBase64OUString);
        addCertificate(xCertificateCreator, aCertificateSequence);
    }
 
    // Update the signature state, perhaps the signing certificate is now trusted.
    SfxObjectShell* pObjectShell = SfxObjectShell::Current();
    if (!pObjectShell)
    {
        return;
    }
 
    pObjectShell->RecheckSignature(false);
}
 
bool SfxLokHelper::supportsCommand(std::u16string_view rCommand)
{
    static const std::initializer_list<std::u16string_view> vSupport = { u"Signature" };
 
    return std::find(vSupport.begin(), vSupport.end(), rCommand) != vSupport.end();
}
 
void SfxLokHelper::getCommandValues(tools::JsonWriter& rJsonWriter, std::string_view rCommand)
{
    static constexpr OStringLiteral aSignature(".uno:Signature");
    if (!o3tl::starts_with(rCommand, aSignature))
    {
        return;
    }
 
    SfxObjectShell* pObjectShell = SfxObjectShell::Current();
    if (!pObjectShell)
    {
        return;
    }
 
    svl::crypto::SigningContext aSigningContext;
    pObjectShell->SignDocumentContentUsingCertificate(aSigningContext);
    rJsonWriter.put("signatureTime", aSigningContext.m_nSignatureTime);
 
    uno::Sequence<sal_Int8> aDigest(reinterpret_cast<sal_Int8*>(aSigningContext.m_aDigest.data()),
                                    aSigningContext.m_aDigest.size());
    OUStringBuffer aBuffer;
    comphelper::Base64::encode(aBuffer, aDigest);
    rJsonWriter.put("digest", aBuffer.makeStringAndClear());
}
 
void SfxLokHelper::notifyUpdate(SfxViewShell const* pThisView, int nType)
{
    if (DisableCallbacks::disabled())
        return;
 
    pThisView->libreOfficeKitViewUpdatedCallback(nType);
}
 
void SfxLokHelper::notifyUpdatePerViewId(SfxViewShell const* pThisView, int nType)
{
    notifyUpdatePerViewId(pThisView, pThisView, pThisView, nType);
}
 
void SfxLokHelper::notifyUpdatePerViewId(SfxViewShell const* pTargetShell, SfxViewShell const* pViewShell,
    SfxViewShell const* pSourceShell, int nType)
{
    if (DisableCallbacks::disabled())
        return;
 
    int viewId = SfxLokHelper::getView(pViewShell);
    int sourceViewId = SfxLokHelper::getView(pSourceShell);
    pTargetShell->libreOfficeKitViewUpdatedCallbackPerViewId(nType, viewId, sourceViewId);
}
 
void SfxLokHelper::notifyOtherViewsUpdatePerViewId(SfxViewShell const* pThisView, int nType)
{
    assert(pThisView != nullptr && "pThisView must be valid");
    if (DisableCallbacks::disabled())
        return;
 
    int viewId = SfxLokHelper::getView(pThisView);
    const ViewShellDocId nCurrentDocId = pThisView->GetDocId();
    SfxViewShell* pViewShell = SfxViewShell::GetFirst();
    while (pViewShell)
    {
        if (pViewShell != pThisView && nCurrentDocId == pViewShell->GetDocId())
            pViewShell->libreOfficeKitViewUpdatedCallbackPerViewId(nType, viewId, viewId);
 
        pViewShell = SfxViewShell::GetNext(*pViewShell);
    }
}
 
namespace
{
    struct LOKAsyncEventData
    {
        int mnView; // Window is not enough.
        VclPtr<vcl::Window> mpWindow;
        VclEventId mnEvent;
        MouseEvent maMouseEvent;
        KeyEvent maKeyEvent;
        OUString maText;
    };
 
    void LOKPostAsyncEvent(void* pEv, void*)
    {
        std::unique_ptr<LOKAsyncEventData> pLOKEv(static_cast<LOKAsyncEventData*>(pEv));
        if (pLOKEv->mpWindow->isDisposed())
            return;
 
        int nView = SfxLokHelper::getView(nullptr);
        if (nView != pLOKEv->mnView)
        {
            SAL_INFO("sfx.view", "LOK - view mismatch " << nView << " vs. " << pLOKEv->mnView);
            SfxLokHelper::setView(pLOKEv->mnView);
        }
 
        if (!pLOKEv->mpWindow->HasChildPathFocus(true))
        {
            SAL_INFO("sfx.view", "LOK - focus mismatch, switching focus");
            pLOKEv->mpWindow->GrabFocus();
        }
 
        VclPtr<vcl::Window> pFocusWindow = pLOKEv->mpWindow->GetFocusedWindow();
        if (!pFocusWindow)
            pFocusWindow = pLOKEv->mpWindow;
 
        if (pLOKEv->mpWindow->isDisposed())
            return;
 
        switch (pLOKEv->mnEvent)
        {
        case VclEventId::WindowKeyInput:
        {
            sal_uInt16 nRepeat = pLOKEv->maKeyEvent.GetRepeat();
            KeyEvent singlePress(pLOKEv->maKeyEvent.GetCharCode(),
                                 pLOKEv->maKeyEvent.GetKeyCode());
            for (sal_uInt16 i = 0; i <= nRepeat; ++i)
                if (!pFocusWindow->isDisposed())
                    pFocusWindow->KeyInput(singlePress);
            break;
        }
        case VclEventId::WindowKeyUp:
            if (!pFocusWindow->isDisposed())
                pFocusWindow->KeyUp(pLOKEv->maKeyEvent);
            break;
        case VclEventId::WindowMouseButtonDown:
            pLOKEv->mpWindow->SetLastMousePos(pLOKEv->maMouseEvent.GetPosPixel());
            pLOKEv->mpWindow->MouseButtonDown(pLOKEv->maMouseEvent);
            // Invoke the context menu
            if (pLOKEv->maMouseEvent.GetButtons() & MOUSE_RIGHT)
            {
                const CommandEvent aCEvt(pLOKEv->maMouseEvent.GetPosPixel(), CommandEventId::ContextMenu, true, nullptr);
                pLOKEv->mpWindow->Command(aCEvt);
            }
            break;
        case VclEventId::WindowMouseButtonUp:
            pLOKEv->mpWindow->SetLastMousePos(pLOKEv->maMouseEvent.GetPosPixel());
            pLOKEv->mpWindow->MouseButtonUp(pLOKEv->maMouseEvent);
 
            // sometimes MouseButtonDown captures mouse and starts tracking, and VCL
            // will not take care of releasing that with tiled rendering
            if (pLOKEv->mpWindow->IsTracking())
                pLOKEv->mpWindow->EndTracking();
 
            break;
        case VclEventId::WindowMouseMove:
            pLOKEv->mpWindow->SetLastMousePos(pLOKEv->maMouseEvent.GetPosPixel());
            pLOKEv->mpWindow->MouseMove(pLOKEv->maMouseEvent);
            pLOKEv->mpWindow->RequestHelp(HelpEvent{
                pLOKEv->mpWindow->OutputToScreenPixel(pLOKEv->maMouseEvent.GetPosPixel()),
                HelpEventMode::QUICK }); // If needed, HelpEventMode should be taken from a config
            break;
        case VclEventId::ExtTextInput:
        case VclEventId::EndExtTextInput:
            pLOKEv->mpWindow->PostExtTextInputEvent(pLOKEv->mnEvent, pLOKEv->maText);
            break;
        default:
            assert(false);
            break;
        }
    }
 
    void postEventAsync(LOKAsyncEventData *pEvent)
    {
        if (!pEvent->mpWindow || pEvent->mpWindow->isDisposed())
        {
            SAL_WARN("vcl", "Async event post - but no valid window as destination " << pEvent->mpWindow.get());
            delete pEvent;
            return;
        }
 
        pEvent->mnView = SfxLokHelper::getView(nullptr);
        if (vcl::lok::isUnipoll())
        {
            if (!Application::IsMainThread())
                SAL_WARN("lok", "Posting event directly but not called from main thread!");
            LOKPostAsyncEvent(pEvent, nullptr);
        }
        else
            Application::PostUserEvent(Link<void*, void>(pEvent, LOKPostAsyncEvent));
    }
}
 
void SfxLokHelper::postKeyEventAsync(const VclPtr<vcl::Window> &xWindow,
                                     int nType, int nCharCode, int nKeyCode, int nRepeat)
{
    LOKAsyncEventData* pLOKEv = new LOKAsyncEventData;
    switch (nType)
    {
    case LOK_KEYEVENT_KEYINPUT:
        pLOKEv->mnEvent = VclEventId::WindowKeyInput;
        break;
    case LOK_KEYEVENT_KEYUP:
        pLOKEv->mnEvent = VclEventId::WindowKeyUp;
        break;
    default:
        assert(false);
    }
    pLOKEv->maKeyEvent = KeyEvent(nCharCode, nKeyCode, nRepeat);
    pLOKEv->mpWindow = xWindow;
    postEventAsync(pLOKEv);
}
 
void SfxLokHelper::setBlockedCommandList(int nViewId, const char* blockedCommandList)
{
    SfxViewShell* pViewShell = SfxLokHelper::getViewOfId(nViewId);
 
    if(pViewShell)
    {
        pViewShell->setBlockedCommandList(blockedCommandList);
    }
}
 
void SfxLokHelper::postExtTextEventAsync(const VclPtr<vcl::Window> &xWindow,
                                         int nType, const OUString &rText)
{
    LOKAsyncEventData* pLOKEv = new LOKAsyncEventData;
    switch (nType)
    {
    case LOK_EXT_TEXTINPUT:
        pLOKEv->mnEvent = VclEventId::ExtTextInput;
        pLOKEv->maText = rText;
        break;
    case LOK_EXT_TEXTINPUT_END:
        pLOKEv->mnEvent = VclEventId::EndExtTextInput;
        pLOKEv->maText = "";
        break;
    default:
        assert(false);
    }
    pLOKEv->mpWindow = xWindow;
    postEventAsync(pLOKEv);
}
 
void SfxLokHelper::postMouseEventAsync(const VclPtr<vcl::Window> &xWindow, LokMouseEventData const & rLokMouseEventData)
{
    LOKAsyncEventData* pLOKEv = new LOKAsyncEventData;
    switch (rLokMouseEventData.mnType)
    {
    case LOK_MOUSEEVENT_MOUSEBUTTONDOWN:
        pLOKEv->mnEvent = VclEventId::WindowMouseButtonDown;
        break;
    case LOK_MOUSEEVENT_MOUSEBUTTONUP:
        pLOKEv->mnEvent = VclEventId::WindowMouseButtonUp;
        break;
    case LOK_MOUSEEVENT_MOUSEMOVE:
        pLOKEv->mnEvent = VclEventId::WindowMouseMove;
        break;
    default:
        assert(false);
    }
 
    // no reason - just always true so far.
    assert (rLokMouseEventData.meModifiers == MouseEventModifiers::SIMPLECLICK);
 
    pLOKEv->maMouseEvent = MouseEvent(rLokMouseEventData.maPosition, rLokMouseEventData.mnCount,
                                      rLokMouseEventData.meModifiers, rLokMouseEventData.mnButtons,
                                      rLokMouseEventData.mnModifier);
    if (rLokMouseEventData.maLogicPosition)
    {
        pLOKEv->maMouseEvent.setLogicPosition(*rLokMouseEventData.maLogicPosition);
    }
    pLOKEv->mpWindow = xWindow;
    postEventAsync(pLOKEv);
}
 
void SfxLokHelper::dumpState(rtl::OStringBuffer &rState)
{
    SfxViewShell* pShell = SfxViewShell::Current();
    sal_Int32 nDocId = pShell ? static_cast<sal_Int32>(pShell->GetDocId().get()) : -1;
 
    rState.append("\n\tDocId:\t");
    rState.append(nDocId);
 
    if (nDocId < 0)
        return;
 
    rState.append("\n\tViewCount:\t");
    rState.append(static_cast<sal_Int32>(getViewsCount(nDocId)));
 
    const SfxViewShell* const pCurrentViewShell = SfxViewShell::Current();
    SfxViewShell* pViewShell = SfxViewShell::GetFirst();
    while (pViewShell)
    {
        if (pCurrentViewShell == nullptr || pViewShell->GetDocId() == pCurrentViewShell-> GetDocId())
            pViewShell->dumpLibreOfficeKitViewState(rState);
 
        pViewShell = SfxViewShell::GetNext(*pViewShell);
    }
}
 
bool SfxLokHelper::testInPlaceComponentMouseEventHit(SfxViewShell* pViewShell, int nType, int nX,
                                                     int nY, int nCount, int nButtons,
                                                     int nModifier, double fScaleX, double fScaleY,
                                                     bool bNegativeX)
{
    // In LOK RTL mode draw/svx operates in negative X coordinates
    // But the coordinates from client is always positive, so negate nX.
    if (bNegativeX)
        nX = -nX;
 
    // check if the user hit a chart/math object which is being edited by this view
    if (LokChartHelper aChartHelper(pViewShell, bNegativeX);
        aChartHelper.postMouseEvent(nType, nX, nY, nCount, nButtons, nModifier, fScaleX, fScaleY))
        return true;
 
    if (LokStarMathHelper aMathHelper(pViewShell);
        aMathHelper.postMouseEvent(nType, nX, nY, nCount, nButtons, nModifier, fScaleX, fScaleY))
        return true;
 
    // check if the user hit a chart which is being edited by someone else
    // and, if so, skip current mouse event
    if (nType != LOK_MOUSEEVENT_MOUSEMOVE)
    {
        if (LokChartHelper::HitAny({nX, nY}, bNegativeX))
            return true;
    }
 
    return false;
}
 
VclPtr<vcl::Window> SfxLokHelper::getInPlaceDocWindow(SfxViewShell* pViewShell)
{
    if (VclPtr<vcl::Window> pWindow = LokChartHelper(pViewShell).GetWindow())
        return pWindow;
    if (VclPtr<vcl::Window> pWindow = LokStarMathHelper(pViewShell).GetWidgetWindow())
        return pWindow;
    return {};
}
 
void SfxLokHelper::sendNetworkAccessError(std::string_view rAction)
{
    tools::JsonWriter aWriter;
    aWriter.put("code", static_cast<sal_uInt32>(
        ErrCode(ErrCodeArea::Inet, sal_uInt16(ErrCodeClass::Access))));
    aWriter.put("kind", "network");
    aWriter.put("cmd", rAction);
 
    SfxViewShell* pViewShell = SfxViewShell::Current();
    if (pViewShell)
    {
        pViewShell->libreOfficeKitViewCallback(
            LOK_CALLBACK_ERROR, aWriter.finishAndGetAsOString());
    }
}
 
SfxLokLanguageGuard::SfxLokLanguageGuard(SfxViewShell* pNewShell)
    : m_bSetLanguage(false)
    , m_pOldShell(nullptr)
{
    m_pOldShell = SfxViewShell::Current();
    if (!comphelper::LibreOfficeKit::isActive() || !pNewShell || pNewShell == m_pOldShell)
    {
        return;
    }
 
    // The current view ID is not the one that belongs to this frame, update
    // language/locale.
    comphelper::LibreOfficeKit::setLanguageTag(pNewShell->GetLOKLanguageTag());
    comphelper::LibreOfficeKit::setLocale(pNewShell->GetLOKLocale());
    m_bSetLanguage = true;
}
 
SfxLokLanguageGuard::~SfxLokLanguageGuard()
{
    if (!m_bSetLanguage || !m_pOldShell)
    {
        return;
    }
 
    comphelper::LibreOfficeKit::setLanguageTag(m_pOldShell->GetLOKLanguageTag());
    comphelper::LibreOfficeKit::setLocale(m_pOldShell->GetLOKLocale());
}
 
LOKEditViewHistory::EditViewHistoryMap LOKEditViewHistory::maEditViewHistory;
 
 
void LOKEditViewHistory::Update(bool bRemove)
{
    if (!comphelper::LibreOfficeKit::isActive())
        return;
 
    SfxViewShell* pViewShell = SfxViewShell::Current();
    if (pViewShell)
    {
        int nDocId = pViewShell->GetDocId().get();
        if (maEditViewHistory.find(nDocId) != maEditViewHistory.end())
            maEditViewHistory[nDocId].remove(pViewShell);
        if (!bRemove)
        {
            maEditViewHistory[nDocId].push_back(pViewShell);
            if (maEditViewHistory[nDocId].size() > 10)
                maEditViewHistory[nDocId].pop_front();
        }
    }
}
 
ViewShellList LOKEditViewHistory::GetHistoryForDoc(ViewShellDocId aDocId)
{
    int nDocId = aDocId.get();
    ViewShellList aResult;
    if (maEditViewHistory.find(nDocId) != maEditViewHistory.end())
        aResult = maEditViewHistory.at(nDocId);
    return aResult;
}
 
 ViewShellList LOKEditViewHistory::GetSortedViewsForDoc(ViewShellDocId aDocId)
 {
     ViewShellList aEditViewHistoryForDoc = LOKEditViewHistory::GetHistoryForDoc(aDocId);
     // all views where document is loaded
     ViewShellList aCurrentDocViewList;
     // active views that are listed in the edit history
     ViewShellList aEditedViewList;
 
     // Populate aCurrentDocViewList and aEditedViewList
     SfxViewShell* pViewShell = SfxViewShell::GetFirst();
     while (pViewShell)
     {
         if (pViewShell->GetDocId() == aDocId)
         {
             if (aEditViewHistoryForDoc.empty() ||
                 std::find(aEditViewHistoryForDoc.begin(), aEditViewHistoryForDoc.end(),
                           pViewShell) == aEditViewHistoryForDoc.end())
             {
                 // append views not listed in the edit history;
                 // the edit history is limited to 10 views,
                 // so it could miss some view where in place editing is occurring
                 aCurrentDocViewList.push_back(pViewShell);
             }
             else
             {
                 // view is listed in the edit history
                 aEditedViewList.push_back(pViewShell);
             }
         }
         pViewShell = SfxViewShell::GetNext(*pViewShell);
     }
 
     // in case some no more active view needs to be removed from the history
     aEditViewHistoryForDoc.remove_if(
         [&aEditedViewList](SfxViewShell* pHistoryItem) {
             return std::find(aEditedViewList.begin(), aEditedViewList.end(), pHistoryItem) == aEditedViewList.end();
         });
 
     // place views belonging to the edit history at the end
     aCurrentDocViewList.splice(aCurrentDocViewList.end(), aEditViewHistoryForDoc);
 
     return aCurrentDocViewList;
 }
 
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */

V530 The return value of function 'append' is required to be utilized.

V530 The return value of function 'append' is required to be utilized.

V530 The return value of function 'append' is required to be utilized.

V530 The return value of function 'append' is required to be utilized.

V530 The return value of function 'append' is required to be utilized.