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