1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
3 * This file is part of the LibreOffice project.
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/.
9 * This file incorporates work covered by the following license notice:
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 .
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>
30 #include <glosdoc.hxx>
31 #include <gloslst.hxx>
32 #include <swunohelper.hxx>
37 #define STRING_DELIM char(0x0A)
38 #define GLOS_TIMEOUT 30000 // update every 30 seconds
39 #define FIND_MAX_GLOS 20
50 class SwGlossDecideDlg
: public weld::GenericDialogController
52 std::unique_ptr
<weld::Button
> m_xOk
;
53 std::unique_ptr
<weld::TreeView
> m_xListLB
;
55 DECL_LINK(DoubleClickHdl
, weld::TreeView
&, bool);
56 DECL_LINK(SelectHdl
, weld::TreeView
&, void);
59 explicit SwGlossDecideDlg(weld::Window
* pParent
);
61 weld::TreeView
& GetTreeView() {return *m_xListLB
;}
66 SwGlossDecideDlg::SwGlossDecideDlg(weld::Window
* pParent
)
67 : GenericDialogController(pParent
, "modules/swriter/ui/selectautotextdialog.ui", "SelectAutoTextDialog")
68 , m_xOk(m_xBuilder
->weld_button("ok"))
69 , m_xListLB(m_xBuilder
->weld_tree_view("treeview"))
71 m_xListLB
->set_size_request(m_xListLB
->get_approximate_digit_width() * 32,
72 m_xListLB
->get_height_rows(8));
73 m_xListLB
->connect_row_activated(LINK(this, SwGlossDecideDlg
, DoubleClickHdl
));
74 m_xListLB
->connect_changed(LINK(this, SwGlossDecideDlg
, SelectHdl
));
77 IMPL_LINK_NOARG(SwGlossDecideDlg
, DoubleClickHdl
, weld::TreeView
&, bool)
79 m_xDialog
->response(RET_OK
);
83 IMPL_LINK_NOARG(SwGlossDecideDlg
, SelectHdl
, weld::TreeView
&, void)
85 m_xOk
->set_sensitive(m_xListLB
->get_selected_index() != -1);
88 SwGlossaryList::SwGlossaryList() :
89 AutoTimer("SwGlossaryList"), m_bFilled(false)
91 SvtPathOptions aPathOpt
;
92 m_sPath
= aPathOpt
.GetAutoTextPath();
93 SetTimeout(GLOS_TIMEOUT
);
96 SwGlossaryList::~SwGlossaryList()
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.
105 bool SwGlossaryList::GetShortName(std::u16string_view rLongName
,
106 OUString
& rShortName
, OUString
& rGroupName
)
111 std::vector
<TripleString
> aTripleStrings
;
113 size_t nCount
= m_aGroupArr
.size();
114 for(size_t i
= 0; i
< nCount
; i
++ )
116 AutoTextGroup
* pGroup
= m_aGroupArr
[i
].get();
117 if(!rGroupName
.isEmpty() && rGroupName
!= pGroup
->sName
)
120 sal_Int32 nPosLong
= 0;
121 for(sal_uInt16 j
= 0; j
< pGroup
->nCount
; j
++)
123 const OUString sLong
= pGroup
->sLongNames
.getToken(0, STRING_DELIM
, nPosLong
);
124 if(rLongName
!= sLong
)
127 TripleString aTriple
;
128 aTriple
.sGroup
= pGroup
->sName
;
129 aTriple
.sBlock
= sLong
;
130 aTriple
.sShort
= pGroup
->sShortNames
.getToken(j
, STRING_DELIM
);
131 aTripleStrings
.push_back(aTriple
);
136 nCount
= aTripleStrings
.size();
139 const TripleString
& rTriple(aTripleStrings
.front());
140 rShortName
= rTriple
.sShort
;
141 rGroupName
= rTriple
.sGroup
;
146 SwView
*pView
= ::GetActiveView();
149 SwGlossDecideDlg
aDlg(pView
->GetFrameWeld());
150 OUString sTitle
= aDlg
.get_title() + " " + aTripleStrings
.front().sBlock
;
151 aDlg
.set_title(sTitle
);
153 weld::TreeView
& rLB
= aDlg
.GetTreeView();
154 for (const auto& rTriple
: aTripleStrings
)
155 rLB
.append_text(rTriple
.sGroup
.getToken(0, GLOS_DELIM
));
158 if (aDlg
.run() == RET_OK
&& rLB
.get_selected_index() != -1)
160 const TripleString
& rTriple(aTripleStrings
[rLB
.get_selected_index()]);
161 rShortName
= rTriple
.sShort
;
162 rGroupName
= rTriple
.sGroup
;
171 size_t SwGlossaryList::GetGroupCount()
175 return m_aGroupArr
.size();
178 OUString
SwGlossaryList::GetGroupName(size_t nPos
)
180 OSL_ENSURE(m_aGroupArr
.size() > nPos
, "group not available");
181 if(nPos
< m_aGroupArr
.size())
183 AutoTextGroup
* pGroup
= m_aGroupArr
[nPos
].get();
184 OUString sRet
= pGroup
->sName
;
190 OUString
SwGlossaryList::GetGroupTitle(size_t nPos
)
192 OSL_ENSURE(m_aGroupArr
.size() > nPos
, "group not available");
193 if(nPos
< m_aGroupArr
.size())
195 AutoTextGroup
* pGroup
= m_aGroupArr
[nPos
].get();
196 return pGroup
->sTitle
;
201 sal_uInt16
SwGlossaryList::GetBlockCount(size_t nGroup
)
203 OSL_ENSURE(m_aGroupArr
.size() > nGroup
, "group not available");
204 if(nGroup
< m_aGroupArr
.size())
206 AutoTextGroup
* pGroup
= m_aGroupArr
[nGroup
].get();
207 return pGroup
->nCount
;
212 OUString
SwGlossaryList::GetBlockLongName(size_t nGroup
, sal_uInt16 nBlock
)
214 OSL_ENSURE(m_aGroupArr
.size() > nGroup
, "group not available");
215 if(nGroup
< m_aGroupArr
.size())
217 AutoTextGroup
* pGroup
= m_aGroupArr
[nGroup
].get();
218 return pGroup
->sLongNames
.getToken(nBlock
, STRING_DELIM
);
223 OUString
SwGlossaryList::GetBlockShortName(size_t nGroup
, sal_uInt16 nBlock
)
225 OSL_ENSURE(m_aGroupArr
.size() > nGroup
, "group not available");
226 if(nGroup
< m_aGroupArr
.size())
228 AutoTextGroup
* pGroup
= m_aGroupArr
[nGroup
].get();
229 return pGroup
->sShortNames
.getToken(nBlock
, STRING_DELIM
);
234 void SwGlossaryList::Update()
239 SvtPathOptions aPathOpt
;
240 const OUString
& sTemp( aPathOpt
.GetAutoTextPath() );
247 SwGlossaries
* pGlossaries
= ::GetGlossaries();
248 const std::vector
<OUString
> & rPathArr
= pGlossaries
->GetPathArray();
249 const OUString
sExt( SwGlossaries::GetExtension() );
252 const size_t nGroupCount
= pGlossaries
->GetGroupCnt();
253 for(size_t i
= 0; i
< nGroupCount
; ++i
)
255 OUString sGrpName
= pGlossaries
->GetGroupName(i
);
256 const size_t nPath
= static_cast<size_t>(
257 o3tl::toInt32(o3tl::getToken(sGrpName
, 1, GLOS_DELIM
)));
258 if( nPath
< rPathArr
.size() )
260 std::unique_ptr
<AutoTextGroup
> pGroup(new AutoTextGroup
);
261 pGroup
->sName
= sGrpName
;
263 FillGroup(pGroup
.get(), pGlossaries
);
264 OUString sName
= rPathArr
[nPath
] + "/" +
265 o3tl::getToken(pGroup
->sName
, 0, GLOS_DELIM
) + sExt
;
266 FStatHelper::GetModifiedDateTimeOfFile( sName
,
267 &pGroup
->aDateModified
,
268 &pGroup
->aDateModified
);
270 m_aGroupArr
.insert( m_aGroupArr
.begin(), std::move(pGroup
) );
277 for( size_t nPath
= 0; nPath
< rPathArr
.size(); nPath
++ )
279 std::vector
<OUString
> aFoundGroupNames
;
280 std::vector
<OUString
> aFiles
;
281 std::vector
<DateTime
> aDateTimeArr
;
283 SWUnoHelper::UCB_GetFileListOfFolder( rPathArr
[nPath
], aFiles
,
284 &sExt
, &aDateTimeArr
);
285 for( size_t nFiles
= 0; nFiles
< aFiles
.size(); ++nFiles
)
287 const OUString aTitle
= aFiles
[ nFiles
];
288 ::DateTime
& rDT
= aDateTimeArr
[ nFiles
];
290 OUString
sName( aTitle
.copy( 0, aTitle
.getLength() - sExt
.getLength() ));
292 aFoundGroupNames
.push_back(sName
);
293 sName
+= OUStringChar(GLOS_DELIM
) + OUString::number( o3tl::narrowing
<sal_uInt16
>(nPath
) );
294 AutoTextGroup
* pFound
= FindGroup( sName
);
297 pFound
= new AutoTextGroup
;
298 pFound
->sName
= sName
;
299 FillGroup( pFound
, pGlossaries
);
300 pFound
->aDateModified
= rDT
;
302 m_aGroupArr
.push_back(std::unique_ptr
<AutoTextGroup
>(pFound
));
304 else if( pFound
->aDateModified
!= rDT
)
306 FillGroup(pFound
, pGlossaries
);
307 pFound
->aDateModified
= rDT
;
311 for( size_t i
= m_aGroupArr
.size(); i
>0; )
314 // maybe remove deleted groups
315 AutoTextGroup
* pGroup
= m_aGroupArr
[i
].get();
316 const size_t nGroupPath
= static_cast<size_t>(
317 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 if( nGroupPath
== nPath
)
322 std::u16string_view sCompareGroup
= o3tl::getToken(pGroup
->sName
, 0, GLOS_DELIM
);
323 bool bFound
= std::any_of(aFoundGroupNames
.begin(), aFoundGroupNames
.end(),
324 [&sCompareGroup
](const OUString
& rGroupName
) { return sCompareGroup
== rGroupName
; });
328 m_aGroupArr
.erase(m_aGroupArr
.begin() + i
);
336 void SwGlossaryList::Invoke()
338 // Only update automatically if a SwView has the focus.
339 if(::GetActiveView())
343 AutoTextGroup
* SwGlossaryList::FindGroup(std::u16string_view rGroupName
)
345 for(const auto & pRet
: m_aGroupArr
)
347 if(pRet
->sName
== rGroupName
)
353 void SwGlossaryList::FillGroup(AutoTextGroup
* pGroup
, SwGlossaries
* pGlossaries
)
355 std::unique_ptr
<SwTextBlocks
> pBlock
= pGlossaries
->GetGroupDoc(pGroup
->sName
);
356 pGroup
->nCount
= pBlock
? pBlock
->GetCount() : 0;
357 pGroup
->sLongNames
.clear();
358 pGroup
->sShortNames
.clear();
360 pGroup
->sTitle
= pBlock
->GetName();
362 for(sal_uInt16 j
= 0; j
< pGroup
->nCount
; j
++)
364 pGroup
->sLongNames
+= pBlock
->GetLongName(j
)
365 + OUStringChar(STRING_DELIM
);
366 pGroup
->sShortNames
+= pBlock
->GetShortName(j
)
367 + OUStringChar(STRING_DELIM
);
371 // Give back all (not exceeding FIND_MAX_GLOS) found modules
372 // with matching beginning.
374 void SwGlossaryList::HasLongName(const std::vector
<OUString
>& rBeginCandidates
,
375 std::vector
<std::pair
<OUString
, sal_uInt16
>>& rLongNames
)
379 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 std::vector
<std::vector
<OUString
>> aResults(rBeginCandidates
.size());
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 for (const auto& pGroup
: m_aGroupArr
)
389 for(sal_uInt16 j
= 0; j
< pGroup
->nCount
; j
++)
391 OUString sBlock
= pGroup
->sLongNames
.getToken(0, STRING_DELIM
, nIdx
);
392 for (size_t i
= 0; i
< rBeginCandidates
.size(); ++i
)
394 const OUString
& s
= rBeginCandidates
[i
];
395 if (s
.getLength() + 1 < sBlock
.getLength()
396 && rSCmp
.isEqual(sBlock
.copy(0, s
.getLength()), s
))
398 aResults
[i
].push_back(sBlock
);
404 std::vector
<std::pair
<OUString
, sal_uInt16
>> aAllResults
;
405 // Sort and concatenate all result lists. See QuickHelpData::SortAndFilter
406 for (size_t i
= 0; i
< rBeginCandidates
.size(); ++i
)
408 std::sort(aResults
[i
].begin(), aResults
[i
].end(),
409 [origWord
= rBeginCandidates
[i
]](const OUString
& s1
, const OUString
& s2
) {
410 int nRet
= s1
.compareToIgnoreAsciiCase(s2
);
413 // fdo#61251 sort stuff that starts with the exact rOrigWord before
414 // another ignore-case candidate
415 int n1StartsWithOrig
= s1
.startsWith(origWord
) ? 0 : 1;
416 int n2StartsWithOrig
= s2
.startsWith(origWord
) ? 0 : 1;
417 return n1StartsWithOrig
< n2StartsWithOrig
;
421 // All suggestions must be accompanied with length of the text they would replace
422 std::transform(aResults
[i
].begin(), aResults
[i
].end(), std::back_inserter(aAllResults
),
423 [nLen
= sal_uInt16(rBeginCandidates
[i
].getLength())](const OUString
& s
) {
424 return std::make_pair(s
, nLen
);
428 const auto& it
= std::unique(
429 aAllResults
.begin(), aAllResults
.end(),
430 [](const std::pair
<OUString
, sal_uInt16
>& s1
, const std::pair
<OUString
, sal_uInt16
>& s2
) {
431 return s1
.first
.equalsIgnoreAsciiCase(s2
.first
);
433 if (const auto nCount
= std::min
<size_t>(std::distance(aAllResults
.begin(), it
), FIND_MAX_GLOS
))
435 rLongNames
.insert(rLongNames
.end(), aAllResults
.begin(), aAllResults
.begin() + nCount
);
439 void SwGlossaryList::ClearGroups()
445 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */