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 <tools/urlobj.hxx>
21 #include <tools/stream.hxx>
22 #include <vcl/help.hxx>
23 #include <vcl/svapp.hxx>
24 #include <vcl/weld.hxx>
25 #include <unotools/transliterationwrapper.hxx>
26 #include <unotools/tempfile.hxx>
27 #include <unotools/pathoptions.hxx>
28 #include <osl/diagnose.h>
29 
30 #include <swtypes.hxx>
31 #include <glosbib.hxx>
32 #include <gloshdl.hxx>
33 #include <actctrl.hxx>
34 #include <glossary.hxx>
35 #include <glosdoc.hxx>
36 #include <swunohelper.hxx>
37 
38 #include <strings.hrc>
39 
40 #define PATH_CASE_SENSITIVE 0x01
41 #define PATH_READONLY       0x02
42 
43 #define RENAME_TOKEN_DELIM      u'\x0001'
44 
SwGlossaryGroupDlg(weld::Window * pParent,std::vector<OUString> const & rPathArr,SwGlossaryHdl * pHdl)45 SwGlossaryGroupDlg::SwGlossaryGroupDlg(weld::Window * pParent,
46     std::vector<OUString> const& rPathArr, SwGlossaryHdl *pHdl)
47     : SfxDialogController(pParent, "modules/swriter/ui/editcategories.ui",
48                           "EditCategoriesDialog")
49     , m_pParent(pParent)
50     , pGlosHdl(pHdl)
51     , m_xNameED(m_xBuilder->weld_entry("name"))
52     , m_xPathLB(m_xBuilder->weld_combo_box("pathlb"))
53     , m_xGroupTLB(m_xBuilder->weld_tree_view("group"))
54     , m_xNewPB(m_xBuilder->weld_button("new"))
55     , m_xDelPB(m_xBuilder->weld_button("delete"))
56     , m_xRenamePB(m_xBuilder->weld_button("rename"))
57 {
58     int nWidth = m_xGroupTLB->get_approximate_digit_width() * 34;
59     m_xPathLB->set_size_request(nWidth, -1);
60     //just has to be something small, real size will be available space
61     m_xGroupTLB->set_size_request(nWidth, m_xGroupTLB->get_height_rows(10));
62 
63     std::vector<int> aWidths;
64     aWidths.push_back(nWidth);
65     m_xGroupTLB->set_column_fixed_widths(aWidths);
66     m_xGroupTLB->connect_changed(LINK(this, SwGlossaryGroupDlg, SelectHdl));
67 
68     m_xNewPB->connect_clicked(LINK(this, SwGlossaryGroupDlg, NewHdl));
69     m_xDelPB->connect_clicked(LINK(this, SwGlossaryGroupDlg, DeleteHdl));
70     m_xNameED->connect_changed(LINK(this, SwGlossaryGroupDlg, ModifyHdl));
71     m_xNameED->connect_insert_text(LINK(this, SwGlossaryGroupDlg, EditInsertTextHdl));
72     m_xPathLB->connect_changed(LINK(this, SwGlossaryGroupDlg, ModifyListBoxHdl));
73     m_xRenamePB->connect_clicked(LINK(this, SwGlossaryGroupDlg, RenameHdl));
74 
75     m_xNameED->connect_size_allocate(LINK(this, SwGlossaryGroupDlg, EntrySizeAllocHdl));
76     m_xPathLB->connect_size_allocate(LINK(this, SwGlossaryGroupDlg, EntrySizeAllocHdl));
77 
78     for (size_t i = 0; i < rPathArr.size(); ++i)
79     {
80         INetURLObject aTempURL(rPathArr[i]);
81         const OUString sPath = aTempURL.GetMainURL(INetURLObject::DecodeMechanism::WithCharset );
82         sal_uInt32 nCaseReadonly = 0;
83         utl::TempFile aTempFile(&sPath);
84         aTempFile.EnableKillingFile();
85         if(!aTempFile.IsValid())
86             nCaseReadonly |= PATH_READONLY;
87         else if( SWUnoHelper::UCB_IsCaseSensitiveFileName( aTempFile.GetURL()))
88             nCaseReadonly |= PATH_CASE_SENSITIVE;
89         m_xPathLB->append(OUString::number(nCaseReadonly), sPath);
90     }
91     m_xPathLB->set_active(0);
92     m_xPathLB->set_sensitive(true);
93 
94     const size_t nCount = pHdl->GetGroupCnt();
95     /* tdf#111870 "My AutoText" comes from mytexts.bau but should be translated
96        here as well, see also SwGlossaryDlg::Init */
97     const OUString sMyAutoTextEnglish("My AutoText");
98     for( size_t i = 0; i < nCount; ++i)
99     {
100         OUString sTitle;
101         OUString sGroup = pHdl->GetGroupName(i, &sTitle);
102         if(sGroup.isEmpty())
103             continue;
104         GlosBibUserData* pData = new GlosBibUserData;
105         pData->sGroupName = sGroup;
106         if ( sTitle == sMyAutoTextEnglish )
107             pData->sGroupTitle = SwResId(STR_MY_AUTOTEXT);
108         else
109             pData->sGroupTitle = sTitle;
110         pData->sPath = m_xPathLB->get_text(sGroup.getToken(1, GLOS_DELIM).toInt32());
111         const OUString sId(OUString::number(reinterpret_cast<sal_Int64>(pData)));
112         m_xGroupTLB->append(sId, pData->sGroupTitle);
113         int nEntry = m_xGroupTLB->find_id(sId);
114         m_xGroupTLB->set_text(nEntry, pData->sPath, 1);
115 
116     }
117     m_xGroupTLB->make_sorted();
118 }
119 
~SwGlossaryGroupDlg()120 SwGlossaryGroupDlg::~SwGlossaryGroupDlg()
121 {
122     int nCount = m_xGroupTLB->n_children();
123     for (int i = 0; i < nCount; ++i)
124     {
125         GlosBibUserData* pUserData = reinterpret_cast<GlosBibUserData*>(m_xGroupTLB->get_id(i).toInt64());
126         delete pUserData;
127     }
128 }
129 
run()130 short SwGlossaryGroupDlg::run()
131 {
132     short nRet = SfxDialogController::run();
133     if (nRet == RET_OK)
134         Apply();
135     return nRet;
136 }
137 
Apply()138 void SwGlossaryGroupDlg::Apply()
139 {
140     if (m_xNewPB->get_sensitive())
141         NewHdl(*m_xNewPB);
142 
143     const OUString aActGroup = SwGlossaryDlg::GetCurrGroup();
144 
145     for (const auto& removedStr : m_RemovedArr)
146     {
147         sal_Int32 nIdx{ 0 };
148         const OUString sDelGroup = removedStr.getToken(0, '\t', nIdx);
149         if( sDelGroup == aActGroup )
150         {
151             //when the current group is deleted, the current group has to be relocated
152             if (m_xGroupTLB->n_children())
153             {
154                 GlosBibUserData* pUserData = reinterpret_cast<GlosBibUserData*>(m_xGroupTLB->get_id(0).toInt64());
155                 pGlosHdl->SetCurGroup(pUserData->sGroupName);
156             }
157         }
158         const OUString sMsg(SwResId(STR_QUERY_DELETE_GROUP1)
159                             + removedStr.getToken(0, '\t', nIdx)
160                             + SwResId(STR_QUERY_DELETE_GROUP2));
161 
162         std::unique_ptr<weld::MessageDialog> xQueryBox(Application::CreateMessageDialog(m_pParent,
163                                                        VclMessageType::Question, VclButtonsType::YesNo, sMsg));
164         xQueryBox->set_default_response(RET_NO);
165         if (RET_YES == xQueryBox->run())
166             pGlosHdl->DelGroup( sDelGroup );
167     }
168 
169     //don't rename before there was one
170     for (auto it(m_RenamedArr.cbegin()); it != m_RenamedArr.cend(); ++it)
171     {
172         sal_Int32 nIdx{ 0 };
173         OUString const sOld(it->getToken(0, RENAME_TOKEN_DELIM, nIdx));
174         OUString sNew(it->getToken(0, RENAME_TOKEN_DELIM, nIdx));
175         OUString const sTitle(it->getToken(0, RENAME_TOKEN_DELIM, nIdx));
176         pGlosHdl->RenameGroup(sOld, sNew, sTitle);
177         if (it == m_RenamedArr.begin())
178         {
179             sCreatedGroup = sNew;
180         }
181     }
182     for (auto& sNewGroup : m_InsertedArr)
183     {
184         OUString sNewTitle = sNewGroup.getToken(0, GLOS_DELIM);
185         if( sNewGroup != aActGroup )
186         {
187             pGlosHdl->NewGroup(sNewGroup, sNewTitle);
188             if(sCreatedGroup.isEmpty())
189                 sCreatedGroup = sNewGroup;
190         }
191     }
192 }
193 
IMPL_LINK_NOARG(SwGlossaryGroupDlg,SelectHdl,weld::TreeView &,void)194 IMPL_LINK_NOARG( SwGlossaryGroupDlg, SelectHdl, weld::TreeView&, void )
195 {
196     m_xNewPB->set_sensitive(false);
197     int nFirstEntry = m_xGroupTLB->get_selected_index();
198     if (nFirstEntry != -1)
199     {
200         GlosBibUserData* pUserData = reinterpret_cast<GlosBibUserData*>(m_xGroupTLB->get_id(nFirstEntry).toInt64());
201         const OUString sEntry(pUserData->sGroupName);
202         const OUString sName(m_xNameED->get_text());
203         bool bExists = false;
204         int nPos = m_xGroupTLB->find_text(sName);
205         if (nPos != -1)
206         {
207             GlosBibUserData* pFoundData = reinterpret_cast<GlosBibUserData*>(m_xGroupTLB->get_id(nPos).toInt64());
208             fprintf(stderr, "comparing %s and %s\n",
209                     OUStringToOString(pFoundData->sGroupName, RTL_TEXTENCODING_UTF8).getStr(),
210                     OUStringToOString(sEntry, RTL_TEXTENCODING_UTF8).getStr());
211             bExists = pFoundData->sGroupName == sEntry;
212         }
213 
214         m_xRenamePB->set_sensitive(!bExists && !sName.isEmpty());
215         fprintf(stderr, "one rename %d\n", !bExists && !sName.isEmpty());
216         m_xDelPB->set_sensitive(IsDeleteAllowed(sEntry));
217     }
218 }
219 
IMPL_LINK_NOARG(SwGlossaryGroupDlg,NewHdl,weld::Button &,void)220 IMPL_LINK_NOARG(SwGlossaryGroupDlg, NewHdl, weld::Button&, void)
221 {
222     OUString sGroup = m_xNameED->get_text()
223         + OUStringChar(GLOS_DELIM)
224         + OUString::number(m_xPathLB->get_active());
225     OSL_ENSURE(!pGlosHdl->FindGroupName(sGroup), "group already available!");
226     m_InsertedArr.push_back(sGroup);
227     GlosBibUserData* pData = new GlosBibUserData;
228     pData->sPath = m_xPathLB->get_active_text();
229     pData->sGroupName = sGroup;
230     pData->sGroupTitle = m_xNameED->get_text();
231     OUString sId(OUString::number(reinterpret_cast<sal_Int64>(pData)));
232     m_xGroupTLB->append(sId, m_xNameED->get_text());
233     int nEntry = m_xGroupTLB->find_id(sId);
234     m_xGroupTLB->set_text(nEntry, pData->sPath, 1);
235     m_xGroupTLB->select(nEntry);
236     SelectHdl(*m_xGroupTLB);
237     m_xGroupTLB->scroll_to_row(nEntry);
238 }
239 
IMPL_LINK_NOARG(SwGlossaryGroupDlg,EntrySizeAllocHdl,const Size &,void)240 IMPL_LINK_NOARG(SwGlossaryGroupDlg, EntrySizeAllocHdl, const Size&, void)
241 {
242     std::vector<int> aWidths;
243     int x, y, width, height;
244     if (m_xPathLB->get_extents_relative_to(*m_xGroupTLB, x, y, width, height))
245     {
246         aWidths.push_back(x);
247         m_xGroupTLB->set_column_fixed_widths(aWidths);
248     }
249 }
250 
IMPL_LINK(SwGlossaryGroupDlg,DeleteHdl,weld::Button &,rButton,void)251 IMPL_LINK( SwGlossaryGroupDlg, DeleteHdl, weld::Button&, rButton, void )
252 {
253     int nEntry = m_xGroupTLB->get_selected_index();
254     if (nEntry == -1)
255     {
256         rButton.set_sensitive(false);
257         return;
258     }
259     GlosBibUserData* pUserData = reinterpret_cast<GlosBibUserData*>(m_xGroupTLB->get_id(nEntry).toInt64());
260     OUString const sEntry(pUserData->sGroupName);
261     // if the name to be deleted is among the new ones - get rid of it
262     bool bDelete = true;
263     auto it = std::find(m_InsertedArr.begin(), m_InsertedArr.end(), sEntry);
264     if (it != m_InsertedArr.end())
265     {
266         m_InsertedArr.erase(it);
267         bDelete = false;
268     }
269     // it should probably be renamed?
270     if(bDelete)
271     {
272         it = std::find_if(m_RenamedArr.begin(), m_RenamedArr.end(),
273             [&sEntry](OUString& s) { return s.getToken(0, RENAME_TOKEN_DELIM) == sEntry; });
274         if (it != m_RenamedArr.end())
275         {
276             m_RenamedArr.erase(it);
277             bDelete = false;
278         }
279     }
280     if(bDelete)
281     {
282         m_RemovedArr.emplace_back(pUserData->sGroupName + "\t" + pUserData->sGroupTitle);
283     }
284     delete pUserData;
285     m_xGroupTLB->remove(nEntry);
286     if (!m_xGroupTLB->n_children())
287         rButton.set_sensitive(false);
288     //the content must be deleted - otherwise the new handler would be called in Apply()
289     m_xNameED->set_text(OUString());
290     ModifyHdl(*m_xNameED);
291 }
292 
IMPL_LINK_NOARG(SwGlossaryGroupDlg,RenameHdl,weld::Button &,void)293 IMPL_LINK_NOARG(SwGlossaryGroupDlg, RenameHdl, weld::Button&, void)
294 {
295     int nEntry = m_xGroupTLB->get_selected_index();
296     GlosBibUserData* pUserData = reinterpret_cast<GlosBibUserData*>(m_xGroupTLB->get_id(nEntry).toInt64());
297     OUString sEntry(pUserData->sGroupName);
298 
299     const OUString sNewTitle(m_xNameED->get_text());
300     OUString sNewName = sNewTitle
301         + OUStringChar(GLOS_DELIM)
302         + OUString::number(m_xPathLB->get_active());
303     OSL_ENSURE(!pGlosHdl->FindGroupName(sNewName), "group already available!");
304 
305     // if the name to be renamed is among the new ones - replace
306     bool bDone = false;
307     auto it = std::find(m_InsertedArr.begin(), m_InsertedArr.end(), sEntry);
308     if (it != m_InsertedArr.end())
309     {
310         m_InsertedArr.erase(it);
311         m_InsertedArr.push_back(sNewName);
312         bDone = true;
313     }
314     if(!bDone)
315     {
316         sEntry += OUStringChar(RENAME_TOKEN_DELIM) + sNewName
317                 + OUStringChar(RENAME_TOKEN_DELIM) + sNewTitle;
318         m_RenamedArr.push_back(sEntry);
319     }
320     delete pUserData;
321     m_xGroupTLB->remove(nEntry);
322 
323     GlosBibUserData* pData = new GlosBibUserData;
324     pData->sPath = m_xPathLB->get_active_text();
325     pData->sGroupName = sNewName;
326     pData->sGroupTitle = sNewTitle;
327 
328     OUString sId(OUString::number(reinterpret_cast<sal_Int64>(pData)));
329     m_xGroupTLB->append(sId, m_xNameED->get_text());
330     nEntry = m_xGroupTLB->find_id(sId);
331     m_xGroupTLB->set_text(nEntry, m_xPathLB->get_active_text(), 1);
332     m_xGroupTLB->select(nEntry);
333     SelectHdl(*m_xGroupTLB);
334     m_xGroupTLB->scroll_to_row(nEntry);
335 }
336 
IMPL_LINK_NOARG(SwGlossaryGroupDlg,ModifyListBoxHdl,weld::ComboBox &,void)337 IMPL_LINK_NOARG(SwGlossaryGroupDlg, ModifyListBoxHdl, weld::ComboBox&, void)
338 {
339     ModifyHdl(*m_xNameED);
340 }
341 
IMPL_LINK_NOARG(SwGlossaryGroupDlg,ModifyHdl,weld::Entry &,void)342 IMPL_LINK_NOARG(SwGlossaryGroupDlg, ModifyHdl, weld::Entry&, void)
343 {
344     const OUString sEntry(m_xNameED->get_text());
345     bool bEnableNew = true;
346     bool bEnableDel = false;
347     sal_uInt32 nCaseReadonly = m_xPathLB->get_active_id().toUInt32();
348     bool bDirReadonly = 0 != (nCaseReadonly&PATH_READONLY);
349 
350     if (sEntry.isEmpty() || bDirReadonly)
351         bEnableNew = false;
352     else if(!sEntry.isEmpty())
353     {
354         int nPos = m_xGroupTLB->find_text(sEntry);
355         //if it's not case sensitive you have to search for yourself
356         if (nPos == -1)
357         {
358             const ::utl::TransliterationWrapper& rSCmp = GetAppCmpStrIgnore();
359             for (int i = 0, nEntryCount = m_xGroupTLB->n_children(); i < nEntryCount; ++i)
360             {
361                 const OUString sTemp = m_xGroupTLB->get_text(i, 0);
362                 nCaseReadonly = m_xPathLB->get_id(m_xPathLB->find_text(m_xGroupTLB->get_text(i,1))).toUInt32();
363                 bool bCase = 0 != (nCaseReadonly & PATH_CASE_SENSITIVE);
364 
365                 if( !bCase && rSCmp.isEqual( sTemp, sEntry ))
366                 {
367                     nPos = i;
368                     break;
369                 }
370             }
371         }
372         if (nPos != -1)
373         {
374             bEnableNew = false;
375             m_xGroupTLB->select(nPos);
376             m_xGroupTLB->scroll_to_row(nPos);
377             SelectHdl(*m_xGroupTLB);
378         }
379     }
380     int nEntry = m_xGroupTLB->get_selected_index();
381     if (nEntry != -1)
382     {
383         GlosBibUserData* pUserData = reinterpret_cast<GlosBibUserData*>(m_xGroupTLB->get_id(nEntry).toInt64());
384         bEnableDel = IsDeleteAllowed(pUserData->sGroupName);
385     }
386 
387     m_xDelPB->set_sensitive(bEnableDel);
388     m_xNewPB->set_sensitive(bEnableNew);
389     m_xRenamePB->set_sensitive(bEnableNew && nEntry != -1);
390     fprintf(stderr, "two rename %d\n", bEnableNew && nEntry != -1);
391 }
392 
IsDeleteAllowed(const OUString & rGroup)393 bool SwGlossaryGroupDlg::IsDeleteAllowed(const OUString &rGroup)
394 {
395     bool bDel = !pGlosHdl->IsReadOnly(&rGroup);
396 
397     // OM: if the name is among the new region name, it is deletable
398     // as well! Because for non existing region names ReadOnly issues
399     // true.
400 
401     auto it = std::find(m_InsertedArr.cbegin(), m_InsertedArr.cend(), rGroup);
402     if (it != m_InsertedArr.cend())
403         bDel = true;
404 
405     return bDel;
406 }
407 
IMPL_STATIC_LINK(SwGlossaryGroupDlg,EditInsertTextHdl,OUString &,rText,bool)408 IMPL_STATIC_LINK(SwGlossaryGroupDlg, EditInsertTextHdl, OUString&, rText, bool)
409 {
410     rText = rText.replaceAll(OUStringChar(SVT_SEARCHPATH_DELIMITER), "");
411     return true;
412 }
413 
414 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
415