1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
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/.
11 #include <sal/config.h>
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>
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
;
79 // Gets the content of the given URL and returns as a standard string
80 std::string
ucbGet(const OUString
& rURL
)
84 auto const s
= utl::UcbStreamHelper::CreateStream(rURL
, StreamMode::STD_READ
);
87 SAL_WARN("cui.dialogs", "CreateStream <" << rURL
<< "> failed");
90 std::string response_body
;
94 auto const n
= s
->ReadBytes(buf
, sizeof buf
);
95 response_body
.append(buf
, n
);
99 SAL_WARN("cui.dialogs", "Reading <" << rURL
<< "> failed with " << s
->GetError());
102 return response_body
;
104 catch (css::uno::Exception
&)
106 TOOLS_WARN_EXCEPTION("cui.dialogs", "Download failed");
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())
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");
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
);
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
),
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
),
171 std::string_view(arrayElement
.child("screenshotURL").string_value().get()),
172 RTL_TEXTENCODING_UTF8
),
175 arrayElement
.child("extensionIntroduction").string_value().get()),
176 RTL_TEXTENCODING_UTF8
),
179 arrayElement
.child("extensionDescription").string_value().get()),
180 RTL_TEXTENCODING_UTF8
),
181 OStringToOUString(std::string_view(arrayElement
.child("releases")
183 .child("compatibility")
186 RTL_TEXTENCODING_UTF8
),
187 OStringToOUString(std::string_view(arrayElement
.child("releases")
189 .child("releaseName")
192 RTL_TEXTENCODING_UTF8
),
193 OStringToOUString(std::string_view(arrayElement
.child("releases")
198 RTL_TEXTENCODING_UTF8
),
200 std::string_view(arrayElement
.child("commentNumber").string_value().get()),
201 RTL_TEXTENCODING_UTF8
),
203 std::string_view(arrayElement
.child("commentURL").string_value().get()),
204 RTL_TEXTENCODING_UTF8
),
206 std::string_view(arrayElement
.child("rating").string_value().get()),
207 RTL_TEXTENCODING_UTF8
),
209 std::string_view(arrayElement
.child("downloadNumber").string_value().get()),
210 RTL_TEXTENCODING_UTF8
),
211 OStringToOUString(std::string_view(arrayElement
.child("releases")
213 .child("downloadURL")
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
&)
255 sPreviewFile
= userFolder
+ aPreviewFile
;
259 void LoadImage(const OUString
& rPreviewFile
, std::shared_ptr
<AdditionsItem
> pCurrentItem
)
261 const sal_Int8 Margin
= 6;
263 SolarMutexGuard aGuard
;
265 GraphicFilter aFilter
;
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());
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
));
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
)
307 , m_bIsFirstLoading(isFirstLoading
)
311 SearchAndParseThread::~SearchAndParseThread() {}
313 void SearchAndParseThread::Append(AdditionInfo
& additionInfo
)
317 OUString aPreviewFile
;
318 bool bResult
= getPreviewFile(additionInfo
, aPreviewFile
); // info vector json data
322 SAL_INFO("cui.dialogs", "Couldn't get the preview file. Skipping: " << aPreviewFile
);
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
)
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
))
367 if (nIteration
>= m_pAdditionsDialog
->m_nCurrentListItemCount
)
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())
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())
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
))
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()
422 if (m_bIsFirstLoading
)
423 sProgress
= CuiResId(RID_SVXSTR_ADDITIONS_LOADING
);
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
);
447 SolarMutexGuard aGuard
;
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
);
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";
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
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
);
546 void AdditionsDialog::SetProgress(const OUString
& rProgress
)
548 if (rProgress
.isEmpty())
550 m_xLabelProgress
->hide();
551 m_xButtonClose
->set_sensitive(true);
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();
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
)
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
+ "...";
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)))
654 m_xImageVoting5
->set_from_icon_name("cmd/sc_stars-full.png");
657 m_xImageVoting4
->set_from_icon_name("cmd/sc_stars-full.png");
660 m_xImageVoting3
->set_from_icon_name("cmd/sc_stars-full.png");
663 m_xImageVoting2
->set_from_icon_name("cmd/sc_stars-full.png");
666 m_xImageVoting1
->set_from_icon_name("cmd/sc_stars-full.png");
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
&)
715 sExtensionFile
= userFolder
+ aExtensionsFile
;
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
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.");
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()
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
);
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
)
837 uno::Reference
<task::XInteractionApprove
> xInteractionApprove(pConts
[pos
],
839 if (xInteractionApprove
.is())
841 xInteractionApprove
->select();
842 // don't query again for ongoing continuations:
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.
873 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */