Coverage Report

Created: 2025-07-07 10:01

/src/libreoffice/sw/source/uibase/utlui/gloslst.cxx
Line
Count
Source (jump to first uncovered line)
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 <vcl/weld.hxx>
21
#include <svl/fstathelper.hxx>
22
#include <unotools/pathoptions.hxx>
23
#include <unotools/transliterationwrapper.hxx>
24
#include <osl/diagnose.h>
25
#include <o3tl/string_view.hxx>
26
#include <swtypes.hxx>
27
#include <swmodule.hxx>
28
#include <shellio.hxx>
29
#include <initui.hxx>
30
#include <glosdoc.hxx>
31
#include <gloslst.hxx>
32
#include <swunohelper.hxx>
33
#include <view.hxx>
34
35
#include <vector>
36
37
0
#define STRING_DELIM char(0x0A)
38
0
#define GLOS_TIMEOUT 30000   // update every 30 seconds
39
#define FIND_MAX_GLOS 20
40
41
namespace {
42
43
struct TripleString
44
{
45
    OUString sGroup;
46
    OUString sBlock;
47
    OUString sShort;
48
};
49
50
class SwGlossDecideDlg : public weld::GenericDialogController
51
{
52
    std::unique_ptr<weld::Button> m_xOk;
53
    std::unique_ptr<weld::TreeView> m_xListLB;
54
55
    DECL_LINK(DoubleClickHdl, weld::TreeView&, bool);
56
    DECL_LINK(SelectHdl, weld::TreeView&, void);
57
58
public:
59
    explicit SwGlossDecideDlg(weld::Window* pParent);
60
61
0
    weld::TreeView& GetTreeView() {return *m_xListLB;}
62
};
63
64
}
65
66
SwGlossDecideDlg::SwGlossDecideDlg(weld::Window* pParent)
67
0
    : GenericDialogController(pParent, u"modules/swriter/ui/selectautotextdialog.ui"_ustr, u"SelectAutoTextDialog"_ustr)
68
0
    , m_xOk(m_xBuilder->weld_button(u"ok"_ustr))
69
0
    , m_xListLB(m_xBuilder->weld_tree_view(u"treeview"_ustr))
70
0
{
71
0
    m_xListLB->set_size_request(m_xListLB->get_approximate_digit_width() * 32,
72
0
                                m_xListLB->get_height_rows(8));
73
0
    m_xListLB->connect_row_activated(LINK(this, SwGlossDecideDlg, DoubleClickHdl));
74
0
    m_xListLB->connect_selection_changed(LINK(this, SwGlossDecideDlg, SelectHdl));
75
0
}
76
77
IMPL_LINK_NOARG(SwGlossDecideDlg, DoubleClickHdl, weld::TreeView&, bool)
78
0
{
79
0
    m_xDialog->response(RET_OK);
80
0
    return true;
81
0
}
82
83
IMPL_LINK_NOARG(SwGlossDecideDlg, SelectHdl, weld::TreeView&, void)
84
0
{
85
0
    m_xOk->set_sensitive(m_xListLB->get_selected_index() != -1);
86
0
}
87
88
SwGlossaryList::SwGlossaryList() :
89
0
    AutoTimer("SwGlossaryList"), m_bFilled(false)
90
0
{
91
0
    SvtPathOptions aPathOpt;
92
0
    m_sPath = aPathOpt.GetAutoTextPath();
93
0
    SetTimeout(GLOS_TIMEOUT);
94
0
}
95
96
SwGlossaryList::~SwGlossaryList()
97
0
{
98
0
    ClearGroups();
99
0
}
100
101
// If the GroupName is already known, then only rShortName
102
// will be filled. Otherwise also rGroupName will be set and
103
// on demand asked for the right group.
104
105
bool SwGlossaryList::GetShortName(std::u16string_view rLongName,
106
                                  OUString& rShortName, OUString& rGroupName )
