Remove non-const Sequence::begin()/end() in internal code
[LibreOffice.git] / cui / source / dialogs / AdditionsDialog.cxx
blobac633177ef36b69b086d8c7ce0042d6397199e0b
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
3 * This file is part of the LibreOffice project.
5 * This Source Code Form is subject to the terms of the Mozilla Public
6 * License, v. 2.0. If a copy of the MPL was not distributed with this
7 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
9 */
11 #include <sal/config.h>
13 #include <algorithm>
14 #include <cmath>
15 #include <string_view>
17 #include <config_folders.h>
19 #include <AdditionsDialog.hxx>
20 #include <dialmgr.hxx>
21 #include <strings.hrc>
23 #include <sal/log.hxx>
25 #include <com/sun/star/graphic/GraphicProvider.hpp>
26 #include <com/sun/star/graphic/XGraphicProvider.hpp>
27 #include <com/sun/star/ucb/NameClash.hpp>
28 #include <com/sun/star/ucb/SimpleFileAccess.hpp>
29 #include <osl/file.hxx>
30 #include <rtl/bootstrap.hxx>
31 #include <tools/urlobj.hxx>
32 #include <tools/stream.hxx>
33 #include <tools/diagnose_ex.h>
34 #include <comphelper/processfactory.hxx>
35 #include <vcl/virdev.hxx>
36 #include <vcl/svapp.hxx>
37 #include <vcl/graphicfilter.hxx>
38 #include <cppuhelper/exc_hlp.hxx>
40 #include <com/sun/star/util/SearchFlags.hpp>
41 #include <com/sun/star/util/SearchAlgorithms2.hpp>
42 #include <unotools/textsearch.hxx>
43 #include <unotools/ucbstreamhelper.hxx>
44 #include <ucbhelper/content.hxx>
46 #include <com/sun/star/deployment/DeploymentException.hpp>
47 #include <com/sun/star/deployment/ExtensionManager.hpp>
48 #include <com/sun/star/lang/WrappedTargetRuntimeException.hpp>
49 #include <com/sun/star/ucb/CommandAbortedException.hpp>
50 #include <com/sun/star/ucb/CommandFailedException.hpp>
52 #include <com/sun/star/task/XInteractionApprove.hpp>
54 #include <orcus/json_document_tree.hpp>
55 #include <orcus/json_parser.hpp>
56 #include <orcus/config.hpp>
57 #include <orcus/pstring.hpp>
59 #define PAGE_SIZE 30
61 using namespace css;
62 using ::com::sun::star::uno::Reference;
63 using ::com::sun::star::uno::XComponentContext;
64 using ::com::sun::star::uno::UNO_QUERY_THROW;
65 using ::com::sun::star::uno::Exception;
66 using ::com::sun::star::graphic::GraphicProvider;
67 using ::com::sun::star::graphic::XGraphicProvider;
68 using ::com::sun::star::uno::Sequence;
69 using ::com::sun::star::beans::PropertyValue;
70 using ::com::sun::star::graphic::XGraphic;
72 using namespace com::sun::star;
73 using namespace ::com::sun::star::uno;
74 using namespace ::com::sun::star::ucb;
75 using namespace ::com::sun::star::beans;
77 namespace
79 // Gets the content of the given URL and returns as a standard string
80 std::string ucbGet(const OUString& rURL)
82 try
84 auto const s = utl::UcbStreamHelper::CreateStream(rURL, StreamMode::STD_READ);
85 if (!s)
87 SAL_WARN("cui.dialogs", "CreateStream <" << rURL << "> failed");
88 return {};
90 std::string response_body;
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())
99 SAL_WARN("cui.dialogs", "Reading <" << rURL << "> failed with " << s->GetError());
100 return {};
102 return response_body;
104 catch (css::uno::Exception&)
106 TOOLS_WARN_EXCEPTION("cui.dialogs", "Download failed");
107 return {};
111 // Downloads and saves the file at the given rURL to a local path (sFolderURL/fileName)
112 void ucbDownload(const OUString& rURL, const OUString& sFolderURL, const OUString& fileName)
116 ucbhelper::Content(sFolderURL, {}, comphelper::getProcessComponentContext())
117 .transferContent(ucbhelper::Content(rURL, {}, comphelper::getProcessComponentContext()),
118 ucbhelper::InsertOperation::Copy, fileName,
119 css::ucb::NameClash::OVERWRITE);
121 catch (css::uno::Exception&)
123 TOOLS_WARN_EXCEPTION("cui.dialogs", "Download failed");
127 void parseResponse(const std::string& rResponse, std::vector<AdditionInfo>& aAdditions)
129 orcus::json::document_tree aJsonDoc;
130 orcus::json_config aConfig;
132 if (rResponse.empty())
133 return;
137 aJsonDoc.load(rResponse, aConfig);
139 catch (const orcus::json::parse_error&)
141 TOOLS_WARN_EXCEPTION("cui.dialogs", "Invalid JSON file from the extensions API");
142 return;
145 auto aDocumentRoot = aJsonDoc.get_document_root();
146 if (aDocumentRoot.type() != orcus::json::node_t::object)
148 SAL_WARN("cui.dialogs", "invalid root entries: " << rResponse);
149 return;
152 auto resultsArray = aDocumentRoot.child("extension");
154 for (size_t i = 0; i < resultsArray.child_count(); ++i)
156 auto arrayElement = resultsArray.child(i);
160 AdditionInfo aNewAddition = {
161 OStringToOUString(std::string_view(arrayElement.child("id").string_value().get()),
162 RTL_TEXTENCODING_UTF8),
163 OStringToOUString(std::string_view(arrayElement.child("name").string_value().get()),
164 RTL_TEXTENCODING_UTF8),
165 OStringToOUString(
166 std::string_view(arrayElement.child("author").string_value().get()),
167 RTL_TEXTENCODING_UTF8),
168 OStringToOUString(std::string_view(arrayElement.child("url").string_value().get()),
169 RTL_TEXTENCODING_UTF8),
170 OStringToOUString(
171 std::string_view(arrayElement.child("screenshotURL").string_value().get()),
172 RTL_TEXTENCODING_UTF8),
173 OStringToOUString(
174 std::string_view(
175 arrayElement.child("extensionIntroduction").string_value().get()),
176 RTL_TEXTENCODING_UTF8),
177 OStringToOUString(
178 std::string_view(
179 arrayElement.child("extensionDescription").string_value().get()),
180 RTL_TEXTENCODING_UTF8),
181 OStringToOUString(std::string_view(arrayElement.child("releases")
182 .child(0)
183 .child("compatibility")
184 .string_value()
185 .get()),
186 RTL_TEXTENCODING_UTF8),
187 OStringToOUString(std::string_view(arrayElement.child("releases")
188 .child(0)
189 .child("releaseName")
190 .string_value()
191 .get()),
192 RTL_TEXTENCODING_UTF8),
193 OStringToOUString(std::string_view(arrayElement.child("releases")
194 .child(0)
195 .child("license")
196 .string_value()
197 .get()),
198 RTL_TEXTENCODING_UTF8),
199 OStringToOUString(
200 std::string_view(arrayElement.child("commentNumber").string_value().get()),
201 RTL_TEXTENCODING_UTF8),
202 OStringToOUString(
203 std::string_view(arrayElement.child("commentURL").string_value().get()),
204 RTL_TEXTENCODING_UTF8),
205 OStringToOUString(
206 std::string_view(arrayElement.child("rating").string_value().get()),
207 RTL_TEXTENCODING_UTF8),
208 OStringToOUString(
209 std::string_view(arrayElement.child("downloadNumber").string_value().get()),
210 RTL_TEXTENCODING_UTF8),
211 OStringToOUString(std::string_view(arrayElement.child("releases")
212 .child(0)
213 .child("downloadURL")
214 .string_value()
215 .get()),
216 RTL_TEXTENCODING_UTF8)
219 aAdditions.push_back(aNewAddition);
221 catch (orcus::json::document_error& e)
223 // This usually happens when one of the values is null (type() == orcus::json::node_t::null)
224 // TODO: Allow null values in additions.
225 SAL_WARN("cui.dialogs", "Additions JSON parse error: " << e.what());
230 bool getPreviewFile(const AdditionInfo& aAdditionInfo, OUString& sPreviewFile)
232 uno::Reference<ucb::XSimpleFileAccess3> xFileAccess
233 = ucb::SimpleFileAccess::create(comphelper::getProcessComponentContext());
235 // copy the images to the user's additions folder
236 OUString userFolder = "${$BRAND_BASE_DIR/" LIBO_ETC_FOLDER
237 "/" SAL_CONFIGFILE("bootstrap") "::UserInstallation}";
238 rtl::Bootstrap::expandMacros(userFolder);
239 userFolder += "/user/additions/" + aAdditionInfo.sExtensionID + "/";
241 OUString aPreviewFile(INetURLObject(aAdditionInfo.sScreenshotURL).getName());
242 OUString aPreviewURL = aAdditionInfo.sScreenshotURL;
246 osl::Directory::createPath(userFolder);
248 if (!xFileAccess->exists(userFolder + aPreviewFile))
249 ucbDownload(aPreviewURL, userFolder, aPreviewFile);
251 catch (const uno::Exception&)
253 return false;
255 sPreviewFile = userFolder + aPreviewFile;
256 return true;
259 void LoadImage(const OUString& rPreviewFile, std::shared_ptr<AdditionsItem> pCurrentItem)
261 const sal_Int8 Margin = 6;
263 SolarMutexGuard aGuard;
265 GraphicFilter aFilter;
266 Graphic aGraphic;
268 INetURLObject aURLObj(rPreviewFile);
270 // for VCL to be able to create bitmaps / do visual changes in the thread
271 aFilter.ImportGraphic(aGraphic, aURLObj);
272 BitmapEx aBmp = aGraphic.GetBitmapEx();
273 Size aBmpSize = aBmp.GetSizePixel();
274 Size aThumbSize(pCurrentItem->m_xImageScreenshot->get_size_request());
275 if (!aBmp.IsEmpty())
277 double aScale;
278 if (aBmpSize.Width() > aThumbSize.Width() - 2 * Margin)
280 aScale = static_cast<double>(aBmpSize.Width()) / (aThumbSize.Width() - 2 * Margin);
281 aBmp.Scale(Size(aBmpSize.Width() / aScale, aBmpSize.Height() / aScale));
283 else if (aBmpSize.Height() > aThumbSize.Height() - 2 * Margin)
285 aScale = static_cast<double>(aBmpSize.Height()) / (aThumbSize.Height() - 2 * Margin);
286 aBmp.Scale(Size(aBmpSize.Width() / aScale, aBmpSize.Height() / aScale));
288 aBmpSize = aBmp.GetSizePixel();
291 ScopedVclPtr<VirtualDevice> xVirDev = pCurrentItem->m_xImageScreenshot->create_virtual_device();
292 xVirDev->SetOutputSizePixel(aThumbSize);
293 //white background since images come with a white border
294 xVirDev->SetBackground(Wallpaper(COL_WHITE));
295 xVirDev->Erase();
296 xVirDev->DrawBitmapEx(Point(aThumbSize.Width() / 2 - aBmpSize.Width() / 2, Margin), aBmp);
297 pCurrentItem->m_xImageScreenshot->set_image(xVirDev.get());
298 xVirDev.disposeAndClear();
301 } // End of the anonymous namespace
303 SearchAndParseThread::SearchAndParseThread(AdditionsDialog* pDialog, const bool isFirstLoading)
304 : Thread("cuiAdditionsSearchThread")
305 , m_pAdditionsDialog(pDialog)
306 , m_bExecute(true)
307 , m_bIsFirstLoading(isFirstLoading)
311 SearchAndParseThread::~SearchAndParseThread() {}
313 void SearchAndParseThread::Append(AdditionInfo& additionInfo)
315 if (!m_bExecute)
316 return;
317 OUString aPreviewFile;
318 bool bResult = getPreviewFile(additionInfo, aPreviewFile); // info vector json data
320 if (!bResult)
322 SAL_INFO("cui.dialogs", "Couldn't get the preview file. Skipping: " << aPreviewFile);
323 return;
326 SolarMutexGuard aGuard;
328 auto newItem = std::make_shared<AdditionsItem>(m_pAdditionsDialog->m_xContentGrid.get(),
329 m_pAdditionsDialog, additionInfo);
330 m_pAdditionsDialog->m_aAdditionsItems.push_back(newItem);
331 std::shared_ptr<AdditionsItem> aCurrentItem = m_pAdditionsDialog->m_aAdditionsItems.back();
333 LoadImage(aPreviewFile, aCurrentItem);
334 m_pAdditionsDialog->m_nCurrentListItemCount++;
336 if (m_pAdditionsDialog->m_nCurrentListItemCount == m_pAdditionsDialog->m_nMaxItemCount)
338 if (m_pAdditionsDialog->m_nCurrentListItemCount
339 != m_pAdditionsDialog->m_aAllExtensionsVector.size())
340 aCurrentItem->m_xButtonShowMore->set_visible(true);
344 void SearchAndParseThread::Search()
346 m_pAdditionsDialog->m_searchOptions.searchString
347 = m_pAdditionsDialog->m_xEntrySearch->get_text();
348 utl::TextSearch textSearch(m_pAdditionsDialog->m_searchOptions);
350 size_t nIteration = 0;
351 for (auto& rInfo : m_pAdditionsDialog->m_aAllExtensionsVector)
353 if (m_pAdditionsDialog->m_nCurrentListItemCount == m_pAdditionsDialog->m_nMaxItemCount)
354 break;
356 OUString sExtensionName = rInfo.sName;
357 OUString sExtensionDescription = rInfo.sDescription;
359 if (!m_pAdditionsDialog->m_xEntrySearch->get_text().isEmpty()
360 && !textSearch.searchForward(sExtensionName)
361 && !textSearch.searchForward(sExtensionDescription))
363 continue;
365 else
367 if (nIteration >= m_pAdditionsDialog->m_nCurrentListItemCount)
368 Append(rInfo);
369 nIteration++;
372 CheckInstalledExtensions();
375 void SearchAndParseThread::CheckInstalledExtensions()
377 const uno::Sequence<uno::Sequence<uno::Reference<deployment::XPackage>>> xAllPackages
378 = m_pAdditionsDialog->getInstalledExtensions();
380 if (!xAllPackages.hasElements())
381 return;
383 OUString currentExtensionName;
385 for (auto& package : xAllPackages)
387 for (auto& extensionVersion : package)
389 if (extensionVersion.is())
391 currentExtensionName = extensionVersion->getName();
392 if (currentExtensionName.isEmpty())
393 continue;
395 m_pAdditionsDialog->m_searchOptions.searchString = currentExtensionName;
396 utl::TextSearch textSearch(m_pAdditionsDialog->m_searchOptions);
398 for (auto& rInfo : m_pAdditionsDialog->m_aAdditionsItems)
400 OUString sExtensionDownloadURL = rInfo->m_sDownloadURL;
402 if (!textSearch.searchForward(sExtensionDownloadURL))
404 continue;
406 else
408 SolarMutexGuard aGuard;
409 rInfo->m_xButtonInstall->set_sensitive(false);
410 rInfo->m_xButtonInstall->set_label(
411 CuiResId(RID_SVXSTR_ADDITIONS_INSTALLEDBUTTON));
419 void SearchAndParseThread::execute()
421 OUString sProgress;
422 if (m_bIsFirstLoading)
423 sProgress = CuiResId(RID_SVXSTR_ADDITIONS_LOADING);
424 else
425 sProgress = CuiResId(RID_SVXSTR_ADDITIONS_SEARCHING);
427 m_pAdditionsDialog->SetProgress(
428 sProgress); // Loading or searching according to being first call or not
430 if (m_bIsFirstLoading)
432 std::string sResponse = ucbGet(m_pAdditionsDialog->m_sURL);
433 parseResponse(sResponse, m_pAdditionsDialog->m_aAllExtensionsVector);
434 std::sort(m_pAdditionsDialog->m_aAllExtensionsVector.begin(),
435 m_pAdditionsDialog->m_aAllExtensionsVector.end(),
436 AdditionsDialog::sortByDownload);
437 Search();
439 else // Searching
441 Search();
444 if (!m_bExecute)
445 return;
447 SolarMutexGuard aGuard;
448 sProgress.clear();
449 m_pAdditionsDialog->SetProgress(sProgress);
452 AdditionsDialog::AdditionsDialog(weld::Window* pParent, const OUString& sAdditionsTag)
453 : GenericDialogController(pParent, "cui/ui/additionsdialog.ui", "AdditionsDialog")
454 , m_aSearchDataTimer("AdditionsDialog SearchDataTimer")
455 , m_xEntrySearch(m_xBuilder->weld_entry("entrySearch"))
456 , m_xButtonClose(m_xBuilder->weld_button("buttonClose"))
457 , m_xMenuButtonSettings(m_xBuilder->weld_menu_button("buttonGear"))
458 , m_xContentWindow(m_xBuilder->weld_scrolled_window("contentWindow"))
459 , m_xContentGrid(m_xBuilder->weld_container("contentGrid"))
460 , m_xLabelProgress(m_xBuilder->weld_label("labelProgress"))
461 , m_xGearBtn(m_xBuilder->weld_menu_button("buttonGear"))
463 m_xGearBtn->connect_selected(LINK(this, AdditionsDialog, GearHdl));
464 m_xGearBtn->set_item_active("gear_sort_voting", true);
466 m_aSearchDataTimer.SetInvokeHandler(LINK(this, AdditionsDialog, ImplUpdateDataHdl));
467 m_aSearchDataTimer.SetTimeout(EDIT_UPDATEDATA_TIMEOUT);
469 m_xEntrySearch->connect_changed(LINK(this, AdditionsDialog, SearchUpdateHdl));
470 m_xEntrySearch->connect_focus_out(LINK(this, AdditionsDialog, FocusOut_Impl));
471 m_xButtonClose->connect_clicked(LINK(this, AdditionsDialog, CloseButtonHdl));
473 m_sTag = sAdditionsTag;
474 m_nMaxItemCount = PAGE_SIZE; // Dialog initialization item count
475 m_nCurrentListItemCount = 0; // First, there is no item on the list.
477 OUString titlePrefix = CuiResId(RID_SVXSTR_ADDITIONS_DIALOG_TITLE_PREFIX);
478 if (!m_sTag.isEmpty())
480 this->set_title(titlePrefix + ": " + sAdditionsTag);
482 else
484 this->set_title(titlePrefix);
485 m_sTag = "allextensions"; // Means empty parameter
487 //FIXME: Temporary URL - v0 is not using actual api
488 OUString rURL = "https://extensions.libreoffice.org/api/v0/" + m_sTag + ".json";
489 m_sURL = rURL;
491 m_xExtensionManager
492 = deployment::ExtensionManager::get(::comphelper::getProcessComponentContext());
494 //Initialize search util
495 m_searchOptions.AlgorithmType2 = css::util::SearchAlgorithms2::ABSOLUTE;
496 m_searchOptions.transliterateFlags |= TransliterationFlags::IGNORE_CASE;
497 m_searchOptions.searchFlag |= (css::util::SearchFlags::REG_NOT_BEGINOFLINE
498 | css::util::SearchFlags::REG_NOT_ENDOFLINE);
499 m_pSearchThread = new SearchAndParseThread(this, true);
500 m_pSearchThread->launch();
503 AdditionsDialog::~AdditionsDialog()
505 if (m_pSearchThread.is())
507 m_pSearchThread->StopExecution();
508 // Release the solar mutex, so the thread is not affected by the race
509 // when it's after the m_bExecute check but before taking the solar
510 // mutex.
511 SolarMutexReleaser aReleaser;
512 m_pSearchThread->join();
516 uno::Sequence<uno::Sequence<uno::Reference<deployment::XPackage>>>
517 AdditionsDialog::getInstalledExtensions()
519 uno::Sequence<uno::Sequence<uno::Reference<deployment::XPackage>>> xAllPackages;
523 xAllPackages = m_xExtensionManager->getAllExtensions(
524 uno::Reference<task::XAbortChannel>(), uno::Reference<ucb::XCommandEnvironment>());
526 catch (const deployment::DeploymentException&)
528 TOOLS_WARN_EXCEPTION("cui.dialogs", "");
530 catch (const ucb::CommandFailedException&)
532 TOOLS_WARN_EXCEPTION("cui.dialogs", "");
534 catch (const ucb::CommandAbortedException&)
536 TOOLS_WARN_EXCEPTION("cui.dialogs", "");
538 catch (const lang::IllegalArgumentException& e)
540 css::uno::Any anyEx = cppu::getCaughtException();
541 throw css::lang::WrappedTargetRuntimeException(e.Message, e.Context, anyEx);
543 return xAllPackages;
546 void AdditionsDialog::SetProgress(const OUString& rProgress)
548 if (rProgress.isEmpty())
550 m_xLabelProgress->hide();
551 m_xButtonClose->set_sensitive(true);
553 else
555 SolarMutexGuard aGuard;
556 m_xLabelProgress->show();
557 m_xLabelProgress->set_label(rProgress);
558 m_xDialog->resize_to_request(); //TODO
562 void AdditionsDialog::ClearList()
564 // for VCL to be able to destroy bitmaps
565 SolarMutexGuard aGuard;
567 for (auto& item : this->m_aAdditionsItems)
569 item->m_xContainer->hide();
571 this->m_aAdditionsItems.clear();
574 void AdditionsDialog::RefreshUI()
576 if (m_pSearchThread.is())
577 m_pSearchThread->StopExecution();
578 ClearList();
579 m_nCurrentListItemCount = 0;
580 m_nMaxItemCount = PAGE_SIZE;
581 m_pSearchThread = new SearchAndParseThread(this, false);
582 m_pSearchThread->launch();
585 bool AdditionsDialog::sortByRating(const AdditionInfo& a, const AdditionInfo& b)
587 return a.sRating.toDouble() > b.sRating.toDouble();
590 bool AdditionsDialog::sortByComment(const AdditionInfo& a, const AdditionInfo& b)
592 return a.sCommentNumber.toUInt32() > b.sCommentNumber.toUInt32();
595 bool AdditionsDialog::sortByDownload(const AdditionInfo& a, const AdditionInfo& b)
597 return a.sDownloadNumber.toUInt32() > b.sDownloadNumber.toUInt32();
600 AdditionsItem::AdditionsItem(weld::Widget* pParent, AdditionsDialog* pParentDialog,
601 const AdditionInfo& additionInfo)
602 : m_xBuilder(Application::CreateBuilder(pParent, "cui/ui/additionsfragment.ui"))
603 , m_xContainer(m_xBuilder->weld_widget("additionsEntry"))
604 , m_xImageScreenshot(m_xBuilder->weld_image("imageScreenshot"))
605 , m_xButtonInstall(m_xBuilder->weld_button("buttonInstall"))
606 , m_xLinkButtonWebsite(m_xBuilder->weld_link_button("btnWebsite"))
607 , m_xLabelName(m_xBuilder->weld_label("lbName"))
608 , m_xLabelAuthor(m_xBuilder->weld_label("labelAuthor"))
609 , m_xLabelDesc(m_xBuilder->weld_label("labelDesc")) // no change (print description)
610 , m_xLabelDescription(m_xBuilder->weld_label("labelDescription"))
611 , m_xLabelLicense(m_xBuilder->weld_label("lbLicenseText"))
612 , m_xLabelVersion(m_xBuilder->weld_label("lbVersionText"))
613 , m_xLabelComments(m_xBuilder->weld_label("labelComments")) // no change
614 , m_xLinkButtonComments(m_xBuilder->weld_link_button("linkButtonComments"))
615 , m_xImageVoting1(m_xBuilder->weld_image("imageVoting1"))
616 , m_xImageVoting2(m_xBuilder->weld_image("imageVoting2"))
617 , m_xImageVoting3(m_xBuilder->weld_image("imageVoting3"))
618 , m_xImageVoting4(m_xBuilder->weld_image("imageVoting4"))
619 , m_xImageVoting5(m_xBuilder->weld_image("imageVoting5"))
620 , m_xLabelNoVoting(m_xBuilder->weld_label("votingLabel"))
621 , m_xImageDownloadNumber(m_xBuilder->weld_image("imageDownloadNumber"))
622 , m_xLabelDownloadNumber(m_xBuilder->weld_label("labelDownloadNumber"))
623 , m_xButtonShowMore(m_xBuilder->weld_button("buttonShowMore"))
624 , m_pParentDialog(pParentDialog)
625 , m_sDownloadURL("")
626 , m_sExtensionID("")
628 SolarMutexGuard aGuard;
630 // AdditionsItem set location
631 m_xContainer->set_grid_left_attach(0);
632 m_xContainer->set_grid_top_attach(pParentDialog->m_aAdditionsItems.size());
634 // Set maximum length of the extension title
635 OUString sExtensionName;
636 const sal_Int32 maxExtensionNameLength = 30;
638 if (additionInfo.sName.getLength() > maxExtensionNameLength)
640 OUString sShortName = additionInfo.sName.copy(0, maxExtensionNameLength - 3);
641 sExtensionName = sShortName + "...";
643 else
645 sExtensionName = additionInfo.sName;
648 m_xLabelName->set_label(sExtensionName);
650 double aExtensionRating = additionInfo.sRating.toDouble();
651 switch (std::isnan(aExtensionRating) ? 0 : int(std::clamp(aExtensionRating, 0.0, 5.0)))
653 case 5:
654 m_xImageVoting5->set_from_icon_name("cmd/sc_stars-full.png");
655 [[fallthrough]];
656 case 4:
657 m_xImageVoting4->set_from_icon_name("cmd/sc_stars-full.png");
658 [[fallthrough]];
659 case 3:
660 m_xImageVoting3->set_from_icon_name("cmd/sc_stars-full.png");
661 [[fallthrough]];
662 case 2:
663 m_xImageVoting2->set_from_icon_name("cmd/sc_stars-full.png");
664 [[fallthrough]];
665 case 1:
666 m_xImageVoting1->set_from_icon_name("cmd/sc_stars-full.png");
667 break;
670 m_xLinkButtonWebsite->set_uri(additionInfo.sExtensionURL);
671 m_xLabelDescription->set_label(additionInfo.sIntroduction);
673 if (!additionInfo.sAuthorName.equalsIgnoreAsciiCase("null"))
674 m_xLabelAuthor->set_label(additionInfo.sAuthorName);
676 m_xButtonInstall->set_label(CuiResId(RID_SVXSTR_ADDITIONS_INSTALLBUTTON));
677 m_xLabelLicense->set_label(additionInfo.sLicense);
678 m_xLabelVersion->set_label(">=" + additionInfo.sCompatibleVersion);
679 m_xLinkButtonComments->set_label(additionInfo.sCommentNumber);
680 m_xLinkButtonComments->set_uri(additionInfo.sCommentURL);
681 m_xLabelDownloadNumber->set_label(additionInfo.sDownloadNumber);
682 m_pParentDialog = pParentDialog;
683 m_sDownloadURL = additionInfo.sDownloadURL;
684 m_sExtensionID = additionInfo.sExtensionID;
686 m_xButtonShowMore->connect_clicked(LINK(this, AdditionsItem, ShowMoreHdl));
687 m_xButtonInstall->connect_clicked(LINK(this, AdditionsItem, InstallHdl));
690 bool AdditionsItem::getExtensionFile(OUString& sExtensionFile)
692 uno::Reference<ucb::XSimpleFileAccess3> xFileAccess
693 = ucb::SimpleFileAccess::create(comphelper::getProcessComponentContext());
695 // copy the extensions' files to the user's additions folder
696 OUString userFolder = "${$BRAND_BASE_DIR/" LIBO_ETC_FOLDER
697 "/" SAL_CONFIGFILE("bootstrap") "::UserInstallation}";
698 rtl::Bootstrap::expandMacros(userFolder);
699 userFolder += "/user/additions/" + m_sExtensionID + "/";
701 OUString aExtensionsFile(INetURLObject(m_sDownloadURL).getName());
702 OUString aExtensionsURL = m_sDownloadURL;
706 osl::Directory::createPath(userFolder);
708 if (!xFileAccess->exists(userFolder + aExtensionsFile))
709 ucbDownload(aExtensionsURL, userFolder, aExtensionsFile);
711 catch (const uno::Exception&)
713 return false;
715 sExtensionFile = userFolder + aExtensionsFile;
716 return true;
719 IMPL_LINK_NOARG(AdditionsDialog, ImplUpdateDataHdl, Timer*, void) { RefreshUI(); }
721 IMPL_LINK_NOARG(AdditionsDialog, SearchUpdateHdl, weld::Entry&, void)
723 m_aSearchDataTimer.Start();
726 IMPL_LINK_NOARG(AdditionsDialog, FocusOut_Impl, weld::Widget&, void)
728 if (m_aSearchDataTimer.IsActive())
730 m_aSearchDataTimer.Stop();
731 m_aSearchDataTimer.Invoke();
735 IMPL_LINK_NOARG(AdditionsDialog, CloseButtonHdl, weld::Button&, void)
737 if (m_pSearchThread.is())
738 m_pSearchThread->StopExecution();
739 this->response(RET_CLOSE);
742 IMPL_LINK_NOARG(AdditionsItem, ShowMoreHdl, weld::Button&, void)
744 this->m_xButtonShowMore->set_visible(false);
745 m_pParentDialog->m_nMaxItemCount += PAGE_SIZE;
746 if (m_pParentDialog->m_pSearchThread.is())
747 m_pParentDialog->m_pSearchThread->StopExecution();
748 m_pParentDialog->m_pSearchThread = new SearchAndParseThread(m_pParentDialog, false);
749 m_pParentDialog->m_pSearchThread->launch();
752 IMPL_LINK_NOARG(AdditionsItem, InstallHdl, weld::Button&, void)
754 m_xButtonInstall->set_label(CuiResId(RID_SVXSTR_ADDITIONS_INSTALLING));
755 m_xButtonInstall->set_sensitive(false);
756 OUString aExtensionFile;
757 bool bResult = getExtensionFile(aExtensionFile); // info vector json data
759 if (!bResult)
761 m_xButtonInstall->set_label(CuiResId(RID_SVXSTR_ADDITIONS_INSTALLBUTTON));
762 m_xButtonInstall->set_sensitive(true);
764 SAL_INFO("cui.dialogs", "Couldn't get the extension file.");
765 return;
768 rtl::Reference<TmpRepositoryCommandEnv> pCmdEnv = new TmpRepositoryCommandEnv();
769 uno::Reference<task::XAbortChannel> xAbortChannel;
772 m_pParentDialog->m_xExtensionManager->addExtension(
773 aExtensionFile, uno::Sequence<beans::NamedValue>(), "user", xAbortChannel, pCmdEnv);
774 m_xButtonInstall->set_label(CuiResId(RID_SVXSTR_ADDITIONS_INSTALLEDBUTTON));
776 catch (const ucb::CommandFailedException)
778 TOOLS_WARN_EXCEPTION("cui.dialogs", "");
779 m_xButtonInstall->set_label(CuiResId(RID_SVXSTR_ADDITIONS_INSTALLBUTTON));
780 m_xButtonInstall->set_sensitive(true);
782 catch (const ucb::CommandAbortedException)
784 TOOLS_WARN_EXCEPTION("cui.dialogs", "");
785 m_xButtonInstall->set_label(CuiResId(RID_SVXSTR_ADDITIONS_INSTALLBUTTON));
786 m_xButtonInstall->set_sensitive(true);
788 catch (const deployment::DeploymentException)
790 TOOLS_WARN_EXCEPTION("cui.dialogs", "");
791 m_xButtonInstall->set_label(CuiResId(RID_SVXSTR_ADDITIONS_INSTALLBUTTON));
792 m_xButtonInstall->set_sensitive(true);
794 catch (const lang::IllegalArgumentException)
796 TOOLS_WARN_EXCEPTION("cui.dialogs", "");
797 m_xButtonInstall->set_label(CuiResId(RID_SVXSTR_ADDITIONS_INSTALLBUTTON));
798 m_xButtonInstall->set_sensitive(true);
800 catch (const css::uno::Exception)
802 TOOLS_WARN_EXCEPTION("cui.dialogs", "");
803 m_xButtonInstall->set_label(CuiResId(RID_SVXSTR_ADDITIONS_INSTALLBUTTON));
804 m_xButtonInstall->set_sensitive(true);
808 // TmpRepositoryCommandEnv
810 TmpRepositoryCommandEnv::TmpRepositoryCommandEnv() {}
812 TmpRepositoryCommandEnv::~TmpRepositoryCommandEnv() {}
813 // XCommandEnvironment
815 uno::Reference<task::XInteractionHandler> TmpRepositoryCommandEnv::getInteractionHandler()
817 return this;
820 uno::Reference<ucb::XProgressHandler> TmpRepositoryCommandEnv::getProgressHandler() { return this; }
822 // XInteractionHandler
823 void TmpRepositoryCommandEnv::handle(uno::Reference<task::XInteractionRequest> const& xRequest)
825 OSL_ASSERT(xRequest->getRequest().getValueTypeClass() == uno::TypeClass_EXCEPTION);
827 bool approve = true;
829 // select:
830 uno::Sequence<Reference<task::XInteractionContinuation>> conts(xRequest->getContinuations());
831 Reference<task::XInteractionContinuation> const* pConts = conts.getConstArray();
832 sal_Int32 len = conts.getLength();
833 for (sal_Int32 pos = 0; pos < len; ++pos)
835 if (approve)
837 uno::Reference<task::XInteractionApprove> xInteractionApprove(pConts[pos],
838 uno::UNO_QUERY);
839 if (xInteractionApprove.is())
841 xInteractionApprove->select();
842 // don't query again for ongoing continuations:
843 approve = false;
849 // XProgressHandler
850 void TmpRepositoryCommandEnv::push(uno::Any const& /*Status*/) {}
852 void TmpRepositoryCommandEnv::update(uno::Any const& /*Status */) {}
854 void TmpRepositoryCommandEnv::pop() {}
856 IMPL_LINK(AdditionsDialog, GearHdl, const OString&, rIdent, void)
858 if (rIdent == "gear_sort_voting")
860 std::sort(m_aAllExtensionsVector.begin(), m_aAllExtensionsVector.end(), sortByRating);
862 else if (rIdent == "gear_sort_comments")
864 std::sort(m_aAllExtensionsVector.begin(), m_aAllExtensionsVector.end(), sortByComment);
866 else if (rIdent == "gear_sort_downloads")
868 std::sort(m_aAllExtensionsVector.begin(), m_aAllExtensionsVector.end(), sortByDownload);
870 // After the sorting, UI will be refreshed to update extension list.
871 RefreshUI();
873 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */