/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
* This file is part of the LibreOffice project.
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*
*/
#include <sal/config.h>
#include <algorithm>
#include <cmath>
#include <config_folders.h>
#include <AdditionsDialog.hxx>
#include <dialmgr.hxx>
#include <strings.hrc>
#include <o3tl/test_info.hxx>
#include <sal/log.hxx>
#include <com/sun/star/graphic/GraphicProvider.hpp>
#include <com/sun/star/graphic/XGraphicProvider.hpp>
#include <com/sun/star/ucb/NameClash.hpp>
#include <com/sun/star/ucb/SimpleFileAccess.hpp>
#include <osl/file.hxx>
#include <rtl/bootstrap.hxx>
#include <tools/urlobj.hxx>
#include <tools/stream.hxx>
#include <comphelper/diagnose_ex.hxx>
#include <comphelper/processfactory.hxx>
#include <vcl/virdev.hxx>
#include <vcl/svapp.hxx>
#include <vcl/graphicfilter.hxx>
#include <cppuhelper/exc_hlp.hxx>
#include <com/sun/star/util/SearchFlags.hpp>
#include <com/sun/star/util/SearchAlgorithms2.hpp>
#include <unotools/textsearch.hxx>
#include <unotools/ucbstreamhelper.hxx>
#include <ucbhelper/content.hxx>
#include <com/sun/star/deployment/DeploymentException.hpp>
#include <com/sun/star/deployment/ExtensionManager.hpp>
#include <com/sun/star/lang/WrappedTargetRuntimeException.hpp>
#include <com/sun/star/ucb/CommandAbortedException.hpp>
#include <com/sun/star/ucb/CommandFailedException.hpp>
#include <com/sun/star/task/XInteractionApprove.hpp>
#include <orcus/json_document_tree.hpp>
#include <orcus/json_parser.hpp>
#include <orcus/config.hpp>
#include <bitmaps.hlst>
#define PAGE_SIZE 30
using namespace css;
using ::com::sun::star::uno::Reference;
using ::com::sun::star::uno::Exception;
using ::com::sun::star::uno::Sequence;
using namespace com::sun::star;
using namespace ::com::sun::star::uno;
using namespace ::com::sun::star::ucb;
using namespace ::com::sun::star::beans;
namespace
{
// Gets the content of the given URL and returns as a standard string
std::string ucbGet(const OUString& rURL, const css::uno::Reference<css::awt::XWindow>& xParentWin)
{
try
{
auto const s
= utl::UcbStreamHelper::CreateStream(rURL, StreamMode::STD_READ, xParentWin, false);
if (!s)
{
SAL_WARN("cui.dialogs", "CreateStream <" << rURL << "> failed");
return {};
}
std::string response_body;
do
{
char buf[4096];
auto const n = s->ReadBytes(buf, sizeof buf);
response_body.append(buf, n);
} while (s->good());
if (s->bad())
{
SAL_WARN("cui.dialogs", "Reading <" << rURL << "> failed with " << s->GetError());
return {};
}
return response_body;
}
catch (css::uno::Exception&)
{
TOOLS_WARN_EXCEPTION("cui.dialogs", "Download failed");
return {};
}
}
// Downloads and saves the file at the given rURL to a local path (sFolderURL/fileName)
void ucbDownload(const OUString& rURL, const OUString& sFolderURL, const OUString& fileName)
{
try
{
ucbhelper::Content(sFolderURL, {}, comphelper::getProcessComponentContext())
.transferContent(ucbhelper::Content(rURL, {}, comphelper::getProcessComponentContext()),
ucbhelper::InsertOperation::Copy, fileName,
css::ucb::NameClash::OVERWRITE);
}
catch (css::uno::Exception&)
{
TOOLS_WARN_EXCEPTION("cui.dialogs", "Download failed");
}
}
void parseResponse(const std::string& rResponse, std::vector<AdditionInfo>& aAdditions)
{
orcus::json::document_tree aJsonDoc;
orcus::json_config aConfig;
if (rResponse.empty())
return;
try
{
aJsonDoc.load(rResponse, aConfig);
}
catch (const orcus::parse_error&)
{
TOOLS_WARN_EXCEPTION("cui.dialogs", "Invalid JSON file from the extensions API");
return;
}
auto aDocumentRoot = aJsonDoc.get_document_root();
if (aDocumentRoot.type() != orcus::json::node_t::object)
{
SAL_WARN("cui.dialogs", "invalid root entries: " << rResponse);
return;
}
auto resultsArray = aDocumentRoot.child("extension");
for (size_t i = 0; i < resultsArray.child_count(); ++i)
{
auto arrayElement = resultsArray.child(i);
try
{
AdditionInfo aNewAddition = {
OStringToOUString(arrayElement.child("id").string_value(), RTL_TEXTENCODING_UTF8),
OStringToOUString(arrayElement.child("name").string_value(), RTL_TEXTENCODING_UTF8),
OStringToOUString(arrayElement.child("author").string_value(),
RTL_TEXTENCODING_UTF8),
OStringToOUString(arrayElement.child("url").string_value(), RTL_TEXTENCODING_UTF8),
OStringToOUString(arrayElement.child("screenshotURL").string_value(),
RTL_TEXTENCODING_UTF8),
OStringToOUString(arrayElement.child("extensionIntroduction").string_value(),
RTL_TEXTENCODING_UTF8),
OStringToOUString(arrayElement.child("extensionDescription").string_value(),
RTL_TEXTENCODING_UTF8),
OStringToOUString(
arrayElement.child("releases").child(0).child("compatibility").string_value(),
RTL_TEXTENCODING_UTF8),
OStringToOUString(
arrayElement.child("releases").child(0).child("releaseName").string_value(),
RTL_TEXTENCODING_UTF8),
OStringToOUString(
arrayElement.child("releases").child(0).child("license").string_value(),
RTL_TEXTENCODING_UTF8),
OStringToOUString(arrayElement.child("commentNumber").string_value(),
RTL_TEXTENCODING_UTF8),
OStringToOUString(arrayElement.child("commentURL").string_value(),
RTL_TEXTENCODING_UTF8),
OStringToOUString(arrayElement.child("rating").string_value(),
RTL_TEXTENCODING_UTF8),
OStringToOUString(arrayElement.child("downloadNumber").string_value(),
RTL_TEXTENCODING_UTF8),
OStringToOUString(
arrayElement.child("releases").child(0).child("downloadURL").string_value(),
RTL_TEXTENCODING_UTF8)
};
aAdditions.push_back(aNewAddition);
}
catch (orcus::json::document_error& e)
{
// This usually happens when one of the values is null (type() == orcus::json::node_t::null)
// TODO: Allow null values in additions.
SAL_WARN("cui.dialogs", "Additions JSON parse error: " << e.what());
}
}
}
bool getPreviewFile(const AdditionInfo& aAdditionInfo, OUString& sPreviewFile)
{
uno::Reference<ucb::XSimpleFileAccess3> xFileAccess
= ucb::SimpleFileAccess::create(comphelper::getProcessComponentContext());
// copy the images to the user's additions folder
OUString userFolder = u"${$BRAND_BASE_DIR/" LIBO_ETC_FOLDER
"/" SAL_CONFIGFILE("bootstrap") "::UserInstallation}"_ustr;
rtl::Bootstrap::expandMacros(userFolder);
userFolder += "/user/additions/" + aAdditionInfo.sExtensionID + "/";
OUString aPreviewFile(INetURLObject(aAdditionInfo.sScreenshotURL).getName());
OUString aPreviewURL = aAdditionInfo.sScreenshotURL;
try
{
osl::Directory::createPath(userFolder);
if (!xFileAccess->exists(userFolder + aPreviewFile))
ucbDownload(aPreviewURL, userFolder, aPreviewFile);
}
catch (const uno::Exception&)
{
return false;
}
sPreviewFile = userFolder + aPreviewFile;
return true;
}
void LoadImage(std::u16string_view rPreviewFile, std::shared_ptr<AdditionsItem> pCurrentItem)
{
const sal_Int8 Margin = 6;
SolarMutexGuard aGuard;
GraphicFilter aFilter;
Graphic aGraphic;
INetURLObject aURLObj(rPreviewFile);
// for VCL to be able to create bitmaps / do visual changes in the thread
aFilter.ImportGraphic(aGraphic, aURLObj);
BitmapEx aBmp = aGraphic.GetBitmapEx();
Size aBmpSize = aBmp.GetSizePixel();
Size aThumbSize(pCurrentItem->m_xImageScreenshot->get_size_request());
if (!aBmp.IsEmpty())
{
double aScale;
if (aBmpSize.Width() > aThumbSize.Width() - 2 * Margin)
{
aScale = static_cast<double>(aBmpSize.Width()) / (aThumbSize.Width() - 2 * Margin);
aBmp.Scale(Size(aBmpSize.Width() / aScale, aBmpSize.Height() / aScale));
}
else if (aBmpSize.Height() > aThumbSize.Height() - 2 * Margin)
{
aScale = static_cast<double>(aBmpSize.Height()) / (aThumbSize.Height() - 2 * Margin);
aBmp.Scale(Size(aBmpSize.Width() / aScale, aBmpSize.Height() / aScale));
};
aBmpSize = aBmp.GetSizePixel();
}
ScopedVclPtr<VirtualDevice> xVirDev = pCurrentItem->m_xImageScreenshot->create_virtual_device();
xVirDev->SetOutputSizePixel(aThumbSize);
//white background since images come with a white border
xVirDev->SetBackground(Wallpaper(COL_WHITE));
xVirDev->Erase();
xVirDev->DrawBitmapEx(Point(aThumbSize.Width() / 2 - aBmpSize.Width() / 2, Margin), aBmp);
pCurrentItem->m_xImageScreenshot->set_image(xVirDev.get());
xVirDev.disposeAndClear();
}
} // End of the anonymous namespace
SearchAndParseThread::SearchAndParseThread(AdditionsDialog* pDialog, const bool isFirstLoading)
: Thread("cuiAdditionsSearchThread")
, m_pAdditionsDialog(pDialog)
, m_bExecute(true)
, m_bIsFirstLoading(isFirstLoading)
{
// if we are running a UITest, e.g. UITest_sw_options then
// don't attempt to downloading anything
m_bUITest = o3tl::IsRunningUITest();
}
SearchAndParseThread::~SearchAndParseThread() {}
void SearchAndParseThread::Append(AdditionInfo& additionInfo)
{
if (!m_bExecute)
return;
OUString aPreviewFile;
bool bResult
= !m_bUITest && getPreviewFile(additionInfo, aPreviewFile); // info vector json data
if (!bResult)
{
SAL_INFO("cui.dialogs", "Couldn't get the preview file. Skipping: " << aPreviewFile);
return;
}
SolarMutexGuard aGuard;
auto newItem = std::make_shared<AdditionsItem>(m_pAdditionsDialog->m_xContentGrid.get(),
m_pAdditionsDialog, additionInfo);
m_pAdditionsDialog->m_aAdditionsItems.push_back(newItem);
std::shared_ptr<AdditionsItem> aCurrentItem = m_pAdditionsDialog->m_aAdditionsItems.back();
LoadImage(aPreviewFile, aCurrentItem);
m_pAdditionsDialog->m_nCurrentListItemCount++;
if (m_pAdditionsDialog->m_nCurrentListItemCount == m_pAdditionsDialog->m_nMaxItemCount)
{
if (m_pAdditionsDialog->m_nCurrentListItemCount
!= m_pAdditionsDialog->m_aAllExtensionsVector.size())
aCurrentItem->m_xButtonShowMore->set_visible(true);
}
}
void SearchAndParseThread::Search()
{
m_pAdditionsDialog->m_searchOptions.searchString
= m_pAdditionsDialog->m_xEntrySearch->get_text();
utl::TextSearch textSearch(m_pAdditionsDialog->m_searchOptions);
size_t nIteration = 0;
for (auto& rInfo : m_pAdditionsDialog->m_aAllExtensionsVector)
{
if (m_pAdditionsDialog->m_nCurrentListItemCount == m_pAdditionsDialog->m_nMaxItemCount)
break;
OUString sExtensionName = rInfo.sName;
OUString sExtensionDescription = rInfo.sDescription;
if (!m_pAdditionsDialog->m_xEntrySearch->get_text().isEmpty()
&& !textSearch.searchForward(sExtensionName)
&& !textSearch.searchForward(sExtensionDescription))
{
continue;
}
else
{
if (nIteration >= m_pAdditionsDialog->m_nCurrentListItemCount)
Append(rInfo);
nIteration++;
}
}
CheckInstalledExtensions();
}
void SearchAndParseThread::CheckInstalledExtensions()
{
const uno::Sequence<uno::Sequence<uno::Reference<deployment::XPackage>>> xAllPackages
= m_pAdditionsDialog->getInstalledExtensions();
if (!xAllPackages.hasElements())
return;
OUString currentExtensionName;
for (auto& package : xAllPackages)
{
for (auto& extensionVersion : package)
{
if (extensionVersion.is())
{
currentExtensionName = extensionVersion->getName();
if (currentExtensionName.isEmpty())
continue;
m_pAdditionsDialog->m_searchOptions.searchString = currentExtensionName;
utl::TextSearch textSearch(m_pAdditionsDialog->m_searchOptions);
for (auto& rInfo : m_pAdditionsDialog->m_aAdditionsItems)
{
OUString sExtensionDownloadURL = rInfo->m_sDownloadURL;
if (!textSearch.searchForward(sExtensionDownloadURL))
{
continue;
}
else
{
SolarMutexGuard aGuard;
rInfo->m_xButtonInstall->set_sensitive(false);
rInfo->m_xButtonInstall->set_label(
CuiResId(RID_CUISTR_ADDITIONS_INSTALLEDBUTTON));
}
}
}
}
}
}
void SearchAndParseThread::execute()
{
OUString sProgress;
if (m_bIsFirstLoading)
sProgress = CuiResId(RID_CUISTR_ADDITIONS_LOADING);
else
sProgress = CuiResId(RID_CUISTR_ADDITIONS_SEARCHING);
m_pAdditionsDialog->SetProgress(
sProgress); // Loading or searching according to being first call or not
if (m_bIsFirstLoading)
{
const auto pDialog = m_pAdditionsDialog->getDialog();
std::string sResponse = !m_bUITest ? ucbGet(m_pAdditionsDialog->m_sURL,
pDialog ? pDialog->GetXWindow() : nullptr)
: "";
parseResponse(sResponse, m_pAdditionsDialog->m_aAllExtensionsVector);
std::sort(m_pAdditionsDialog->m_aAllExtensionsVector.begin(),
m_pAdditionsDialog->m_aAllExtensionsVector.end(),
AdditionsDialog::sortByDownload);
Search();
}
else // Searching
{
Search();
}
if (!m_bExecute)
return;
SolarMutexGuard aGuard;
sProgress.clear();
m_pAdditionsDialog->SetProgress(sProgress);
}
AdditionsDialog::AdditionsDialog(weld::Window* pParent, const OUString& sAdditionsTag)
: GenericDialogController(pParent, u"cui/ui/additionsdialog.ui"_ustr, u"AdditionsDialog"_ustr)
, m_aSearchDataTimer("AdditionsDialog SearchDataTimer")
, m_xEntrySearch(m_xBuilder->weld_entry(u"entrySearch"_ustr))
, m_xButtonClose(m_xBuilder->weld_button(u"buttonClose"_ustr))
, m_xContentWindow(m_xBuilder->weld_scrolled_window(u"contentWindow"_ustr))
, m_xContentGrid(m_xBuilder->weld_container(u"contentGrid"_ustr))
, m_xLabelProgress(m_xBuilder->weld_label(u"labelProgress"_ustr))
, m_xGearBtn(m_xBuilder->weld_menu_button(u"buttonGear"_ustr))
{
m_xGearBtn->connect_selected(LINK(this, AdditionsDialog, GearHdl));
m_xGearBtn->set_item_active(u"gear_sort_voting"_ustr, true);
m_aSearchDataTimer.SetInvokeHandler(LINK(this, AdditionsDialog, ImplUpdateDataHdl));
m_aSearchDataTimer.SetTimeout(EDIT_UPDATEDATA_TIMEOUT);
m_xEntrySearch->connect_changed(LINK(this, AdditionsDialog, SearchUpdateHdl));
m_xEntrySearch->connect_focus_out(LINK(this, AdditionsDialog, FocusOut_Impl));
m_xButtonClose->connect_clicked(LINK(this, AdditionsDialog, CloseButtonHdl));
m_sTag = sAdditionsTag;
m_nMaxItemCount = PAGE_SIZE; // Dialog initialization item count
m_nCurrentListItemCount = 0; // First, there is no item on the list.
OUString titlePrefix = CuiResId(RID_CUISTR_ADDITIONS_DIALOG_TITLE_PREFIX);
if (!m_sTag.isEmpty())
{ // tdf#142564 localize extension category names
OUString sDialogTitle = u""_ustr;
if (sAdditionsTag == "Templates")
{
sDialogTitle = CuiResId(RID_CUISTR_ADDITIONS_TEMPLATES);
}
else if (sAdditionsTag == "Dictionary")
{
sDialogTitle = CuiResId(RID_CUISTR_ADDITIONS_DICTIONARY);
}
else if (sAdditionsTag == "Gallery")
{
sDialogTitle = CuiResId(RID_CUISTR_ADDITIONS_GALLERY);
}
else if (sAdditionsTag == "Icons")
{
sDialogTitle = CuiResId(RID_CUISTR_ADDITIONS_ICONS);
}
else if (sAdditionsTag == "Color Palette")
{
sDialogTitle = CuiResId(RID_CUISTR_ADDITIONS_PALETTES);
}
else if (sAdditionsTag == "Themes")
{
sDialogTitle = CuiResId(RID_CUISTR_ADDITIONS_THEMES);
}
this->set_title(sDialogTitle);
}
else
{
this->set_title(titlePrefix);
m_sTag = "allextensions"; // Means empty parameter
}
OUString sEncodedURLPart = INetURLObject::encode(m_sTag, INetURLObject::PART_PCHAR,
INetURLObject::EncodeMechanism::All);
//FIXME: Temporary URL - v0 is not using actual api
m_sURL = "https://extensions.libreoffice.org/api/v0/" + sEncodedURLPart + ".json";
m_xExtensionManager
= deployment::ExtensionManager::get(::comphelper::getProcessComponentContext());
//Initialize search util
m_searchOptions.AlgorithmType2 = css::util::SearchAlgorithms2::ABSOLUTE;
m_searchOptions.transliterateFlags |= TransliterationFlags::IGNORE_CASE;
m_searchOptions.searchFlag |= (css::util::SearchFlags::REG_NOT_BEGINOFLINE
| css::util::SearchFlags::REG_NOT_ENDOFLINE);
m_pSearchThread = new SearchAndParseThread(this, true);
m_pSearchThread->launch();
}
AdditionsDialog::~AdditionsDialog()
{
if (m_pSearchThread.is())
{
m_pSearchThread->StopExecution();
// Release the solar mutex, so the thread is not affected by the race
// when it's after the m_bExecute check but before taking the solar
// mutex.
SolarMutexReleaser aReleaser;
m_pSearchThread->join();
}
}
uno::Sequence<uno::Sequence<uno::Reference<deployment::XPackage>>>
AdditionsDialog::getInstalledExtensions()
{
uno::Sequence<uno::Sequence<uno::Reference<deployment::XPackage>>> xAllPackages;
try
{
xAllPackages = m_xExtensionManager->getAllExtensions(
uno::Reference<task::XAbortChannel>(), uno::Reference<ucb::XCommandEnvironment>());
}
catch (const deployment::DeploymentException&)
{
TOOLS_WARN_EXCEPTION("cui.dialogs", "");
}
catch (const ucb::CommandFailedException&)
{
TOOLS_WARN_EXCEPTION("cui.dialogs", "");
}
catch (const ucb::CommandAbortedException&)
{
TOOLS_WARN_EXCEPTION("cui.dialogs", "");
}
catch (const lang::IllegalArgumentException& e)
{
css::uno::Any anyEx = cppu::getCaughtException();
throw css::lang::WrappedTargetRuntimeException(e.Message, e.Context, anyEx);
}
return xAllPackages;
}
void AdditionsDialog::SetProgress(const OUString& rProgress)
{
if (rProgress.isEmpty())
{
m_xLabelProgress->hide();
m_xButtonClose->set_sensitive(true);
}
else
{
SolarMutexGuard aGuard;
m_xLabelProgress->show();
m_xLabelProgress->set_label(rProgress);
m_xDialog->resize_to_request(); //TODO
}
}
void AdditionsDialog::ClearList()
{
// for VCL to be able to destroy bitmaps
SolarMutexGuard aGuard;
for (auto& item : this->m_aAdditionsItems)
{
item->m_xContainer->hide();
}
this->m_aAdditionsItems.clear();
}
void AdditionsDialog::RefreshUI()
{
if (m_pSearchThread.is())
m_pSearchThread->StopExecution();
ClearList();
m_nCurrentListItemCount = 0;
m_nMaxItemCount = PAGE_SIZE;
m_pSearchThread = new SearchAndParseThread(this, false);
m_pSearchThread->launch();
}
bool AdditionsDialog::sortByRating(const AdditionInfo& a, const AdditionInfo& b)
{
return a.sRating.toDouble() > b.sRating.toDouble();
}
bool AdditionsDialog::sortByComment(const AdditionInfo& a, const AdditionInfo& b)
{
return a.sCommentNumber.toUInt32() > b.sCommentNumber.toUInt32();
}
bool AdditionsDialog::sortByDownload(const AdditionInfo& a, const AdditionInfo& b)
{
return a.sDownloadNumber.toUInt32() > b.sDownloadNumber.toUInt32();
}
AdditionsItem::AdditionsItem(weld::Widget* pParent, AdditionsDialog* pParentDialog,
const AdditionInfo& additionInfo)
: m_xBuilder(Application::CreateBuilder(pParent, u"cui/ui/additionsfragment.ui"_ustr))
, m_xContainer(m_xBuilder->weld_widget(u"additionsEntry"_ustr))
, m_xImageScreenshot(m_xBuilder->weld_image(u"imageScreenshot"_ustr))
, m_xButtonInstall(m_xBuilder->weld_button(u"buttonInstall"_ustr))
, m_xLinkButtonWebsite(m_xBuilder->weld_link_button(u"btnWebsite"_ustr))
, m_xLabelName(m_xBuilder->weld_label(u"lbName"_ustr))
, m_xLabelAuthor(m_xBuilder->weld_label(u"labelAuthor"_ustr))
, m_xLabelDescription(m_xBuilder->weld_label(u"labelDescription"_ustr))
, m_xLabelLicense(m_xBuilder->weld_label(u"lbLicenseText"_ustr))
, m_xLabelVersion(m_xBuilder->weld_label(u"lbVersionText"_ustr))
, m_xLinkButtonComments(m_xBuilder->weld_link_button(u"linkButtonComments"_ustr))
, m_xImageVoting1(m_xBuilder->weld_image(u"imageVoting1"_ustr))
, m_xImageVoting2(m_xBuilder->weld_image(u"imageVoting2"_ustr))
, m_xImageVoting3(m_xBuilder->weld_image(u"imageVoting3"_ustr))
, m_xImageVoting4(m_xBuilder->weld_image(u"imageVoting4"_ustr))
, m_xImageVoting5(m_xBuilder->weld_image(u"imageVoting5"_ustr))
, m_xLabelDownloadNumber(m_xBuilder->weld_label(u"labelDownloadNumber"_ustr))
, m_xButtonShowMore(m_xBuilder->weld_button(u"buttonShowMore"_ustr))
, m_pParentDialog(pParentDialog)
, m_sDownloadURL(u""_ustr)
, m_sExtensionID(u""_ustr)
{
SolarMutexGuard aGuard;
// AdditionsItem set location
m_xContainer->set_grid_left_attach(0);
m_xContainer->set_grid_top_attach(pParentDialog->m_aAdditionsItems.size());
// Set maximum length of the extension title
OUString sExtensionName;
const sal_Int32 maxExtensionNameLength = 30;
if (additionInfo.sName.getLength() > maxExtensionNameLength)
{
std::u16string_view sShortName = additionInfo.sName.subView(0, maxExtensionNameLength - 3);
sExtensionName = OUString::Concat(sShortName) + "...";
}
else
{
sExtensionName = additionInfo.sName;
}
m_xLabelName->set_label(sExtensionName);
double aExtensionRating = additionInfo.sRating.toDouble();
switch (std::isnan(aExtensionRating) ? 0 : int(std::clamp(aExtensionRating, 0.0, 5.0)))
{
case 5:
m_xImageVoting5->set_from_icon_name(RID_SVXBMP_STARS_FULL);
[[fallthrough]];
case 4:
m_xImageVoting4->set_from_icon_name(RID_SVXBMP_STARS_FULL);
[[fallthrough]];
case 3:
m_xImageVoting3->set_from_icon_name(RID_SVXBMP_STARS_FULL);
[[fallthrough]];
case 2:
m_xImageVoting2->set_from_icon_name(RID_SVXBMP_STARS_FULL);
[[fallthrough]];
case 1:
m_xImageVoting1->set_from_icon_name(RID_SVXBMP_STARS_FULL);
break;
}
m_xLinkButtonWebsite->set_uri(additionInfo.sExtensionURL);
m_xLabelDescription->set_label(additionInfo.sIntroduction);
if (!additionInfo.sAuthorName.equalsIgnoreAsciiCase("null"))
m_xLabelAuthor->set_label(additionInfo.sAuthorName);
m_xButtonInstall->set_label(CuiResId(RID_CUISTR_ADDITIONS_INSTALLBUTTON));
m_xLabelLicense->set_label(additionInfo.sLicense);
m_xLabelVersion->set_label(">=" + additionInfo.sCompatibleVersion);
m_xLinkButtonComments->set_label(additionInfo.sCommentNumber);
m_xLinkButtonComments->set_uri(additionInfo.sCommentURL);
m_xLabelDownloadNumber->set_label(additionInfo.sDownloadNumber);
m_pParentDialog = pParentDialog;
m_sDownloadURL = additionInfo.sDownloadURL;
m_sExtensionID = additionInfo.sExtensionID;
m_xButtonShowMore->connect_clicked(LINK(this, AdditionsItem, ShowMoreHdl));
m_xButtonInstall->connect_clicked(LINK(this, AdditionsItem, InstallHdl));
}
bool AdditionsItem::getExtensionFile(OUString& sExtensionFile)
{
uno::Reference<ucb::XSimpleFileAccess3> xFileAccess
= ucb::SimpleFileAccess::create(comphelper::getProcessComponentContext());
// copy the extensions' files to the user's additions folder
OUString userFolder = u"${$BRAND_BASE_DIR/" LIBO_ETC_FOLDER
"/" SAL_CONFIGFILE("bootstrap") "::UserInstallation}"_ustr;
rtl::Bootstrap::expandMacros(userFolder);
userFolder += "/user/additions/" + m_sExtensionID + "/";
OUString aExtensionsFile(INetURLObject(m_sDownloadURL).getName());
OUString aExtensionsURL = m_sDownloadURL;
try
{
osl::Directory::createPath(userFolder);
if (!xFileAccess->exists(userFolder + aExtensionsFile))
ucbDownload(aExtensionsURL, userFolder, aExtensionsFile);
}
catch (const uno::Exception&)
{
return false;
}
sExtensionFile = userFolder + aExtensionsFile;
return true;
}
IMPL_LINK_NOARG(AdditionsDialog, ImplUpdateDataHdl, Timer*, void) { RefreshUI(); }
IMPL_LINK_NOARG(AdditionsDialog, SearchUpdateHdl, weld::Entry&, void)
{
m_aSearchDataTimer.Start();
}
IMPL_LINK_NOARG(AdditionsDialog, FocusOut_Impl, weld::Widget&, void)
{
if (m_aSearchDataTimer.IsActive())
{
m_aSearchDataTimer.Stop();
m_aSearchDataTimer.Invoke();
}
}
IMPL_LINK_NOARG(AdditionsDialog, CloseButtonHdl, weld::Button&, void)
{
if (m_pSearchThread.is())
m_pSearchThread->StopExecution();
this->response(RET_CLOSE);
}
IMPL_LINK_NOARG(AdditionsItem, ShowMoreHdl, weld::Button&, void)
{
this->m_xButtonShowMore->set_visible(false);
m_pParentDialog->m_nMaxItemCount += PAGE_SIZE;
if (m_pParentDialog->m_pSearchThread.is())
m_pParentDialog->m_pSearchThread->StopExecution();
m_pParentDialog->m_pSearchThread = new SearchAndParseThread(m_pParentDialog, false);
m_pParentDialog->m_pSearchThread->launch();
}
IMPL_LINK_NOARG(AdditionsItem, InstallHdl, weld::Button&, void)
{
m_xButtonInstall->set_label(CuiResId(RID_CUISTR_ADDITIONS_INSTALLING));
m_xButtonInstall->set_sensitive(false);
OUString aExtensionFile;
bool bResult = getExtensionFile(aExtensionFile); // info vector json data
if (!bResult)
{
m_xButtonInstall->set_label(CuiResId(RID_CUISTR_ADDITIONS_INSTALLBUTTON));
m_xButtonInstall->set_sensitive(true);
SAL_INFO("cui.dialogs", "Couldn't get the extension file.");
return;
}
rtl::Reference<TmpRepositoryCommandEnv> pCmdEnv = new TmpRepositoryCommandEnv();
uno::Reference<task::XAbortChannel> xAbortChannel;
try
{
m_pParentDialog->m_xExtensionManager->addExtension(aExtensionFile,
uno::Sequence<beans::NamedValue>(),
u"user"_ustr, xAbortChannel, pCmdEnv);
m_xButtonInstall->set_label(CuiResId(RID_CUISTR_ADDITIONS_INSTALLEDBUTTON));
}
catch (const ucb::CommandFailedException)
{
TOOLS_WARN_EXCEPTION("cui.dialogs", "");
m_xButtonInstall->set_label(CuiResId(RID_CUISTR_ADDITIONS_INSTALLBUTTON));
m_xButtonInstall->set_sensitive(true);
}
catch (const ucb::CommandAbortedException)
{
TOOLS_WARN_EXCEPTION("cui.dialogs", "");
m_xButtonInstall->set_label(CuiResId(RID_CUISTR_ADDITIONS_INSTALLBUTTON));
m_xButtonInstall->set_sensitive(true);
}
catch (const deployment::DeploymentException)
{
TOOLS_WARN_EXCEPTION("cui.dialogs", "");
m_xButtonInstall->set_label(CuiResId(RID_CUISTR_ADDITIONS_INSTALLBUTTON));
m_xButtonInstall->set_sensitive(true);
}
catch (const lang::IllegalArgumentException)
{
TOOLS_WARN_EXCEPTION("cui.dialogs", "");
m_xButtonInstall->set_label(CuiResId(RID_CUISTR_ADDITIONS_INSTALLBUTTON));
m_xButtonInstall->set_sensitive(true);
}
catch (const css::uno::Exception)
{
TOOLS_WARN_EXCEPTION("cui.dialogs", "");
m_xButtonInstall->set_label(CuiResId(RID_CUISTR_ADDITIONS_INSTALLBUTTON));
m_xButtonInstall->set_sensitive(true);
}
}
// TmpRepositoryCommandEnv
TmpRepositoryCommandEnv::TmpRepositoryCommandEnv() {}
TmpRepositoryCommandEnv::~TmpRepositoryCommandEnv() {}
// XCommandEnvironment
uno::Reference<task::XInteractionHandler> TmpRepositoryCommandEnv::getInteractionHandler()
{
return this;
}
uno::Reference<ucb::XProgressHandler> TmpRepositoryCommandEnv::getProgressHandler() { return this; }
// XInteractionHandler
void TmpRepositoryCommandEnv::handle(uno::Reference<task::XInteractionRequest> const& xRequest)
{
OSL_ASSERT(xRequest->getRequest().getValueTypeClass() == uno::TypeClass_EXCEPTION);
bool approve = true;
// select:
for (const auto& cont : xRequest->getContinuations())
{
if (approve)
{
uno::Reference<task::XInteractionApprove> xInteractionApprove(cont, uno::UNO_QUERY);
if (xInteractionApprove.is())
{
xInteractionApprove->select();
// don't query again for ongoing continuations:
approve = false;
}
}
}
}
// XProgressHandler
void TmpRepositoryCommandEnv::push(uno::Any const& /*Status*/) {}
void TmpRepositoryCommandEnv::update(uno::Any const& /*Status */) {}
void TmpRepositoryCommandEnv::pop() {}
IMPL_LINK(AdditionsDialog, GearHdl, const OUString&, rIdent, void)
{
if (rIdent == "gear_sort_voting")
{
std::sort(m_aAllExtensionsVector.begin(), m_aAllExtensionsVector.end(), sortByRating);
}
else if (rIdent == "gear_sort_comments")
{
std::sort(m_aAllExtensionsVector.begin(), m_aAllExtensionsVector.end(), sortByComment);
}
else if (rIdent == "gear_sort_downloads")
{
std::sort(m_aAllExtensionsVector.begin(), m_aAllExtensionsVector.end(), sortByDownload);
}
// After the sorting, UI will be refreshed to update extension list.
RefreshUI();
}
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
↑ V530 The return value of function 'ImportGraphic' is required to be utilized.
↑ V1053 Calling the 'getDialog' virtual function indirectly in the constructor may lead to unexpected result at runtime. Check lines: 'AdditionsDialog.cxx:482', 'weld.hxx:2691', 'weld.hxx:2708'.