107
0
{
108
0
    if(!m_bFilled)
109
0
        Update();
110
111
0
    std::vector<TripleString> aTripleStrings;
112
113
0
    size_t nCount = m_aGroupArr.size();
114
0
    for(size_t i = 0; i < nCount; i++ )
115
0
    {
116
0
        AutoTextGroup* pGroup = m_aGroupArr[i].get();
117
0
        if(!rGroupName.isEmpty() && rGroupName != pGroup->sName)
118
0
            continue;
119
120
0
        sal_Int32 nPosLong = 0;
121
0
        for(sal_uInt16 j = 0; j < pGroup->nCount; j++)
122
0
        {
123
0
            const OUString sLong = pGroup->sLongNames.getToken(0, STRING_DELIM, nPosLong);
124
0
            if(rLongName != sLong)
125
0
                continue;
126
127
0
            TripleString aTriple;
128
0
            aTriple.sGroup = pGroup->sName;
129
0
            aTriple.sBlock = sLong;
130
0
            aTriple.sShort = pGroup->sShortNames.getToken(j, STRING_DELIM);
131
0
            aTripleStrings.push_back(aTriple);
132
0
        }
133
0
    }
134
135
0
    bool bRet = false;
136
0
    nCount = aTripleStrings.size();
137
0
    if(1 == nCount)
138
0
    {
139
0
        const TripleString& rTriple(aTripleStrings.front());
140
0
        rShortName = rTriple.sShort;
141
0
        rGroupName = rTriple.sGroup;
142
0
        bRet = true;
143
0
    }
144
0
    else if(1 < nCount)
145
0
    {
146
0
        SwView *pView  = ::GetActiveView();
147
0
        if (!pView)
148
0
            return bRet;
149
0
        SwGlossDecideDlg aDlg(pView->GetFrameWeld());
150
0
        OUString sTitle = aDlg.get_title() + " " + aTripleStrings.front().sBlock;
151
0
        aDlg.set_title(sTitle);
152
153
0
        weld::TreeView& rLB = aDlg.GetTreeView();
154
0
        for (const auto& rTriple : aTripleStrings)
155
0
            rLB.append_text(rTriple.sGroup.getToken(0, GLOS_DELIM));
156
157
0
        rLB.select(0);
158
0
        if (aDlg.run() == RET_OK && rLB.get_selected_index() != -1)
159
0
        {
160
0
            const TripleString& rTriple(aTripleStrings[rLB.get_selected_index()]);
161
0
            rShortName = rTriple.sShort;
162
0
            rGroupName = rTriple.sGroup;
163
0
            bRet = true;
164
0
        }
165
0
        else
166
0
            bRet = false;
167
0
    }
168
0
    return bRet;
169
0
}
170
171
size_t SwGlossaryList::GetGroupCount()
172
0
{
173
0
    if(!m_bFilled)
174
0
        Update();
175
0
    return m_aGroupArr.size();
176
0
}
177
178
OUString SwGlossaryList::GetGroupName(size_t nPos)
179
0
{
180
0
    OSL_ENSURE(m_aGroupArr.size() > nPos, "group not available");
181
0
    if(nPos < m_aGroupArr.size())
182
0
    {
183
0
        AutoTextGroup* pGroup = m_aGroupArr[nPos].get();
184
0
        OUString sRet = pGroup->sName;
185
0
        return sRet;
186
0
    }
187
0
    return OUString();
188
0
}
189
190
OUString SwGlossaryList::GetGroupTitle(size_t nPos)
191
0
{
192
0
    OSL_ENSURE(m_aGroupArr.size() > nPos, "group not available");
193
0
    if(nPos < m_aGroupArr.size())
194
0
    {
195
0
        AutoTextGroup* pGroup = m_aGroupArr[nPos].get();
196
0
        return pGroup->sTitle;
197
0
    }
198
0
    return OUString();
199
0
}
200
201
sal_uInt16 SwGlossaryList::GetBlockCount(size_t nGroup)
202
0
{
203
0
    OSL_ENSURE(m_aGroupArr.size() > nGroup, "group not available");
204
0
    if(nGroup < m_aGroupArr.size())
205
0
    {
206
0
        AutoTextGroup* pGroup = m_aGroupArr[nGroup].get();
207
0
        return pGroup->nCount;
208
0
    }
209
0
    return 0;
210
0
}
211
212
OUString SwGlossaryList::GetBlockLongName(size_t nGroup, sal_uInt16 nBlock)
213
0
{
214
0
    OSL_ENSURE(m_aGroupArr.size() > nGroup, "group not available");
215
0
    if(nGroup < m_aGroupArr.size())
216
0
    {
217
0
        AutoTextGroup* pGroup = m_aGroupArr[nGroup].get();
218
0
        return pGroup->sLongNames.getToken(nBlock, STRING_DELIM);
219
0
    }
220
0
    return OUString();
221
0
}
222
223
OUString SwGlossaryList::GetBlockShortName(size_t nGroup, sal_uInt16 nBlock)
224
0
{
225
0
    OSL_ENSURE(m_aGroupArr.size() > nGroup, "group not available");
226
0
    if(nGroup < m_aGroupArr.size())
227
0
    {
228
0
        AutoTextGroup* pGroup = m_aGroupArr[nGroup].get();
229
0
        return pGroup->sShortNames.getToken(nBlock, STRING_DELIM);
230
0
    }
231
0
    return OUString();
232
0
}
233
234
void SwGlossaryList::Update()
235
0
{
236
0
    if(!IsActive())
237
0
        Start();
238
239
0
    SvtPathOptions aPathOpt;
240
0
    const OUString& sTemp( aPathOpt.GetAutoTextPath() );
241
0
    if(sTemp != m_sPath)
242
0
    {
243
0
        m_sPath = sTemp;
244
0
        m_bFilled = false;
245
0
        ClearGroups();
246
0
    }
247
0
    SwGlossaries* pGlossaries = ::GetGlossaries();
248
0
    const std::vector<OUString> & rPathArr = pGlossaries->GetPathArray();
249
0
    const OUString sExt( SwGlossaries::GetExtension() );
250
0
    if(!m_bFilled)
251
0
    {
252
0
        const size_t nGroupCount = pGlossaries->GetGroupCnt();
253
0
        for(size_t i = 0; i < nGroupCount; ++i)
254
0
        {
255
0
            OUString sGrpName = pGlossaries->GetGroupName(i);
256
0
            const size_t nPath = static_cast<size_t>(
257
0
                o3tl::toInt32(o3tl::getToken(sGrpName, 1, GLOS_DELIM)));
258
0
            if( nPath < rPathArr.size() )
259
0
            {
260
0
                std::unique_ptr<AutoTextGroup> pGroup(new AutoTextGroup);
261
0
                pGroup->sName = sGrpName;
262
263
0
                FillGroup(pGroup.get(), pGlossaries);
264
0
                OUString sName = rPathArr[nPath] + "/" +
265
0
                    o3tl::getToken(pGroup->sName, 0, GLOS_DELIM) + sExt;
266
0
                FStatHelper::GetModifiedDateTimeOfFile( sName,
267
0
                                                &pGroup->aDateModified,
268
0
                                                &pGroup->aDateModified );
269
270
0
                m_aGroupArr.insert( m_aGroupArr.begin(), std::move(pGroup) );
271
0
            }
272
0
        }
273
0
        m_bFilled = true;
274
0
    }
275
0
    else
276
0
    {
277
0
        for( size_t nPath = 0; nPath < rPathArr.size(); nPath++ )
278
0
        {
279
0
            std::vector<OUString> aFoundGroupNames;
280
0
            std::vector<OUString> aFiles;
281
0
            std::vector<DateTime> aDateTimeArr;
282
283
0
            SWUnoHelper::UCB_GetFileListOfFolder( rPathArr[nPath], aFiles,
284
0
                                                    &sExt, &aDateTimeArr );
285
0
            for( size_t nFiles = 0; nFiles < aFiles.size(); ++nFiles )
286
0
            {
287
0
                const OUString& aTitle = aFiles[ nFiles ];
288
0
                ::DateTime& rDT = aDateTimeArr[ nFiles ];
289
290
0
                OUString sName( aTitle.copy( 0, aTitle.getLength() - sExt.getLength() ));
291
292
0
                aFoundGroupNames.push_back(sName);
293
0
                sName += OUStringChar(GLOS_DELIM) + OUString::number( o3tl::narrowing<sal_uInt16>(nPath) );
294
0
                AutoTextGroup* pFound = FindGroup( sName );
295
0
                if( !pFound )
296
0
                {
297
0
                    pFound = new AutoTextGroup;
298
0
                    pFound->sName = sName;
299
0
                    FillGroup( pFound, pGlossaries );
300
0
                    pFound->aDateModified = rDT;
301
302
0
                    m_aGroupArr.push_back(std::unique_ptr<AutoTextGroup>(pFound));
303
0
                }
304
0
                else if( pFound->aDateModified != rDT )
305
0
                {
306
0
                    FillGroup(pFound, pGlossaries);
307
0
                    pFound->aDateModified = rDT;
308
0
                }
309
0
            }
310
311
0
            for( size_t i = m_aGroupArr.size(); i>0; )
312
0
            {
313
0
                --i;
314
                // maybe remove deleted groups
315
0
                AutoTextGroup* pGroup = m_aGroupArr[i].get();
316
0
                const size_t nGroupPath = static_cast<size_t>(
317
0
                    o3tl::toInt32(o3tl::getToken(pGroup->sName, 1, GLOS_DELIM)));
318
                // Only the groups will be checked which are registered
319
                // for the current subpath.
320
0
                if( nGroupPath == nPath )
321
0
                {
322
0
                    std::u16string_view sCompareGroup = o3tl::getToken(pGroup->sName, 0, GLOS_DELIM);
323
0
                    bool bFound = std::any_of(aFoundGroupNames.begin(), aFoundGroupNames.end(),
324
0
                        [&sCompareGroup](const OUString& rGroupName) { return sCompareGroup == rGroupName; });
325
326
0
                    if(!bFound)
327
0
                    {
328
0
                        m_aGroupArr.erase(m_aGroupArr.begin() + i);
329
0
                    }
330
0
                }
331
0
            }
332
0
        }
333
0
    }
334
0
}
335
336
void SwGlossaryList::Invoke()
337
0
{
338
    // Only update automatically if a SwView has the focus.
339
0
    if(::GetActiveView())
340
0
        Update();
341
0
}
342
343
AutoTextGroup* SwGlossaryList::FindGroup(std::u16string_view rGroupName)
344
0
{
345
0
    for(const auto & pRet : m_aGroupArr)
346
0
    {
347
0
        if(pRet->sName == rGroupName)
348
0
            return pRet.get();
349
0
    }
350
0
    return nullptr;
351
0
}
352
353
void SwGlossaryList::FillGroup(AutoTextGroup* pGroup, SwGlossaries* pGlossaries)
354
0
{
355
0
    std::unique_ptr<SwTextBlocks> pBlock = pGlossaries->GetGroupDoc(pGroup->sName);
356
0
    pGroup->nCount = pBlock ? pBlock->GetCount() : 0;
357
0
    pGroup->sLongNames.clear();
358
0
    pGroup->sShortNames.clear();
359
0
    if(pBlock)
360
0
        pGroup->sTitle = pBlock->GetName();
361
362
0
    for(sal_uInt16 j = 0; j < pGroup->nCount; j++)
363
0
    {
364
0
        pGroup->sLongNames += pBlock->GetLongName(j)
365
0
            + OUStringChar(STRING_DELIM);
366
0
        pGroup->sShortNames += pBlock->GetShortName(j)
367
0
            + OUStringChar(STRING_DELIM);
368
0
    }
369
0
}
370
371
// Give back all (not exceeding FIND_MAX_GLOS) found modules
372
// with matching beginning.
373
374
void SwGlossaryList::HasLongName(const std::vector<OUString>& rBeginCandidates,
375
                                 std::vector<std::pair<OUString, sal_uInt16>>& rLongNames)
