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