Files
Michael Weghorn f1f6a505a0 weld: Pass weld::TextWidget& in text change callback/handler
Switch the weld::Entry and weld::TextView handlers
that are called when the text changes due to user
interaction to pass a weld::TextWidget& param
instead of a weld::Entry& and weld::TextView&
one respectively.
Adjust related methods across the code base accordingly.

This unifies the behavior for both of these
weld::TextWidget subclasses and prepares for
deduplicating the logic and moving it to the
common base class in an upcoming commit.

That will also ensure they stay in sync.
(Change also motivated by the fact that their behavior was
differing before

    Change-Id: I03d2a13cd84d53d806eb9e363f49c4b6be28d938
    Author: Michael Weghorn <m.weghorn@posteo.de>
    Date:   Thu Jul 9 13:08:58 2026 +0200

        tdf#130857 weld: Don't signal about programmatic TextView text change
.)

Change-Id: I5572fc88caf577fae9a23a39f1ec4f2179935a99
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/207936
Tested-by: Jenkins
Reviewed-by: Michael Weghorn <m.weghorn@posteo.de>
2026-07-10 18:17:27 +02:00

872 lines
30 KiB
C++

/* -*- 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/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/graph.hxx>
#include <vcl/graphicfilter.hxx>
#include <vcl/vclenum.hxx>
#include <vcl/weld/Dialog.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 MAX_ITEMS_PER_PAGE 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, const AdditionsItem& rCurrentItem)
{
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);
Bitmap aBmp = aGraphic.GetBitmap();
Size aBmpSize = aBmp.GetSizePixel();
Size aThumbSize(rCurrentItem.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 = rCurrentItem.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->DrawBitmap(Point(aThumbSize.Width() / 2 - aBmpSize.Width() / 2, Margin), aBmp);
rCurrentItem.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;
m_pAdditionsDialog->m_aAdditionsItems.push_back(std::make_unique<AdditionsItem>(
m_pAdditionsDialog->m_xContentBox.get(), m_pAdditionsDialog, additionInfo));
AdditionsItem& rCurrentItem = *m_pAdditionsDialog->m_aAdditionsItems.back();
LoadImage(aPreviewFile, rCurrentItem);
m_pAdditionsDialog->m_nCurrentListItemCount++;
if (m_pAdditionsDialog->m_nCurrentListItemCount == m_pAdditionsDialog->m_nMaxItemCount)
{
if (m_pAdditionsDialog->m_nCurrentListItemCount
!= m_pAdditionsDialog->m_aAllExtensionsVector.size())
m_pAdditionsDialog->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_xContentBox(m_xBuilder->weld_box(u"contentBox"_ustr))
, m_xButtonShowMore(m_xBuilder->weld_button(u"buttonShowMore"_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_rating"_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_xButtonShowMore->connect_clicked(LINK(this, AdditionsDialog, ShowMoreHdl));
m_xButtonClose->connect_clicked(LINK(this, AdditionsDialog, CloseButtonHdl));
m_nMaxItemCount = MAX_ITEMS_PER_PAGE; // Dialog initialization item count
m_nCurrentListItemCount = 0; // First, there is no item on the list.
OUString sTag = sAdditionsTag;
OUString titlePrefix = CuiResId(RID_CUISTR_ADDITIONS_DIALOG_TITLE_PREFIX);
if (!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);
}
set_title(sDialogTitle);
}
else
{
set_title(titlePrefix);
sTag = "allextensions"; // Means empty parameter
}
OUString sEncodedURLPart = INetURLObject::encode(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 : m_aAdditionsItems)
{
item->m_xContainer->hide();
}
m_aAdditionsItems.clear();
}
void AdditionsDialog::RefreshUI()
{
if (m_pSearchThread.is())
m_pSearchThread->StopExecution();
ClearList();
m_nCurrentListItemCount = 0;
m_nMaxItemCount = MAX_ITEMS_PER_PAGE;
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::Box* pParentBox, AdditionsDialog* pParentDialog,
const AdditionInfo& additionInfo)
: m_xBuilder(Application::CreateBuilder(pParentBox, 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_xImageRating1(m_xBuilder->weld_image(u"imageRating1"_ustr))
, m_xImageRating2(m_xBuilder->weld_image(u"imageRating2"_ustr))
, m_xImageRating3(m_xBuilder->weld_image(u"imageRating3"_ustr))
, m_xImageRating4(m_xBuilder->weld_image(u"imageRating4"_ustr))
, m_xImageRating5(m_xBuilder->weld_image(u"imageRating5"_ustr))
, m_xLabelDownloadNumber(m_xBuilder->weld_label(u"labelDownloadNumber"_ustr))
, m_pParentDialog(pParentDialog)
, m_sDownloadURL(u""_ustr)
, m_sExtensionID(u""_ustr)
{
SolarMutexGuard aGuard;
// AdditionsItem set location
pParentBox->reorder_child(m_xContainer.get(), 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_xImageRating5->set_from_icon_name(RID_SVXBMP_STARS_FULL);
[[fallthrough]];
case 4:
m_xImageRating4->set_from_icon_name(RID_SVXBMP_STARS_FULL);
[[fallthrough]];
case 3:
m_xImageRating3->set_from_icon_name(RID_SVXBMP_STARS_FULL);
[[fallthrough]];
case 2:
m_xImageRating2->set_from_icon_name(RID_SVXBMP_STARS_FULL);
[[fallthrough]];
case 1:
m_xImageRating1->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_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::TextWidget&, 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();
response(RET_CLOSE);
}
IMPL_LINK_NOARG(AdditionsDialog, ShowMoreHdl, weld::Button&, void)
{
m_xButtonShowMore->set_visible(false);
m_nMaxItemCount += MAX_ITEMS_PER_PAGE;
if (m_pSearchThread.is())
m_pSearchThread->StopExecution();
m_pSearchThread = new SearchAndParseThread(this, false);
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_rating")
{
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: */