376
0
{
377
0
    if(!m_bFilled)
378
0
        Update();
379
0
    const ::utl::TransliterationWrapper& rSCmp = GetAppCmpStrIgnore();
380
    // We store results for all candidate words in separate lists, so that later
381
    // we can sort them according to the candidate position
382
0
    std::vector<std::vector<OUString>> aResults(rBeginCandidates.size());
383
384
    // We can't break after FIND_MAX_GLOS items found, since first items may have ended up in
385
    // lower-priority lists, and those from higher-priority lists are yet to come. So process all.
386
0
    for (const auto& pGroup : m_aGroupArr)
387
0
    {
388
0
        sal_Int32 nIdx{ 0 };
389
0
        for(sal_uInt16 j = 0; j < pGroup->nCount; j++)
390
0
        {
391
0
            OUString sBlock = pGroup->sLongNames.getToken(0, STRING_DELIM, nIdx);
392
0
            for (size_t i = 0; i < rBeginCandidates.size(); ++i)
393
0
            {
394
0
                const OUString& s = rBeginCandidates[i];
395
0
                if (s.getLength() + 1 < sBlock.getLength()
396
0
                    && rSCmp.isEqual(sBlock.copy(0, s.getLength()), s))
397
0
                {
398
0
                    aResults[i].push_back(sBlock);
399
0
                }
400
0
            }
401
0
        }
402
0
    }
403
404
0
    std::vector<std::pair<OUString, sal_uInt16>> aAllResults;
405
    // Sort and concatenate all result lists. See QuickHelpData::SortAndFilter
406
0
    for (size_t i = 0; i < rBeginCandidates.size(); ++i)
407
0
    {
408
0
        std::sort(aResults[i].begin(), aResults[i].end(),
409
0
                  [origWord = rBeginCandidates[i]](const OUString& s1, const OUString& s2) {
410
0
                      int nRet = s1.compareToIgnoreAsciiCase(s2);
411
0
                      if (nRet == 0)
412
0
                      {
413
                          // fdo#61251 sort stuff that starts with the exact rOrigWord before
414
                          // another ignore-case candidate
415
0
                          int n1StartsWithOrig = s1.startsWith(origWord) ? 0 : 1;
416
0
                          int n2StartsWithOrig = s2.startsWith(origWord) ? 0 : 1;
417
0
                          return n1StartsWithOrig < n2StartsWithOrig;
418
0
                      }
419
0
                      return nRet < 0;
420
0
                  });
421
        // All suggestions must be accompanied with length of the text they would replace
422
0
        std::transform(aResults[i].begin(), aResults[i].end(), std::back_inserter(aAllResults),
423
0
                       [nLen = sal_uInt16(rBeginCandidates[i].getLength())](const OUString& s) {
424
0
                           return std::make_pair(s, nLen);
425
0
                       });
426
0
    }
427
428
0
    const auto it = std::unique(
429
0
        aAllResults.begin(), aAllResults.end(),
430
0
        [](const std::pair<OUString, sal_uInt16>& s1, const std::pair<OUString, sal_uInt16>& s2) {
431
0
            return s1.first.equalsIgnoreAsciiCase(s2.first);
432
0
        });
433
0
    if (const auto nCount = std::min<size_t>(std::distance(aAllResults.begin(), it), FIND_MAX_GLOS))
434
0
    {
435
0
        rLongNames.insert(rLongNames.end(), aAllResults.begin(), aAllResults.begin() + nCount);
436
0
    }
437
0
}
438
439
void    SwGlossaryList::ClearGroups()
440
0
{
441
0
    m_aGroupArr.clear();
442
0
    m_bFilled = false;
443
0
}
444
445
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */