summaryrefslogtreecommitdiffstats
path: root/test/source/lokcallback.cxx
blob: 767448c771cae76b5ef9827df8d738ac92e0bb7c (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
/* -*- 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 <test/lokcallback.hxx>

#include <LibreOfficeKit/LibreOfficeKitEnums.h>
#include <rtl/strbuf.hxx>
#include <tools/gen.hxx>
#include <comphelper/lok.hxx>
#include <sfx2/viewsh.hxx>

TestLokCallbackWrapper::TestLokCallbackWrapper(LibreOfficeKitCallback callback, void* data)
    : Idle("TestLokCallbackWrapper flush timer")
    , m_callback(callback)
    , m_data(data)
{
    // Flushing timer needs to run with the lowest priority, so that all pending tasks
    // such as invalidations are processed before it.
    SetPriority(TaskPriority::LOWEST);
}

void TestLokCallbackWrapper::clear()
{
    m_viewId = -1;
    m_updatedTypes.clear();
    m_updatedTypesPerViewId.clear();
}

inline void TestLokCallbackWrapper::startTimer()
{
    if (!IsActive())
        Start();
}

constexpr int NO_VIEWID = -1;

inline void TestLokCallbackWrapper::callCallback(int nType, const char* pPayload, int nViewId)
{
    discardUpdatedTypes(nType, nViewId);
    m_callback(nType, pPayload, m_data);
    startTimer();
}

void TestLokCallbackWrapper::libreOfficeKitViewCallback(int nType, const rtl::OString& pPayload)
{
    callCallback(nType, pPayload.getStr(), NO_VIEWID);
}

void TestLokCallbackWrapper::libreOfficeKitViewCallbackWithViewId(int nType,
                                                                  const rtl::OString& pPayload,
                                                                  int nViewId)
{
    callCallback(nType, pPayload.getStr(), nViewId);
}

void TestLokCallbackWrapper::libreOfficeKitViewInvalidateTilesCallback(
    const tools::Rectangle* pRect, int nPart, int nMode)
{
    OStringBuffer buf(64);
    if (pRect)
        buf.append(pRect->toString());
    else
        buf.append("EMPTY");
    if (comphelper::LibreOfficeKit::isPartInInvalidation())
    {
        buf.append(", " + OString::number(static_cast<sal_Int32>(nPart)) + ", "
                   + OString::number(static_cast<sal_Int32>(nMode)));
    }
    callCallback(LOK_CALLBACK_INVALIDATE_TILES, buf.makeStringAndClear().getStr(), NO_VIEWID);
}

// TODO This is probably a pointless code duplication with CallbackFlushHandler,
// and using this in unittests also means that CallbackFlushHandler does not get
// tested as thoroughly as it could. On the other hand, this class is simpler,
// so debugging those unittests should also be simpler. The proper solution
// is presumably this class using CallbackFlushHandler internally by default,
// but having an option to use this simpler code when needed.

void TestLokCallbackWrapper::libreOfficeKitViewUpdatedCallback(int nType)
{
    if (std::find(m_updatedTypes.begin(), m_updatedTypes.end(), nType) == m_updatedTypes.end())
    {
        m_updatedTypes.push_back(nType);
        startTimer();
    }
}

void TestLokCallbackWrapper::libreOfficeKitViewUpdatedCallbackPerViewId(int nType, int nViewId,
                                                                        int nSourceViewId)
{
    const PerViewIdData data{ nType, nViewId, nSourceViewId };
    auto& l = m_updatedTypesPerViewId;
    // The source view doesn't matter for uniqueness, just keep the latest one.
    auto it = std::find_if(l.begin(), l.end(), [data](const PerViewIdData& other) {
        return data.type == other.type && data.viewId == other.viewId;
    });
    if (it != l.end())
        *it = data;
    else
        l.push_back(data);
    startTimer();
}

void TestLokCallbackWrapper::libreOfficeKitViewAddPendingInvalidateTiles()
{
    // Invoke() will call flushPendingLOKInvalidateTiles().
    startTimer();
}

void TestLokCallbackWrapper::discardUpdatedTypes(int nType, int nViewId)
{
    // If a callback is called directly with an event, drop the updated flag for it, since
    // the direct event replaces it.
    for (auto it = m_updatedTypes.begin(); it != m_updatedTypes.end();)
    {
        if (*it == nType)
            it = m_updatedTypes.erase(it);
        else
            ++it;
    }
    // If we do not have a specific view id, drop flag for all views.
    bool allViewIds = false;
    if (nViewId < 0)
        allViewIds = true;
    if (nType == LOK_CALLBACK_INVALIDATE_VISIBLE_CURSOR
        && !comphelper::LibreOfficeKit::isViewIdForVisCursorInvalidation())
        allViewIds = true;
    for (auto it = m_updatedTypesPerViewId.begin(); it != m_updatedTypesPerViewId.end();)
    {
        if (it->type == nType && (allViewIds || it->viewId == nViewId))
            it = m_updatedTypesPerViewId.erase(it);
        else
            ++it;
    }
}

void TestLokCallbackWrapper::flushLOKData()
{
    if (m_updatedTypes.empty() && m_updatedTypesPerViewId.empty())
        return;
    // Ask for payloads of all the pending types that need updating, and call the generic callback with that data.
    assert(m_viewId >= 0);
    SfxViewShell* viewShell = SfxViewShell::GetFirst(false, [this](const SfxViewShell* shell) {
        return shell->GetViewShellId().get() == m_viewId;
    });
    assert(viewShell != nullptr);
    // First move data to local structures, so that callbacks don't possibly modify it.
    std::vector<int> updatedTypes;
    std::swap(updatedTypes, m_updatedTypes);
    std::vector<PerViewIdData> updatedTypesPerViewId;
    std::swap(updatedTypesPerViewId, m_updatedTypesPerViewId);

    for (int type : updatedTypes)
    {
        std::optional<OString> payload = viewShell->getLOKPayload(type, m_viewId);
        if (payload)
            libreOfficeKitViewCallback(type, *payload);
    }
    for (const PerViewIdData& data : updatedTypesPerViewId)
    {
        viewShell = SfxViewShell::GetFirst(false, [data](const SfxViewShell* shell) {
            return shell->GetViewShellId().get() == data.sourceViewId;
        });
        assert(viewShell != nullptr);
        std::optional<OString> payload = viewShell->getLOKPayload(data.type, data.viewId);
        if (payload)
            libreOfficeKitViewCallbackWithViewId(data.type, *payload, data.viewId);
    }
}

void TestLokCallbackWrapper::Invoke()
{
    // Timer timeout, flush any possibly pending data.
    for (SfxViewShell* viewShell = SfxViewShell::GetFirst(false); viewShell != nullptr;
         viewShell = SfxViewShell::GetNext(*viewShell, false))
    {
        viewShell->flushPendingLOKInvalidateTiles();
    }
    flushLOKData();
}

/* vim:set shiftwidth=4 softtabstop=4 expandtab: */