LibreOffice Module cui (master) 1
AdditionsDialog.cxx
Go to the documentation of this file.
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 */
10
11#include <sal/config.h>
12
13#include <algorithm>
14#include <cmath>
15
16#include <config_folders.h>
17
18#include <AdditionsDialog.hxx>
19#include <dialmgr.hxx>
20#include <strings.hrc>
21
22#include <sal/log.hxx>
23
24#include <com/sun/star/graphic/GraphicProvider.hpp>
25#include <com/sun/star/graphic/XGraphicProvider.hpp>
26#include <com/sun/star/ucb/NameClash.hpp>
27#include <com/sun/star/ucb/SimpleFileAccess.hpp>
28#include <osl/file.hxx>
29#include <rtl/bootstrap.hxx>
30#include <tools/urlobj.hxx>
31#include <tools/stream.hxx>
34#include <vcl/virdev.hxx>
35#include <vcl/svapp.hxx>
36#include <vcl/graphicfilter.hxx>
38
39#include <com/sun/star/util/SearchFlags.hpp>
40#include <com/sun/star/util/SearchAlgorithms2.hpp>
43#include <ucbhelper/content.hxx>
44
45#include <com/sun/star/deployment/DeploymentException.hpp>
46#include <com/sun/star/deployment/ExtensionManager.hpp>
47#include <com/sun/star/lang/WrappedTargetRuntimeException.hpp>
48#include <com/sun/star/ucb/CommandAbortedException.hpp>
49#include <com/sun/star/ucb/CommandFailedException.hpp>
50
51#include <com/sun/star/task/XInteractionApprove.hpp>
52
53#include <orcus/json_document_tree.hpp>
54#include <orcus/json_parser.hpp>
55#include <orcus/config.hpp>
56
57#include <bitmaps.hlst>
58
59#define PAGE_SIZE 30
60
61using namespace css;
62using ::com::sun::star::uno::Reference;
63using ::com::sun::star::uno::Exception;
64using ::com::sun::star::uno::Sequence;
65
66using namespace com::sun::star;
67using namespace ::com::sun::star::uno;
68using namespace ::com::sun::star::ucb;
69using namespace ::com::sun::star::beans;
70
71namespace
72{
73// Gets the content of the given URL and returns as a standard string
74std::string ucbGet(const OUString& rURL)
75{
76 try
77 {
78 auto const s = utl::UcbStreamHelper::CreateStream(rURL, StreamMode::STD_READ);
79 if (!s)
80 {
81 SAL_WARN("cui.dialogs", "CreateStream <" << rURL << "> failed");
82 return {};
83 }
84 std::string response_body;
85 do
86 {
87 char buf[4096];
88 auto const n = s->ReadBytes(buf, sizeof buf);
89 response_body.append(buf, n);
90 } while (s->good());
91 if (s->bad())
92 {
93 SAL_WARN("cui.dialogs", "Reading <" << rURL << "> failed with " << s->GetError());
94 return {};
95 }
96 return response_body;
97 }
98 catch (css::uno::Exception&)
99 {
100 TOOLS_WARN_EXCEPTION("cui.dialogs", "Download failed");
101 return {};
102 }
103}
104
105// Downloads and saves the file at the given rURL to a local path (sFolderURL/fileName)
106void ucbDownload(const OUString& rURL, const OUString& sFolderURL, const OUString& fileName)
107{
108 try
109 {
113 css::ucb::NameClash::OVERWRITE);
114 }
115 catch (css::uno::Exception&)
116 {
117 TOOLS_WARN_EXCEPTION("cui.dialogs", "Download failed");
118 }
119}
120
121void parseResponse(const std::string& rResponse, std::vector<AdditionInfo>& aAdditions)
122{
123 orcus::json::document_tree aJsonDoc;
124 orcus::json_config aConfig;
125
126 if (rResponse.empty())
127 return;
128
129 try
130 {
131 aJsonDoc.load(rResponse, aConfig);
132 }
133 catch (const orcus::parse_error&)
134 {
135 TOOLS_WARN_EXCEPTION("cui.dialogs", "Invalid JSON file from the extensions API");
136 return;
137 }
138
139 auto aDocumentRoot = aJsonDoc.get_document_root();
140 if (aDocumentRoot.type() != orcus::json::node_t::object)
141 {
142 SAL_WARN("cui.dialogs", "invalid root entries: " << rResponse);
143 return;
144 }
145
146 auto resultsArray = aDocumentRoot.child("extension");
147
148 for (size_t i = 0; i < resultsArray.child_count(); ++i)
149 {
150 auto arrayElement = resultsArray.child(i);
151
152 try
153 {
154 AdditionInfo aNewAddition = {
155 OStringToOUString(arrayElement.child("id").string_value(), RTL_TEXTENCODING_UTF8),
156 OStringToOUString(arrayElement.child("name").string_value(), RTL_TEXTENCODING_UTF8),
157 OStringToOUString(arrayElement.child("author").string_value(),
158 RTL_TEXTENCODING_UTF8),
159 OStringToOUString(arrayElement.child("url").string_value(), RTL_TEXTENCODING_UTF8),
160 OStringToOUString(arrayElement.child("screenshotURL").string_value(),
161 RTL_TEXTENCODING_UTF8),
162 OStringToOUString(arrayElement.child("extensionIntroduction").string_value(),
163 RTL_TEXTENCODING_UTF8),
164 OStringToOUString(arrayElement.child("extensionDescription").string_value(),
165 RTL_TEXTENCODING_UTF8),
166 OStringToOUString(
167 arrayElement.child("releases").child(0).child("compatibility").string_value(),
168 RTL_TEXTENCODING_UTF8),
169 OStringToOUString(
170 arrayElement.child("releases").child(0).child("releaseName").string_value(),
171 RTL_TEXTENCODING_UTF8),
172 OStringToOUString(
173 arrayElement.child("releases").child(0).child("license").string_value(),
174 RTL_TEXTENCODING_UTF8),
175 OStringToOUString(arrayElement.child("commentNumber").string_value(),
176 RTL_TEXTENCODING_UTF8),
177 OStringToOUString(arrayElement.child("commentURL").string_value(),
178 RTL_TEXTENCODING_UTF8),
179 OStringToOUString(arrayElement.child("rating").string_value(),
180 RTL_TEXTENCODING_UTF8),
181 OStringToOUString(arrayElement.child("downloadNumber").string_value(),
182 RTL_TEXTENCODING_UTF8),
183 OStringToOUString(
184 arrayElement.child("releases").child(0).child("downloadURL").string_value(),
185 RTL_TEXTENCODING_UTF8)
186 };
187
188 aAdditions.push_back(aNewAddition);
189 }
190 catch (orcus::json::document_error& e)
191 {
192 // This usually happens when one of the values is null (type() == orcus::json::node_t::null)
193 // TODO: Allow null values in additions.
194 SAL_WARN("cui.dialogs", "Additions JSON parse error: " << e.what());
195 }
196 }
197}
198
199bool getPreviewFile(const AdditionInfo& aAdditionInfo, OUString& sPreviewFile)
200{
202 = ucb::SimpleFileAccess::create(comphelper::getProcessComponentContext());
203
204 // copy the images to the user's additions folder
205 OUString userFolder = "${$BRAND_BASE_DIR/" LIBO_ETC_FOLDER
206 "/" SAL_CONFIGFILE("bootstrap") "::UserInstallation}";
207 rtl::Bootstrap::expandMacros(userFolder);
208 userFolder += "/user/additions/" + aAdditionInfo.sExtensionID + "/";
209
210 OUString aPreviewFile(INetURLObject(aAdditionInfo.sScreenshotURL).getName());
211 OUString aPreviewURL = aAdditionInfo.sScreenshotURL;
212
213 try
214 {
215 osl::Directory::createPath(userFolder);
216
217 if (!xFileAccess->exists(userFolder + aPreviewFile))
218 ucbDownload(aPreviewURL, userFolder, aPreviewFile);
219 }
220 catch (const uno::Exception&)
221 {
222 return false;
223 }
224 sPreviewFile = userFolder + aPreviewFile;
225 return true;
226}
227
228void LoadImage(std::u16string_view rPreviewFile, std::shared_ptr<AdditionsItem> pCurrentItem)
229{
230 const sal_Int8 Margin = 6;
231
232 SolarMutexGuard aGuard;
233
234 GraphicFilter aFilter;
235 Graphic aGraphic;
236
237 INetURLObject aURLObj(rPreviewFile);
238
239 // for VCL to be able to create bitmaps / do visual changes in the thread
240 aFilter.ImportGraphic(aGraphic, aURLObj);
241 BitmapEx aBmp = aGraphic.GetBitmapEx();
242 Size aBmpSize = aBmp.GetSizePixel();
243 Size aThumbSize(pCurrentItem->m_xImageScreenshot->get_size_request());
244 if (!aBmp.IsEmpty())
245 {
246 double aScale;
247 if (aBmpSize.Width() > aThumbSize.Width() - 2 * Margin)
248 {
249 aScale = static_cast<double>(aBmpSize.Width()) / (aThumbSize.Width() - 2 * Margin);
250 aBmp.Scale(Size(aBmpSize.Width() / aScale, aBmpSize.Height() / aScale));
251 }
252 else if (aBmpSize.Height() > aThumbSize.Height() - 2 * Margin)
253 {
254 aScale = static_cast<double>(aBmpSize.Height()) / (aThumbSize.Height() - 2 * Margin);
255 aBmp.Scale(Size(aBmpSize.Width() / aScale, aBmpSize.Height() / aScale));
256 };
257 aBmpSize = aBmp.GetSizePixel();
258 }
259
260 ScopedVclPtr<VirtualDevice> xVirDev = pCurrentItem->m_xImageScreenshot->create_virtual_device();
261 xVirDev->SetOutputSizePixel(aThumbSize);
262 //white background since images come with a white border
263 xVirDev->SetBackground(Wallpaper(COL_WHITE));
264 xVirDev->Erase();
265 xVirDev->DrawBitmapEx(Point(aThumbSize.Width() / 2 - aBmpSize.Width() / 2, Margin), aBmp);
266 pCurrentItem->m_xImageScreenshot->set_image(xVirDev.get());
267 xVirDev.disposeAndClear();
268}
269
270} // End of the anonymous namespace
271
273 : Thread("cuiAdditionsSearchThread")
274 , m_pAdditionsDialog(pDialog)
275 , m_bExecute(true)
276 , m_bIsFirstLoading(isFirstLoading)
277{
278}
279
281
283{
284 if (!m_bExecute)
285 return;
286 OUString aPreviewFile;
287 bool bResult = getPreviewFile(additionInfo, aPreviewFile); // info vector json data
288
289 if (!bResult)
290 {
291 SAL_INFO("cui.dialogs", "Couldn't get the preview file. Skipping: " << aPreviewFile);
292 return;
293 }
294
295 SolarMutexGuard aGuard;
296
297 auto newItem = std::make_shared<AdditionsItem>(m_pAdditionsDialog->m_xContentGrid.get(),
298 m_pAdditionsDialog, additionInfo);
299 m_pAdditionsDialog->m_aAdditionsItems.push_back(newItem);
300 std::shared_ptr<AdditionsItem> aCurrentItem = m_pAdditionsDialog->m_aAdditionsItems.back();
301
302 LoadImage(aPreviewFile, aCurrentItem);
304
306 {
309 aCurrentItem->m_xButtonShowMore->set_visible(true);
310 }
311}
312
314{
316 = m_pAdditionsDialog->m_xEntrySearch->get_text();
318
319 size_t nIteration = 0;
320 for (auto& rInfo : m_pAdditionsDialog->m_aAllExtensionsVector)
321 {
323 break;
324
325 OUString sExtensionName = rInfo.sName;
326 OUString sExtensionDescription = rInfo.sDescription;
327
328 if (!m_pAdditionsDialog->m_xEntrySearch->get_text().isEmpty()
329 && !textSearch.searchForward(sExtensionName)
330 && !textSearch.searchForward(sExtensionDescription))
331 {
332 continue;
333 }
334 else
335 {
337 Append(rInfo);
338 nIteration++;
339 }
340 }
342}
343
345{
348
349 if (!xAllPackages.hasElements())
350 return;
351
352 OUString currentExtensionName;
353
354 for (auto& package : xAllPackages)
355 {
356 for (auto& extensionVersion : package)
357 {
358 if (extensionVersion.is())
359 {
360 currentExtensionName = extensionVersion->getName();
361 if (currentExtensionName.isEmpty())
362 continue;
363
364 m_pAdditionsDialog->m_searchOptions.searchString = currentExtensionName;
366
367 for (auto& rInfo : m_pAdditionsDialog->m_aAdditionsItems)
368 {
369 OUString sExtensionDownloadURL = rInfo->m_sDownloadURL;
370
371 if (!textSearch.searchForward(sExtensionDownloadURL))
372 {
373 continue;
374 }
375 else
376 {
377 SolarMutexGuard aGuard;
378 rInfo->m_xButtonInstall->set_sensitive(false);
379 rInfo->m_xButtonInstall->set_label(
380 CuiResId(RID_CUISTR_ADDITIONS_INSTALLEDBUTTON));
381 }
382 }
383 }
384 }
385 }
386}
387
389{
390 OUString sProgress;
392 sProgress = CuiResId(RID_CUISTR_ADDITIONS_LOADING);
393 else
394 sProgress = CuiResId(RID_CUISTR_ADDITIONS_SEARCHING);
395
397 sProgress); // Loading or searching according to being first call or not
398
400 {
401 std::string sResponse = ucbGet(m_pAdditionsDialog->m_sURL);
402 parseResponse(sResponse, m_pAdditionsDialog->m_aAllExtensionsVector);
406 Search();
407 }
408 else // Searching
409 {
410 Search();
411 }
412
413 if (!m_bExecute)
414 return;
415
416 SolarMutexGuard aGuard;
417 sProgress.clear();
419}
420
421AdditionsDialog::AdditionsDialog(weld::Window* pParent, const OUString& sAdditionsTag)
422 : GenericDialogController(pParent, "cui/ui/additionsdialog.ui", "AdditionsDialog")
423 , m_aSearchDataTimer("AdditionsDialog SearchDataTimer")
424 , m_xEntrySearch(m_xBuilder->weld_entry("entrySearch"))
425 , m_xButtonClose(m_xBuilder->weld_button("buttonClose"))
426 , m_xContentWindow(m_xBuilder->weld_scrolled_window("contentWindow"))
427 , m_xContentGrid(m_xBuilder->weld_container("contentGrid"))
428 , m_xLabelProgress(m_xBuilder->weld_label("labelProgress"))
429 , m_xGearBtn(m_xBuilder->weld_menu_button("buttonGear"))
430{
431 m_xGearBtn->connect_selected(LINK(this, AdditionsDialog, GearHdl));
432 m_xGearBtn->set_item_active("gear_sort_voting", true);
433
434 m_aSearchDataTimer.SetInvokeHandler(LINK(this, AdditionsDialog, ImplUpdateDataHdl));
436
437 m_xEntrySearch->connect_changed(LINK(this, AdditionsDialog, SearchUpdateHdl));
438 m_xEntrySearch->connect_focus_out(LINK(this, AdditionsDialog, FocusOut_Impl));
439 m_xButtonClose->connect_clicked(LINK(this, AdditionsDialog, CloseButtonHdl));
440
441 m_sTag = sAdditionsTag;
442 m_nMaxItemCount = PAGE_SIZE; // Dialog initialization item count
443 m_nCurrentListItemCount = 0; // First, there is no item on the list.
444
445 OUString titlePrefix = CuiResId(RID_CUISTR_ADDITIONS_DIALOG_TITLE_PREFIX);
446 if (!m_sTag.isEmpty())
447 { // tdf#142564 localize extension category names
448 OUString sDialogTitle = "";
449 if (sAdditionsTag == "Templates")
450 {
451 sDialogTitle = CuiResId(RID_CUISTR_ADDITIONS_TEMPLATES);
452 }
453 else if (sAdditionsTag == "Dictionary")
454 {
455 sDialogTitle = CuiResId(RID_CUISTR_ADDITIONS_DICTIONARY);
456 }
457 else if (sAdditionsTag == "Gallery")
458 {
459 sDialogTitle = CuiResId(RID_CUISTR_ADDITIONS_GALLERY);
460 }
461 else if (sAdditionsTag == "Icons")
462 {
463 sDialogTitle = CuiResId(RID_CUISTR_ADDITIONS_ICONS);
464 }
465 else if (sAdditionsTag == "Color Palette")
466 {
467 sDialogTitle = CuiResId(RID_CUISTR_ADDITIONS_PALETTES);
468 }
469 this->set_title(sDialogTitle);
470 }
471 else
472 {
473 this->set_title(titlePrefix);
474 m_sTag = "allextensions"; // Means empty parameter
475 }
476
477 OUString sEncodedURLPart = INetURLObject::encode(m_sTag, INetURLObject::PART_PCHAR,
479
480 //FIXME: Temporary URL - v0 is not using actual api
481 OUString rURL = "https://extensions.libreoffice.org/api/v0/" + sEncodedURLPart + ".json";
482 m_sURL = rURL;
483
485 = deployment::ExtensionManager::get(::comphelper::getProcessComponentContext());
486
487 //Initialize search util
488 m_searchOptions.AlgorithmType2 = css::util::SearchAlgorithms2::ABSOLUTE;
489 m_searchOptions.transliterateFlags |= TransliterationFlags::IGNORE_CASE;
490 m_searchOptions.searchFlag |= (css::util::SearchFlags::REG_NOT_BEGINOFLINE
491 | css::util::SearchFlags::REG_NOT_ENDOFLINE);
492 m_pSearchThread = new SearchAndParseThread(this, true);
493 m_pSearchThread->launch();
494}
495
497{
498 if (m_pSearchThread.is())
499 {
500 m_pSearchThread->StopExecution();
501 // Release the solar mutex, so the thread is not affected by the race
502 // when it's after the m_bExecute check but before taking the solar
503 // mutex.
504 SolarMutexReleaser aReleaser;
505 m_pSearchThread->join();
506 }
507}
508
511{
513
514 try
515 {
516 xAllPackages = m_xExtensionManager->getAllExtensions(
518 }
519 catch (const deployment::DeploymentException&)
520 {
521 TOOLS_WARN_EXCEPTION("cui.dialogs", "");
522 }
523 catch (const ucb::CommandFailedException&)
524 {
525 TOOLS_WARN_EXCEPTION("cui.dialogs", "");
526 }
527 catch (const ucb::CommandAbortedException&)
528 {
529 TOOLS_WARN_EXCEPTION("cui.dialogs", "");
530 }
531 catch (const lang::IllegalArgumentException& e)
532 {
533 css::uno::Any anyEx = cppu::getCaughtException();
534 throw css::lang::WrappedTargetRuntimeException(e.Message, e.Context, anyEx);
535 }
536 return xAllPackages;
537}
538
539void AdditionsDialog::SetProgress(const OUString& rProgress)
540{
541 if (rProgress.isEmpty())
542 {
543 m_xLabelProgress->hide();
544 m_xButtonClose->set_sensitive(true);
545 }
546 else
547 {
548 SolarMutexGuard aGuard;
549 m_xLabelProgress->show();
550 m_xLabelProgress->set_label(rProgress);
551 m_xDialog->resize_to_request(); //TODO
552 }
553}
554
556{
557 // for VCL to be able to destroy bitmaps
558 SolarMutexGuard aGuard;
559
560 for (auto& item : this->m_aAdditionsItems)
561 {
562 item->m_xContainer->hide();
563 }
564 this->m_aAdditionsItems.clear();
565}
566
568{
569 if (m_pSearchThread.is())
570 m_pSearchThread->StopExecution();
571 ClearList();
574 m_pSearchThread = new SearchAndParseThread(this, false);
575 m_pSearchThread->launch();
576}
577
579{
580 return a.sRating.toDouble() > b.sRating.toDouble();
581}
582
584{
585 return a.sCommentNumber.toUInt32() > b.sCommentNumber.toUInt32();
586}
587
589{
590 return a.sDownloadNumber.toUInt32() > b.sDownloadNumber.toUInt32();
591}
592
594 const AdditionInfo& additionInfo)
595 : m_xBuilder(Application::CreateBuilder(pParent, "cui/ui/additionsfragment.ui"))
596 , m_xContainer(m_xBuilder->weld_widget("additionsEntry"))
597 , m_xImageScreenshot(m_xBuilder->weld_image("imageScreenshot"))
598 , m_xButtonInstall(m_xBuilder->weld_button("buttonInstall"))
599 , m_xLinkButtonWebsite(m_xBuilder->weld_link_button("btnWebsite"))
600 , m_xLabelName(m_xBuilder->weld_label("lbName"))
601 , m_xLabelAuthor(m_xBuilder->weld_label("labelAuthor"))
602 , m_xLabelDescription(m_xBuilder->weld_label("labelDescription"))
603 , m_xLabelLicense(m_xBuilder->weld_label("lbLicenseText"))
604 , m_xLabelVersion(m_xBuilder->weld_label("lbVersionText"))
605 , m_xLinkButtonComments(m_xBuilder->weld_link_button("linkButtonComments"))
606 , m_xImageVoting1(m_xBuilder->weld_image("imageVoting1"))
607 , m_xImageVoting2(m_xBuilder->weld_image("imageVoting2"))
608 , m_xImageVoting3(m_xBuilder->weld_image("imageVoting3"))
609 , m_xImageVoting4(m_xBuilder->weld_image("imageVoting4"))
610 , m_xImageVoting5(m_xBuilder->weld_image("imageVoting5"))
611 , m_xLabelDownloadNumber(m_xBuilder->weld_label("labelDownloadNumber"))
612 , m_xButtonShowMore(m_xBuilder->weld_button("buttonShowMore"))
613 , m_pParentDialog(pParentDialog)
614 , m_sDownloadURL("")
615 , m_sExtensionID("")
616{
617 SolarMutexGuard aGuard;
618
619 // AdditionsItem set location
620 m_xContainer->set_grid_left_attach(0);
621 m_xContainer->set_grid_top_attach(pParentDialog->m_aAdditionsItems.size());
622
623 // Set maximum length of the extension title
624 OUString sExtensionName;
625 const sal_Int32 maxExtensionNameLength = 30;
626
627 if (additionInfo.sName.getLength() > maxExtensionNameLength)
628 {
629 std::u16string_view sShortName = additionInfo.sName.subView(0, maxExtensionNameLength - 3);
630 sExtensionName = OUString::Concat(sShortName) + "...";
631 }
632 else
633 {
634 sExtensionName = additionInfo.sName;
635 }
636
637 m_xLabelName->set_label(sExtensionName);
638
639 double aExtensionRating = additionInfo.sRating.toDouble();
640 switch (std::isnan(aExtensionRating) ? 0 : int(std::clamp(aExtensionRating, 0.0, 5.0)))
641 {
642 case 5:
643 m_xImageVoting5->set_from_icon_name(RID_SVXBMP_STARS_FULL);
644 [[fallthrough]];
645 case 4:
646 m_xImageVoting4->set_from_icon_name(RID_SVXBMP_STARS_FULL);
647 [[fallthrough]];
648 case 3:
649 m_xImageVoting3->set_from_icon_name(RID_SVXBMP_STARS_FULL);
650 [[fallthrough]];
651 case 2:
652 m_xImageVoting2->set_from_icon_name(RID_SVXBMP_STARS_FULL);
653 [[fallthrough]];
654 case 1:
655 m_xImageVoting1->set_from_icon_name(RID_SVXBMP_STARS_FULL);
656 break;
657 }
658
659 m_xLinkButtonWebsite->set_uri(additionInfo.sExtensionURL);
660 m_xLabelDescription->set_label(additionInfo.sIntroduction);
661
662 if (!additionInfo.sAuthorName.equalsIgnoreAsciiCase("null"))
663 m_xLabelAuthor->set_label(additionInfo.sAuthorName);
664
665 m_xButtonInstall->set_label(CuiResId(RID_CUISTR_ADDITIONS_INSTALLBUTTON));
666 m_xLabelLicense->set_label(additionInfo.sLicense);
667 m_xLabelVersion->set_label(">=" + additionInfo.sCompatibleVersion);
668 m_xLinkButtonComments->set_label(additionInfo.sCommentNumber);
669 m_xLinkButtonComments->set_uri(additionInfo.sCommentURL);
670 m_xLabelDownloadNumber->set_label(additionInfo.sDownloadNumber);
671 m_pParentDialog = pParentDialog;
672 m_sDownloadURL = additionInfo.sDownloadURL;
673 m_sExtensionID = additionInfo.sExtensionID;
674
675 m_xButtonShowMore->connect_clicked(LINK(this, AdditionsItem, ShowMoreHdl));
676 m_xButtonInstall->connect_clicked(LINK(this, AdditionsItem, InstallHdl));
677}
678
679bool AdditionsItem::getExtensionFile(OUString& sExtensionFile)
680{
682 = ucb::SimpleFileAccess::create(comphelper::getProcessComponentContext());
683
684 // copy the extensions' files to the user's additions folder
685 OUString userFolder = "${$BRAND_BASE_DIR/" LIBO_ETC_FOLDER
686 "/" SAL_CONFIGFILE("bootstrap") "::UserInstallation}";
687 rtl::Bootstrap::expandMacros(userFolder);
688 userFolder += "/user/additions/" + m_sExtensionID + "/";
689
690 OUString aExtensionsFile(INetURLObject(m_sDownloadURL).getName());
691 OUString aExtensionsURL = m_sDownloadURL;
692
693 try
694 {
695 osl::Directory::createPath(userFolder);
696
697 if (!xFileAccess->exists(userFolder + aExtensionsFile))
698 ucbDownload(aExtensionsURL, userFolder, aExtensionsFile);
699 }
700 catch (const uno::Exception&)
701 {
702 return false;
703 }
704 sExtensionFile = userFolder + aExtensionsFile;
705 return true;
706}
707
708IMPL_LINK_NOARG(AdditionsDialog, ImplUpdateDataHdl, Timer*, void) { RefreshUI(); }
709
711{
712 m_aSearchDataTimer.Start();
713}
714
716{
717 if (m_aSearchDataTimer.IsActive())
718 {
719 m_aSearchDataTimer.Stop();
720 m_aSearchDataTimer.Invoke();
721 }
722}
723
725{
726 if (m_pSearchThread.is())
727 m_pSearchThread->StopExecution();
728 this->response(RET_CLOSE);
729}
730
732{
733 this->m_xButtonShowMore->set_visible(false);
734 m_pParentDialog->m_nMaxItemCount += PAGE_SIZE;
735 if (m_pParentDialog->m_pSearchThread.is())
736 m_pParentDialog->m_pSearchThread->StopExecution();
737 m_pParentDialog->m_pSearchThread = new SearchAndParseThread(m_pParentDialog, false);
738 m_pParentDialog->m_pSearchThread->launch();
739}
740
742{
743 m_xButtonInstall->set_label(CuiResId(RID_CUISTR_ADDITIONS_INSTALLING));
744 m_xButtonInstall->set_sensitive(false);
745 OUString aExtensionFile;
746 bool bResult = getExtensionFile(aExtensionFile); // info vector json data
747
748 if (!bResult)
749 {
750 m_xButtonInstall->set_label(CuiResId(RID_CUISTR_ADDITIONS_INSTALLBUTTON));
751 m_xButtonInstall->set_sensitive(true);
752
753 SAL_INFO("cui.dialogs", "Couldn't get the extension file.");
754 return;
755 }
756
759 try
760 {
761 m_pParentDialog->m_xExtensionManager->addExtension(
762 aExtensionFile, uno::Sequence<beans::NamedValue>(), "user", xAbortChannel, pCmdEnv);
763 m_xButtonInstall->set_label(CuiResId(RID_CUISTR_ADDITIONS_INSTALLEDBUTTON));
764 }
765 catch (const ucb::CommandFailedException)
766 {
767 TOOLS_WARN_EXCEPTION("cui.dialogs", "");
768 m_xButtonInstall->set_label(CuiResId(RID_CUISTR_ADDITIONS_INSTALLBUTTON));
769 m_xButtonInstall->set_sensitive(true);
770 }
771 catch (const ucb::CommandAbortedException)
772 {
773 TOOLS_WARN_EXCEPTION("cui.dialogs", "");
774 m_xButtonInstall->set_label(CuiResId(RID_CUISTR_ADDITIONS_INSTALLBUTTON));
775 m_xButtonInstall->set_sensitive(true);
776 }
777 catch (const deployment::DeploymentException)
778 {
779 TOOLS_WARN_EXCEPTION("cui.dialogs", "");
780 m_xButtonInstall->set_label(CuiResId(RID_CUISTR_ADDITIONS_INSTALLBUTTON));
781 m_xButtonInstall->set_sensitive(true);
782 }
783 catch (const lang::IllegalArgumentException)
784 {
785 TOOLS_WARN_EXCEPTION("cui.dialogs", "");
786 m_xButtonInstall->set_label(CuiResId(RID_CUISTR_ADDITIONS_INSTALLBUTTON));
787 m_xButtonInstall->set_sensitive(true);
788 }
789 catch (const css::uno::Exception)
790 {
791 TOOLS_WARN_EXCEPTION("cui.dialogs", "");
792 m_xButtonInstall->set_label(CuiResId(RID_CUISTR_ADDITIONS_INSTALLBUTTON));
793 m_xButtonInstall->set_sensitive(true);
794 }
795}
796
797// TmpRepositoryCommandEnv
798
800
802// XCommandEnvironment
803
805{
806 return this;
807}
808
810
811// XInteractionHandler
813{
814 OSL_ASSERT(xRequest->getRequest().getValueTypeClass() == uno::TypeClass_EXCEPTION);
815
816 bool approve = true;
817
818 // select:
819 uno::Sequence<Reference<task::XInteractionContinuation>> conts(xRequest->getContinuations());
820 Reference<task::XInteractionContinuation> const* pConts = conts.getConstArray();
821 sal_Int32 len = conts.getLength();
822 for (sal_Int32 pos = 0; pos < len; ++pos)
823 {
824 if (approve)
825 {
826 uno::Reference<task::XInteractionApprove> xInteractionApprove(pConts[pos],
827 uno::UNO_QUERY);
828 if (xInteractionApprove.is())
829 {
830 xInteractionApprove->select();
831 // don't query again for ongoing continuations:
832 approve = false;
833 }
834 }
835 }
836}
837
838// XProgressHandler
839void TmpRepositoryCommandEnv::push(uno::Any const& /*Status*/) {}
840
841void TmpRepositoryCommandEnv::update(uno::Any const& /*Status */) {}
842
844
845IMPL_LINK(AdditionsDialog, GearHdl, const OUString&, rIdent, void)
846{
847 if (rIdent == "gear_sort_voting")
848 {
849 std::sort(m_aAllExtensionsVector.begin(), m_aAllExtensionsVector.end(), sortByRating);
850 }
851 else if (rIdent == "gear_sort_comments")
852 {
853 std::sort(m_aAllExtensionsVector.begin(), m_aAllExtensionsVector.end(), sortByComment);
854 }
855 else if (rIdent == "gear_sort_downloads")
856 {
857 std::sort(m_aAllExtensionsVector.begin(), m_aAllExtensionsVector.end(), sortByDownload);
858 }
859 // After the sorting, UI will be refreshed to update extension list.
860 RefreshUI();
861}
862/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
IMPL_LINK(AdditionsDialog, GearHdl, const OUString &, rIdent, void)
IMPL_LINK_NOARG(AdditionsDialog, ImplUpdateDataHdl, Timer *, void)
#define PAGE_SIZE
i18nutil::SearchOptions2 m_searchOptions
std::unique_ptr< weld::MenuButton > m_xGearBtn
std::vector< std::shared_ptr< AdditionsItem > > m_aAdditionsItems
std::vector< AdditionInfo > m_aAllExtensionsVector
void SetProgress(const OUString &rProgress)
static bool sortByComment(const AdditionInfo &a, const AdditionInfo &b)
std::unique_ptr< weld::Button > m_xButtonClose
AdditionsDialog(weld::Window *pParent, const OUString &sAdditionsTag)
std::unique_ptr< weld::Container > m_xContentGrid
std::unique_ptr< weld::Entry > m_xEntrySearch
css::uno::Reference< css::deployment::XExtensionManager > m_xExtensionManager
static bool sortByRating(const AdditionInfo &a, const AdditionInfo &b)
~AdditionsDialog() override
static bool sortByDownload(const AdditionInfo &a, const AdditionInfo &b)
size_t m_nCurrentListItemCount
::rtl::Reference< SearchAndParseThread > m_pSearchThread
std::unique_ptr< weld::Label > m_xLabelProgress
css::uno::Sequence< css::uno::Sequence< css::uno::Reference< css::deployment::XPackage > > > getInstalledExtensions()
std::unique_ptr< weld::Image > m_xImageVoting5
std::unique_ptr< weld::Button > m_xButtonInstall
std::unique_ptr< weld::Label > m_xLabelName
std::unique_ptr< weld::Image > m_xImageVoting4
std::unique_ptr< weld::Image > m_xImageVoting3
std::unique_ptr< weld::Label > m_xLabelDownloadNumber
bool getExtensionFile(OUString &sExtensionFile)
std::unique_ptr< weld::Image > m_xImageVoting1
OUString m_sDownloadURL
AdditionsDialog * m_pParentDialog
AdditionsItem(weld::Widget *pParent, AdditionsDialog *pParentDialog, const AdditionInfo &additionInfo)
std::unique_ptr< weld::LinkButton > m_xLinkButtonWebsite
std::unique_ptr< weld::Label > m_xLabelLicense
OUString m_sExtensionID
std::unique_ptr< weld::Label > m_xLabelAuthor
std::unique_ptr< weld::Widget > m_xContainer
std::unique_ptr< weld::LinkButton > m_xLinkButtonComments
std::unique_ptr< weld::Label > m_xLabelVersion
std::unique_ptr< weld::Label > m_xLabelDescription
std::unique_ptr< weld::Button > m_xButtonShowMore
std::unique_ptr< weld::Image > m_xImageVoting2
bool Scale(const Size &rNewSize, BmpScaleFlag nScaleFlag=BmpScaleFlag::Default)
bool IsEmpty() const
const Size & GetSizePixel() const
ErrCode ImportGraphic(Graphic &rGraphic, const INetURLObject &rPath, sal_uInt16 nFormat=GRFILTER_FORMAT_DONTKNOW, sal_uInt16 *pDeterminedFormat=nullptr, GraphicFilterImportFlags nImportFlags=GraphicFilterImportFlags::NONE)
BitmapEx GetBitmapEx(const GraphicConversionParameters &rParameters=GraphicConversionParameters()) const
OUString getName(sal_Int32 nIndex=LAST_SEGMENT, bool bIgnoreFinalSlash=true, DecodeMechanism eMechanism=DecodeMechanism::ToIUri, rtl_TextEncoding eCharset=RTL_TEXTENCODING_UTF8) const
static OUString encode(std::u16string_view rText, Part ePart, EncodeMechanism eMechanism, rtl_TextEncoding eCharset=RTL_TEXTENCODING_UTF8)
virtual void execute() override
virtual ~SearchAndParseThread() override
SearchAndParseThread(AdditionsDialog *pDialog, bool bIsFirstLoading)
AdditionsDialog * m_pAdditionsDialog
void Append(AdditionInfo &additionInfo)
std::atomic< bool > m_bExecute
constexpr tools::Long Height() const
constexpr tools::Long Width() const
void SetTimeout(sal_uInt64 nTimeoutMs)
void SetInvokeHandler(const Link< Timer *, void > &rLink)
virtual void SAL_CALL push(css::uno::Any const &Status) override
virtual void SAL_CALL handle(css::uno::Reference< css::task::XInteractionRequest > const &xRequest) override
virtual css::uno::Reference< css::task::XInteractionHandler > SAL_CALL getInteractionHandler() override
virtual void SAL_CALL pop() override
virtual ~TmpRepositoryCommandEnv() override
virtual void SAL_CALL update(css::uno::Any const &Status) override
virtual css::uno::Reference< css::ucb::XProgressHandler > SAL_CALL getProgressHandler() override
void disposeAndClear()
reference_type * get() const
bool searchForward(const OUString &rStr)
static std::unique_ptr< SvStream > CreateStream(const OUString &rFileName, StreamMode eOpenMode, css::uno::Reference< css::awt::XWindow > xParentWin=nullptr)
void set_title(const OUString &rTitle)
std::shared_ptr< weld::Dialog > m_xDialog
constexpr ::Color COL_WHITE(0xFF, 0xFF, 0xFF)
#define SAL_CONFIGFILE(name)
OUString CuiResId(TranslateId aKey)
Definition: cuiresmgr.cxx:23
#define TOOLS_WARN_EXCEPTION(area, stream)
sal_Int64 n
uno_Any a
#define SAL_WARN(area, stream)
#define SAL_INFO(area, stream)
Reference< XComponentContext > getProcessComponentContext()
Any SAL_CALL getCaughtException()
int i
Reference< XNameAccess > m_xContainer
OUString sExtensionID
OUString sDownloadURL
OUString sAuthorName
OUString sScreenshotURL
OUString sDownloadNumber
OUString sCommentNumber
OUString sCommentURL
OUString sCompatibleVersion
OUString sExtensionURL
OUString sIntroduction
TransliterationFlags transliterateFlags
#define EDIT_UPDATEDATA_TIMEOUT
signed char sal_Int8
RET_CLOSE
size_t pos