Coverage Report

Created: 2026-11-06 11:29

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/libreoffice/sd/source/ui/sidebar/RecentlyUsedMasterPages.cxx
Line
Count
Source
1
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2
/*
3
 * This file is part of the LibreOffice project.
4
 *
5
 * This Source Code Form is subject to the terms of the Mozilla Public
6
 * License, v. 2.0. If a copy of the MPL was not distributed with this
7
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
8
 *
9
 * This file incorporates work covered by the following license notice:
10
 *
11
 *   Licensed to the Apache Software Foundation (ASF) under one or more
12
 *   contributor license agreements. See the NOTICE file distributed
13
 *   with this work for additional information regarding copyright
14
 *   ownership. The ASF licenses this file to you under the Apache
15
 *   License, Version 2.0 (the "License"); you may not use this file
16
 *   except in compliance with the License. You may obtain a copy of
17
 *   the License at http://www.apache.org/licenses/LICENSE-2.0 .
18
 */
19
20
#include "RecentlyUsedMasterPages.hxx"
21
#include "MasterPageContainerProviders.hxx"
22
#include <MasterPageObserver.hxx>
23
#include "MasterPageDescriptor.hxx"
24
#include <tools/ConfigurationAccess.hxx>
25
26
#include <algorithm>
27
#include <memory>
28
#include <vector>
29
30
#include <com/sun/star/container/XNameAccess.hpp>
31
#include <com/sun/star/container/XNameContainer.hpp>
32
#include <com/sun/star/lang/XSingleServiceFactory.hpp>
33
#include <osl/doublecheckedlocking.h>
34
#include <osl/getglobalmutex.hxx>
35
36
using namespace ::com::sun::star;
37
using namespace ::com::sun::star::uno;
38
39
namespace {
40
41
OUString GetPathToImpressConfigurationRoot()
42
0
{
43
0
    return u"/org.openoffice.Office.Impress/"_ustr;
44
0
}
45
OUString GetPathToSetNode()
46
0
{
47
0
    return u"MultiPaneGUI/ToolPanel/RecentlyUsedMasterPages"_ustr;
48
0
}
49
50
} // end of anonymous namespace
51
52
namespace sd::sidebar {
53
54
RecentlyUsedMasterPages* RecentlyUsedMasterPages::mpInstance = nullptr;
55
56
RecentlyUsedMasterPages&  RecentlyUsedMasterPages::Instance()
57
0
{
58
0
    if (mpInstance == nullptr)
59
0
    {
60
0
        ::osl::GetGlobalMutex aMutexFunctor;
61
0
        ::osl::MutexGuard aGuard (aMutexFunctor());
62
0
        if (mpInstance == nullptr)
63
0
        {
64
0
            RecentlyUsedMasterPages* pInstance = new RecentlyUsedMasterPages();
65
0
            pInstance->LateInit();
66
0
            SdGlobalResourceContainer::Instance().AddResource (
67
0
                ::std::unique_ptr<SdGlobalResource>(pInstance));
68
0
            OSL_DOUBLE_CHECKED_LOCKING_MEMORY_BARRIER();
69
0
            mpInstance = pInstance;
70
0
        }
71
0
    }
72
0
    else {
73
0
        OSL_DOUBLE_CHECKED_LOCKING_MEMORY_BARRIER();
74
0
    }
75
76
0
    return *mpInstance;
77
0
}
78
79
constexpr size_t gnMaxListSize(8);
80
81
RecentlyUsedMasterPages::RecentlyUsedMasterPages()
82
0
    : mpContainer(std::make_shared<MasterPageContainer>())
83
0
{
84
0
}
85
86
void RecentlyUsedMasterPages::ImplDestroy()
87
0
{
88
0
    Link<MasterPageContainerChangeEvent&,void> aLink (LINK(this,RecentlyUsedMasterPages,MasterPageContainerChangeListener));
89
0
    mpContainer->RemoveChangeListener(aLink);
90
91
0
    MasterPageObserver::Instance().RemoveEventListener(
92
0
        LINK(this,RecentlyUsedMasterPages,MasterPageChangeListener));
93
0
}
94
95
RecentlyUsedMasterPages::~RecentlyUsedMasterPages()
96
0
{
97
0
    suppress_fun_call_w_exception(ImplDestroy());
98
0
}
99
100
void RecentlyUsedMasterPages::LateInit()
101
0
{
102
0
    Link<MasterPageContainerChangeEvent&,void> aLink (LINK(this,RecentlyUsedMasterPages,MasterPageContainerChangeListener));
103
0
    mpContainer->AddChangeListener(aLink);
104
105
0
    LoadPersistentValues ();
106
0
    MasterPageObserver::Instance().AddEventListener(
107
0
        LINK(this,RecentlyUsedMasterPages,MasterPageChangeListener));
108
0
}
109
110
void RecentlyUsedMasterPages::LoadPersistentValues()
111
0
{
112
0
    try
113
0
    {
114
0
        sdtools::ConfigurationAccess aConfiguration (
115
0
            GetPathToImpressConfigurationRoot(),
116
0
            sdtools::ConfigurationAccess::READ_ONLY);
117
0
        Reference<container::XNameAccess> xSet (
118
0
            aConfiguration.GetConfigurationNode(GetPathToSetNode()),
119
0
            UNO_QUERY);
120
0
        if ( ! xSet.is())
121
0
            return;
122
123
0
        static constexpr OUStringLiteral sURLMemberName(u"URL");
124
0
        static constexpr OUStringLiteral sNameMemberName(u"Name");
125
0
        OUString sURL;
126
0
        OUString sName;
127
128
        // Read the names and URLs of the master pages.
129
0
        const Sequence<OUString> aKeys (xSet->getElementNames());
130
0
        mvMasterPages.clear();
131
0
        mvMasterPages.reserve(aKeys.getLength());
132
0
        for (const auto& rKey : aKeys)
133
0
        {
134
0
            Reference<container::XNameAccess> xSetItem (
135
0
                xSet->getByName(rKey), UNO_QUERY);
136
0
            if (xSetItem.is())
137
0
            {
138
0
                Any aURL (xSetItem->getByName(sURLMemberName));
139
0
                Any aName (xSetItem->getByName(sNameMemberName));
140
0
                aURL >>= sURL;
141
0
                aName >>= sName;
142
0
                SharedMasterPageDescriptor pDescriptor = std::make_shared<MasterPageDescriptor>(
143
0
                    MasterPageContainer::TEMPLATE,
144
0
                    -1,
145
0
                    sURL,
146
0
                    OUString(),
147
0
                    sName,
148
0
                    false,
149
0
                    std::make_shared<TemplatePageObjectProvider>(sURL),
150
0
                    std::make_shared<TemplatePreviewProvider>(sURL));
151
                // For user supplied templates we use a different
152
                // preview provider: The preview in the document shows
153
                // not only shapes on the master page but also shapes on
154
                // the foreground.  This is misleading and therefore
155
                // these previews are discarded and created directly
156
                // from the page objects.
157
0
                if (pDescriptor->GetURLClassification() == MasterPageDescriptor::URLCLASS_USER)
158
0
                    pDescriptor->mpPreviewProvider = std::make_shared<PagePreviewProvider>();
159
0
                MasterPageContainer::Token aToken (mpContainer->PutMasterPage(pDescriptor));
160
0
                mvMasterPages.emplace_back(aToken,sURL,sName);
161
0
            }
162
0
        }
163
164
0
        ResolveList();
165
0
    }
166
0
    catch (Exception&)
167
0
    {
168
        // Ignore exception.
169
0
    }
170
0
}
171
172
void RecentlyUsedMasterPages::SavePersistentValues()
173
0
{
174
0
    try
175
0
    {
176
0
        sdtools::ConfigurationAccess aConfiguration (
177
0
            GetPathToImpressConfigurationRoot(),
178
0
            sdtools::ConfigurationAccess::READ_WRITE);
179
0
        Reference<container::XNameContainer> xSet (
180
0
            aConfiguration.GetConfigurationNode(GetPathToSetNode()),
181
0
            UNO_QUERY);
182
0
        if ( ! xSet.is())
183
0
            return;
184
185
        // Clear the set.
186
0
        const Sequence<OUString> aKeys (xSet->getElementNames());
187
0
        for (const auto& rKey : aKeys)
188
0
            xSet->removeByName (rKey);
189
190
        // Fill it with the URLs of this object.
191
0
        static constexpr OUStringLiteral sURLMemberName(u"URL");
192
0
        static constexpr OUStringLiteral sNameMemberName(u"Name");
193
0
        Any aValue;
194
0
        Reference<lang::XSingleServiceFactory> xChildFactory (
195
0
            xSet, UNO_QUERY);
196
0
        if ( ! xChildFactory.is())
197
0
            return;
198
0
        sal_Int32 nIndex(0);
199
0
        for (const auto& rDescriptor : mvMasterPages)
200
0
        {
201
            // Create new child.
202
0
            OUString sKey = "index_" + OUString::number(nIndex);
203
0
            Reference<container::XNameReplace> xChild(
204
0
                xChildFactory->createInstance(), UNO_QUERY);
205
0
            if (xChild.is())
206
0
            {
207
0
                xSet->insertByName (sKey, Any(xChild));
208
209
0
                aValue <<= rDescriptor.msURL;
210
0
                xChild->replaceByName (sURLMemberName, aValue);
211
212
0
                aValue <<= rDescriptor.msName;
213
0
                xChild->replaceByName (sNameMemberName, aValue);
214
0
            }
215
0
            ++nIndex;
216
0
        }
217
218
        // Write the data back to disk.
219
0
        aConfiguration.CommitChanges();
220
0
    }
221
0
    catch (Exception&)
222
0
    {
223
        // Ignore exception.
224
0
    }
225
0
}
226
227
void RecentlyUsedMasterPages::AddEventListener (const Link<LinkParamNone*,void>& rEventListener)
228
0
{
229
0
    if (::std::find (
230
0
        maListeners.begin(),
231
0
        maListeners.end(),
232
0
        rEventListener) == maListeners.end())
233
0
    {
234
0
        maListeners.push_back (rEventListener);
235
0
    }
236
0
}
237
238
void RecentlyUsedMasterPages::RemoveEventListener (const Link<LinkParamNone*,void>& rEventListener)
239
0
{
240
0
    auto aIter = ::std::find (
241
0
        maListeners.begin(),
242
0
        maListeners.end(),
243
0
        rEventListener);
244
0
    if (aIter != maListeners.end())
245
0
        maListeners.erase(aIter);
246
0
}
247
248
int RecentlyUsedMasterPages::GetMasterPageCount() const
249
0
{
250
0
    return mvMasterPages.size();
251
0
}
252
253
MasterPageContainer::Token RecentlyUsedMasterPages::GetTokenForIndex (sal_uInt32 nIndex) const
254
0
{
255
0
    if(nIndex<mvMasterPages.size())
256
0
        return mvMasterPages[nIndex].maToken;
257
0
    else
258
0
        return MasterPageContainer::NIL_TOKEN;
259
0
}
260
261
void RecentlyUsedMasterPages::SendEvent()
262
0
{
263
0
    for (const auto& aLink : maListeners)
264
0
    {
265
0
        aLink.Call(nullptr);
266
0
    }
267
0
}
268
269
IMPL_LINK(RecentlyUsedMasterPages, MasterPageChangeListener,
270
    MasterPageObserverEvent&, rEvent, void)
271
0
{
272
0
    switch (rEvent.meType)
273
0
    {
274
0
        case MasterPageObserverEvent::ET_MASTER_PAGE_ADDED:
275
0
        case MasterPageObserverEvent::ET_MASTER_PAGE_EXISTS:
276
0
            AddMasterPage(
277
0
                mpContainer->GetTokenForStyleName(rEvent.mrMasterPageName));
278
0
            break;
279
280
0
        case MasterPageObserverEvent::ET_MASTER_PAGE_REMOVED:
281
            // Do not change the list of recently master pages (the deleted
282
            // page was recently used) but tell the listeners.  They may want
283
            // to update their lists.
284
0
            SendEvent();
285
0
            break;
286
0
    }
287
0
}
288
289
IMPL_LINK(RecentlyUsedMasterPages, MasterPageContainerChangeListener,
290
    MasterPageContainerChangeEvent&, rEvent, void)
291
0
{
292
0
    switch (rEvent.meEventType)
293
0
    {
294
0
        case MasterPageContainerChangeEvent::EventType::CHILD_ADDED:
295
0
        case MasterPageContainerChangeEvent::EventType::CHILD_REMOVED:
296
0
        case MasterPageContainerChangeEvent::EventType::INDEX_CHANGED:
297
0
            ResolveList();
298
0
            break;
299
300
0
        default:
301
            // Ignored.
302
0
            break;
303
0
    }
304
0
}
305
306
void RecentlyUsedMasterPages::AddMasterPage (
307
    MasterPageContainer::Token aToken)
308
0
{
309
    // For the page to be inserted the token has to be valid and the page
310
    // has to have a valid URL.  This excludes master pages that do not come
311
    // from template files.
312
0
    if (aToken == MasterPageContainer::NIL_TOKEN
313
0
        || mpContainer->GetURLForToken(aToken).isEmpty())
314
0
        return;
315
316
0
    MasterPageList::iterator aIterator (
317
0
        ::std::find_if(mvMasterPages.begin(),mvMasterPages.end(),
318
0
            Descriptor::TokenComparator(aToken)));
319
0
    if (aIterator != mvMasterPages.end())
320
0
    {
321
        // When an entry for the given token already exists then remove
322
        // it now and insert it later at the head of the list.
323
0
        mvMasterPages.erase (aIterator);
324
0
    }
325
326
0
    mvMasterPages.insert(mvMasterPages.begin(),
327
0
        Descriptor(
328
0
            aToken,
329
0
            mpContainer->GetURLForToken(aToken),
330
0
            mpContainer->GetStyleNameForToken(aToken)));
331
332
    // Shorten list to maximal size.
333
0
    while (mvMasterPages.size() > gnMaxListSize)
334
0
    {
335
0
        mvMasterPages.pop_back ();
336
0
    }
337
338
0
    SavePersistentValues ();
339
0
    SendEvent();
340
0
}
341
342
void RecentlyUsedMasterPages::ResolveList()
343
0
{
344
0
    bool bNotify (false);
345
346
0
    for (auto& rDescriptor : mvMasterPages)
347
0
    {
348
0
        if (rDescriptor.maToken == MasterPageContainer::NIL_TOKEN)
349
0
        {
350
0
            MasterPageContainer::Token aToken (mpContainer->GetTokenForURL(rDescriptor.msURL));
351
0
            rDescriptor.maToken = aToken;
352
0
            if (aToken != MasterPageContainer::NIL_TOKEN)
353
0
                bNotify = true;
354
0
        }
355
0
        else
356
0
        {
357
0
            if ( ! mpContainer->HasToken(rDescriptor.maToken))
358
0
            {
359
0
                rDescriptor.maToken = MasterPageContainer::NIL_TOKEN;
360
0
                bNotify = true;
361
0
            }
362
0
        }
363
0
    }
364
365
0
    if (bNotify)
366
0
        SendEvent();
367
0
}
368
369
} // end of namespace sd::sidebar
370
371
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */