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>
872 lines
30 KiB
C++
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: */
|