summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorYusuf Keten <ketenyusuf@gmail.com>2020-07-07 03:22:01 +0300
committerMuhammet Kara <muhammet.kara@collabora.com>2020-07-10 12:39:56 +0200
commitdaefcf77bd99d9dae9aaa08695c40c2a99d753d7 (patch)
treed726171e8865a18c6c769fe72546aa393d84cbe3
parentc54c8fedeaff84e1c92147ae929d6beada95bcaf (diff)
tdf#133026: Tight integration of extensions - Adding thread structure
- Add new class - SearchAndParseThread. - Add new label to show progress such as not found, loading, searching, etc. - Add cache to prevent unnecessary data transfers. Change-Id: Ieb5dbdabc3c9b5224e89be959728ca9b6d5c63b9 Reviewed-on: https://gerrit.libreoffice.org/c/core/+/98226 Tested-by: Jenkins Reviewed-by: Muhammet Kara <muhammet.kara@collabora.com>
-rw-r--r--cui/inc/strings.hrc3
-rw-r--r--cui/source/dialogs/AdditionsDialog.cxx301
-rw-r--r--cui/source/inc/AdditionsDialog.hxx30
-rw-r--r--cui/uiconfig/ui/additionsdialog.ui47
4 files changed, 323 insertions, 58 deletions
diff --git a/cui/inc/strings.hrc b/cui/inc/strings.hrc
index d54a0f9cd370..be9c3f7d3a3e 100644
--- a/cui/inc/strings.hrc
+++ b/cui/inc/strings.hrc
@@ -395,6 +395,9 @@
#define RID_SVXSTR_ADDITIONS_INSTALLBUTTON NC_("RID_SVXSTR_ADDITIONS_INSTALLBUTTON", "Install")
#define RID_SVXSTR_ADDITIONS_LICENCE NC_("RID_SVXSTR_ADDITIONS_LICENCE", "License:")
#define RID_SVXSTR_ADDITIONS_REQUIREDVERSION NC_("RID_SVXSTR_ADDITIONS_REQUIREDVERSION","Required version: >=")
+#define RID_SVXSTR_ADDITIONS_SEARCHING NC_("RID_SVXSTR_ADDITIONS_SEARCHING", "Searching...")
+#define RID_SVXSTR_ADDITIONS_LOADING NC_("RID_SVXSTR_ADDITIONS_LOADING", "Loading...")
+#define RID_SVXSTR_ADDITIONS_NORESULTS NC_("ID_SVXSTR_ADDITIONS_NORESULTS", "No results found")
#endif
diff --git a/cui/source/dialogs/AdditionsDialog.cxx b/cui/source/dialogs/AdditionsDialog.cxx
index 5ed8ad97e46c..ba49e07b9365 100644
--- a/cui/source/dialogs/AdditionsDialog.cxx
+++ b/cui/source/dialogs/AdditionsDialog.cxx
@@ -7,23 +7,41 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*
*/
+#include <config_folders.h>
#include <AdditionsDialog.hxx>
+
#include <rtl/ustrbuf.hxx>
#include <unordered_set>
#include <sal/log.hxx>
-#include <tools/stream.hxx>
#include <strings.hrc>
#include <com/sun/star/uno/XComponentContext.hpp>
#include <com/sun/star/inspection/PropertyLineElement.hpp>
#include <com/sun/star/graphic/GraphicProvider.hpp>
#include <com/sun/star/graphic/XGraphicProvider.hpp>
+#include <com/sun/star/ucb/SimpleFileAccess.hpp>
+#include <osl/file.hxx>
+#include <rtl/bootstrap.hxx>
+#include <rtl/strbuf.hxx>
#include <tools/debug.hxx>
#include <tools/diagnose_ex.h>
+#include <tools/urlobj.hxx>
+#include <tools/stream.hxx>
#include <comphelper/processfactory.hxx>
#include <comphelper/string.hxx>
#include <dialmgr.hxx>
+#include <vcl/virdev.hxx>
+#include <vcl/svapp.hxx>
+#include <vcl/settings.hxx>
+#include <vcl/graphicfilter.hxx>
+#include <vcl/mnemonic.hxx>
+
+#include <com/sun/star/task/InteractionHandler.hpp>
+#include <com/sun/star/xml/sax/XParser.hpp>
+#include <com/sun/star/xml/sax/Parser.hpp>
+#include <ucbhelper/content.hxx>
+#include <comphelper/simplefileaccessinteraction.hxx>
#include <curl/curl.h>
#include <orcus/json_document_tree.hpp>
@@ -40,14 +58,25 @@ using ::com::sun::star::graphic::XGraphicProvider;
using ::com::sun::star::uno::Sequence;
using ::com::sun::star::beans::PropertyValue;
using ::com::sun::star::graphic::XGraphic;
+using namespace com::sun::star;
+using namespace ::com::sun::star::uno;
+using namespace ::com::sun::star::ucb;
+using namespace ::com::sun::star::beans;
+
+#ifdef UNX
+const char kUserAgent[] = "LibreOffice AdditionsDownloader/1.0 (Linux)";
+#else
+const char kUserAgent[] = "LibreOffice AdditionsDownloader/1.0 (unknown platform)";
+#endif
namespace
{
struct AdditionInfo
{
+ OUString sExtensionID;
OUString sName;
OUString sAuthorName;
- OUString sPreviewURL;
+ OUString sExtensionURL;
OUString sScreenshotURL;
OUString sIntroduction;
OUString sDescription;
@@ -72,6 +101,18 @@ size_t WriteCallback(void* ptr, size_t size, size_t nmemb, void* userp)
return real_size;
}
+// Callback to get the response data from server to a file.
+size_t WriteCallbackFile(void* ptr, size_t size, size_t nmemb, void* userp)
+{
+ if (!userp)
+ return 0;
+
+ SvStream* response = static_cast<SvStream*>(userp);
+ size_t real_size = size * nmemb;
+ response->WriteBytes(ptr, real_size);
+ return real_size;
+}
+
// Gets the content of the given URL and returns as a standard string
std::string curlGet(const OString& rURL)
{
@@ -104,6 +145,36 @@ std::string curlGet(const OString& rURL)
return response_body;
}
+// Downloads and saves the file at the given rURL to a local path (sFileURL)
+void curlDownload(const OString& rURL, const OUString& sFileURL)
+{
+ CURL* curl = curl_easy_init();
+ SvFileStream aFile(sFileURL, StreamMode::WRITE);
+
+ if (!curl)
+ return;
+
+ curl_easy_setopt(curl, CURLOPT_URL, rURL.getStr());
+ curl_easy_setopt(curl, CURLOPT_USERAGENT, kUserAgent);
+
+ curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, WriteCallbackFile);
+ curl_easy_setopt(curl, CURLOPT_WRITEDATA, static_cast<void*>(&aFile));
+
+ CURLcode cc = curl_easy_perform(curl);
+ long http_code = 0;
+ curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &http_code);
+
+ if (http_code != 200)
+ {
+ SAL_WARN("cui.dialogs", "Download failed. Error code: " << http_code);
+ }
+
+ if (cc != CURLE_OK)
+ {
+ SAL_WARN("cui.dialogs", "curl error: " << cc);
+ }
+}
+
void parseResponse(const std::string& rResponse, std::vector<AdditionInfo>& aAdditions)
{
orcus::json::document_tree aJsonDoc;
@@ -130,6 +201,8 @@ void parseResponse(const std::string& rResponse, std::vector<AdditionInfo>& aAdd
try
{
AdditionInfo aNewAddition = {
+ OStringToOUString(OString(arrayElement.child("id").string_value().get()),
+ RTL_TEXTENCODING_UTF8),
OStringToOUString(OString(arrayElement.child("name").string_value().get()),
RTL_TEXTENCODING_UTF8),
OStringToOUString(OString(arrayElement.child("author").string_value().get()),
@@ -189,71 +262,197 @@ void parseResponse(const std::string& rResponse, std::vector<AdditionInfo>& aAdd
}
}
}
+
+bool getPreviewFile(const AdditionInfo& aAdditionInfo, OUString& sPreviewFile)
+{
+ uno::Reference<ucb::XSimpleFileAccess3> xFileAccess
+ = ucb::SimpleFileAccess::create(comphelper::getProcessComponentContext());
+ Reference<XComponentContext> xContext(::comphelper::getProcessComponentContext());
+
+ // copy the images to the user's additions folder
+ OUString userFolder = "${$BRAND_BASE_DIR/" LIBO_ETC_FOLDER
+ "/" SAL_CONFIGFILE("bootstrap") "::UserInstallation}";
+ rtl::Bootstrap::expandMacros(userFolder);
+ userFolder += "/user/additions/" + aAdditionInfo.sExtensionID + "/";
+
+ OUString aPreviewFile(INetURLObject(aAdditionInfo.sScreenshotURL).getName());
+ OString aPreviewURL = OUStringToOString(aAdditionInfo.sScreenshotURL, RTL_TEXTENCODING_UTF8);
+
+ try
+ {
+ osl::Directory::createPath(userFolder);
+
+ if (!xFileAccess->exists(userFolder + aPreviewFile))
+ curlDownload(aPreviewURL, userFolder + aPreviewFile);
+ }
+ catch (const uno::Exception&)
+ {
+ return false;
+ }
+ sPreviewFile = userFolder + aPreviewFile;
+ return true;
}
-AdditionsDialog::AdditionsDialog(weld::Window* pParent)
- : GenericDialogController(pParent, "cui/ui/additionsdialog.ui", "AdditionsDialog")
- , m_xEntrySearch(m_xBuilder->weld_entry("entrySearch"))
- , m_xMenuButtonSettings(m_xBuilder->weld_menu_button("buttonGear"))
- , m_xContentWindow(m_xBuilder->weld_scrolled_window("contentWindow"))
- , m_xContentGrid(m_xBuilder->weld_container("contentGrid"))
+} // End of the anonymous namespace
+
+SearchAndParseThread::SearchAndParseThread(AdditionsDialog* pDialog, const OUString& rURL,
+ const bool& isFirstLoading)
+ : Thread("cuiAdditionsSearchThread")
+ , m_pAdditionsDialog(pDialog)
+ , m_aURL(rURL)
+ , m_bExecute(true)
+ , m_bIsFirstLoading(isFirstLoading)
{
- fillGrid();
}
-AdditionsDialog::~AdditionsDialog() {}
+SearchAndParseThread::~SearchAndParseThread() {}
-void AdditionsDialog::fillGrid()
+void SearchAndParseThread::execute()
{
- // TODO - Temporary URL
- OString rURL = "https://yusufketen.com/extensionTest.json";
+ //m_pAdditionsDialog->ClearSearchResults();
+ OUString sProgress;
+ if (m_bIsFirstLoading)
+ sProgress = CuiResId(RID_SVXSTR_ADDITIONS_LOADING);
+ else
+ sProgress = CuiResId(RID_SVXSTR_ADDITIONS_SEARCHING);
+
+ m_pAdditionsDialog->SetProgress(sProgress);
+
+ uno::Reference<ucb::XSimpleFileAccess3> xFileAccess
+ = ucb::SimpleFileAccess::create(comphelper::getProcessComponentContext());
+ OString rURL = OUStringToOString(m_aURL, RTL_TEXTENCODING_UTF8);
std::string sResponse = curlGet(rURL);
+
std::vector<AdditionInfo> additionInfos;
+
parseResponse(sResponse, additionInfos);
- sal_Int32 i = 0;
- for (const auto& additionInfo : additionInfos)
+ if (additionInfos.empty())
{
- m_aAdditionsItems.emplace_back(m_xContentGrid.get());
- AdditionsItem& aCurrentItem = m_aAdditionsItems.back();
-
- sal_Int32 nGridPositionY = i++;
- aCurrentItem.m_xContainer->set_grid_left_attach(0);
- aCurrentItem.m_xContainer->set_grid_top_attach(nGridPositionY);
-
- aCurrentItem.m_xLinkButtonName->set_label(additionInfo.sName);
- aCurrentItem.m_xLinkButtonName->set_uri(additionInfo.sPreviewURL);
- aCurrentItem.m_xLabelDescription->set_label(additionInfo.sIntroduction);
- aCurrentItem.m_xLabelAuthor->set_label(additionInfo.sAuthorName);
- aCurrentItem.m_xButtonInstall->set_label(CuiResId(RID_SVXSTR_ADDITIONS_INSTALLBUTTON));
- OUString sLicenseString = CuiResId(RID_SVXSTR_ADDITIONS_LICENCE) + additionInfo.sLicense;
- aCurrentItem.m_xLabelLicense->set_label(sLicenseString);
- OUString sVersionString
- = CuiResId(RID_SVXSTR_ADDITIONS_REQUIREDVERSION) + additionInfo.sCompatibleVersion;
- aCurrentItem.m_xLabelVersion->set_label(sVersionString);
- aCurrentItem.m_xLinkButtonComments->set_label(additionInfo.sCommentNumber);
- aCurrentItem.m_xLinkButtonComments->set_uri(additionInfo.sCommentURL);
- aCurrentItem.m_xLabelDownloadNumber->set_label(additionInfo.sDownloadNumber);
-
- Reference<XGraphic> xGraphic;
- try
+ sProgress = CuiResId(RID_SVXSTR_ADDITIONS_NORESULTS);
+ m_pAdditionsDialog->SetProgress(sProgress);
+ return;
+ }
+ else
+ {
+ //Get Preview Files
+ sal_Int32 i = 0;
+ for (const auto& additionInfo : additionInfos)
{
- Reference<XComponentContext> xContext(::comphelper::getProcessComponentContext());
- Reference<XGraphicProvider> xGraphicProvider(GraphicProvider::create(xContext));
+ if (!m_bExecute)
+ return;
+
+ OUString aPreviewFile;
+ bool bResult = getPreviewFile(additionInfo, aPreviewFile);
+
+ if (!bResult)
+ {
+ SAL_INFO("cui.dialogs",
+ "Couldn't get the preview file. Skipping: " << aPreviewFile);
+ continue;
+ }
+
+ SolarMutexGuard aGuard;
+ m_pAdditionsDialog->m_aAdditionsItems.emplace_back(
+ m_pAdditionsDialog->m_xContentGrid.get());
+ AdditionsItem& aCurrentItem = m_pAdditionsDialog->m_aAdditionsItems.back();
+
+ sal_Int32 nGridPositionY = i++;
+ aCurrentItem.m_xContainer->set_grid_left_attach(0);
+ aCurrentItem.m_xContainer->set_grid_top_attach(nGridPositionY);
+
+ aCurrentItem.m_xLinkButtonName->set_label(additionInfo.sName);
+ aCurrentItem.m_xLinkButtonName->set_uri(additionInfo.sExtensionURL);
+ aCurrentItem.m_xLabelDescription->set_label(additionInfo.sIntroduction);
+ aCurrentItem.m_xLabelAuthor->set_label(additionInfo.sAuthorName);
+ aCurrentItem.m_xButtonInstall->set_label(CuiResId(RID_SVXSTR_ADDITIONS_INSTALLBUTTON));
+ OUString sLicenseString
+ = CuiResId(RID_SVXSTR_ADDITIONS_LICENCE) + additionInfo.sLicense;
+ aCurrentItem.m_xLabelLicense->set_label(sLicenseString);
+ OUString sVersionString
+ = CuiResId(RID_SVXSTR_ADDITIONS_REQUIREDVERSION) + additionInfo.sCompatibleVersion;
+ aCurrentItem.m_xLabelVersion->set_label(sVersionString);
+ aCurrentItem.m_xLinkButtonComments->set_label(additionInfo.sCommentNumber);
+ aCurrentItem.m_xLinkButtonComments->set_uri(additionInfo.sCommentURL);
+ aCurrentItem.m_xLabelDownloadNumber->set_label(additionInfo.sDownloadNumber);
+
+ GraphicFilter aFilter;
+ Graphic aGraphic;
+
+ INetURLObject aURLObj(aPreviewFile);
+
+ // This block may be added according to need
+ /*OUString aAdditionsSetting = additionInfo.sSlug
+ + ";" + additionInfo.sName
+ + ";" + additionInfo.sPreviewURL
+ + ";" + additionInfo.sHeaderURL
+ + ";" + additionInfo.sFooterURL
+ + ";" + additionInfo.sTextColor;
+
+ m_pAdditionsDialog->AddPersonaSetting( aPersonaSetting );
+ */
+
+ // for VCL to be able to create bitmaps / do visual changes in the thread
+ aFilter.ImportGraphic(aGraphic, aURLObj);
+ BitmapEx aBmp = aGraphic.GetBitmapEx();
+
+ ScopedVclPtr<VirtualDevice> xVirDev
+ = aCurrentItem.m_xImageScreenshot->create_virtual_device();
+ xVirDev->SetOutputSizePixel(aBmp.GetSizePixel());
+ xVirDev->DrawBitmapEx(Point(0, 0), aBmp);
+
+ aCurrentItem.m_xImageScreenshot->set_image(xVirDev.get());
+ xVirDev.disposeAndClear();
+ }
+ }
- Sequence<PropertyValue> aMediaProperties(1);
- aMediaProperties[0].Name = "URL";
- aMediaProperties[0].Value <<= additionInfo.sScreenshotURL;
+ if (!m_bExecute)
+ return;
- xGraphic = Reference<XGraphic>(xGraphicProvider->queryGraphic(aMediaProperties),
- css::uno::UNO_SET_THROW);
- }
- catch (const Exception&)
- {
- DBG_UNHANDLED_EXCEPTION("cui.dialogs");
- }
+ SolarMutexGuard aGuard;
+ sProgress.clear();
+ m_pAdditionsDialog->SetProgress(sProgress);
+}
+
+AdditionsDialog::AdditionsDialog(weld::Window* pParent)
+ : GenericDialogController(pParent, "cui/ui/additionsdialog.ui", "AdditionsDialog")
+ , m_xEntrySearch(m_xBuilder->weld_entry("entrySearch"))
+ , m_xMenuButtonSettings(m_xBuilder->weld_menu_button("buttonGear"))
+ , m_xContentWindow(m_xBuilder->weld_scrolled_window("contentWindow"))
+ , m_xContentGrid(m_xBuilder->weld_container("contentGrid"))
+ , m_xLabelProgress(m_xBuilder->weld_label("labelProgress"))
+{
+ // TODO - Temporary URL
+ OString rURL = "https://yusufketen.com/extensionTest.json";
+
+ m_pSearchThread
+ = new SearchAndParseThread(this, OStringToOUString(rURL, RTL_TEXTENCODING_UTF8), true);
+ m_pSearchThread->launch();
+ // fillGrid();
+}
- aCurrentItem.m_xImageScreenshot->set_image(xGraphic);
+AdditionsDialog::~AdditionsDialog()
+{
+ if (m_pSearchThread.is())
+ {
+ // 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();
+ }
+}
+
+void AdditionsDialog::SetProgress(const OUString& rProgress)
+{
+ if (rProgress.isEmpty())
+ m_xLabelProgress->hide();
+ else
+ {
+ SolarMutexGuard aGuard;
+ m_xLabelProgress->show();
+ m_xLabelProgress->set_label(rProgress);
+ m_xDialog->resize_to_request(); //TODO
}
}
diff --git a/cui/source/inc/AdditionsDialog.hxx b/cui/source/inc/AdditionsDialog.hxx
index ee252e0ea79e..8b3211bb3d1a 100644
--- a/cui/source/inc/AdditionsDialog.hxx
+++ b/cui/source/inc/AdditionsDialog.hxx
@@ -11,6 +11,8 @@
#pragma once
#include <vcl/svapp.hxx>
+#include <salhelper/thread.hxx>
+#include <rtl/ref.hxx>
#include <vcl/weld.hxx>
struct AdditionsItem
@@ -50,10 +52,14 @@ struct AdditionsItem
std::unique_ptr<weld::Image> m_xImageDownloadNumber;
std::unique_ptr<weld::Label> m_xLabelDownloadNumber;
};
+class SearchAndParseThread;
class AdditionsDialog : public weld::GenericDialogController
{
private:
+ // void fillGrid();
+
+public:
std::unique_ptr<weld::Entry> m_xEntrySearch;
std::unique_ptr<weld::MenuButton> m_xMenuButtonSettings;
std::vector<AdditionsItem> m_aAdditionsItems;
@@ -61,11 +67,31 @@ private:
std::unique_ptr<weld::ScrolledWindow> m_xContentWindow;
std::unique_ptr<weld::Container> m_xContentGrid;
- void fillGrid();
+ std::unique_ptr<weld::Label> m_xLabelProgress;
+ ::rtl::Reference<SearchAndParseThread> m_pSearchThread;
-public:
AdditionsDialog(weld::Window* pParent);
~AdditionsDialog() override;
+
+ void SetProgress(const OUString& rProgress);
+};
+
+class SearchAndParseThread : public salhelper::Thread
+{
+private:
+ AdditionsDialog* m_pAdditionsDialog;
+ OUString m_aURL;
+ std::atomic<bool> m_bExecute;
+ bool m_bIsFirstLoading;
+
+ virtual ~SearchAndParseThread() override;
+ virtual void execute() override;
+
+public:
+ SearchAndParseThread(AdditionsDialog* pDialog, const OUString& rURL,
+ const bool& bIsFirstLoading);
+
+ void StopExecution() { m_bExecute = false; }
};
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/cui/uiconfig/ui/additionsdialog.ui b/cui/uiconfig/ui/additionsdialog.ui
index 1eb7736220a1..0cd02d98e8cb 100644
--- a/cui/uiconfig/ui/additionsdialog.ui
+++ b/cui/uiconfig/ui/additionsdialog.ui
@@ -26,17 +26,54 @@
<property name="can_focus">False</property>
<property name="layout_style">end</property>
<child>
+ <object class="GtkBox">
+ <property name="width_request">300</property>
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="orientation">vertical</property>
+ <property name="spacing">5</property>
+ <child>
+ <placeholder/>
+ </child>
+ <child>
+ <object class="GtkLabel" id="labelProgress">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="hexpand">True</property>
+ <property name="label" translatable="yes" context="additionsdialog|ProgressLabel">Progress Label</property>
+ <child internal-child="accessible">
+ <object class="AtkObject" id="labelProgress-atkobject">
+ <property name="AtkObject::accessible-name" translatable="yes" context="additionsdialog|ProgressLabel">ProgressLabel</property>
+ <property name="AtkObject::accessible-description" translatable="yes" context="additionsdialog|ProgressLabel">This label shows that the progress of the operations such as loading extensions, not found, etc.</property>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
<object class="GtkButton" id="buttonClose">
<property name="label">gtk-close</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
+ <property name="valign">end</property>
<property name="use_stock">True</property>
</object>
<packing>
<property name="expand">False</property>
- <property name="fill">True</property>
- <property name="position">0</property>
+ <property name="fill">False</property>
+ <property name="position">1</property>
</packing>
</child>
</object>
@@ -67,8 +104,8 @@
<property name="primary_icon_sensitive">False</property>
<child internal-child="accessible">
<object class="AtkObject" id="entrySearch-atkobject">
- <property name="AtkObject::accessible-name" translatable="yes" context="additionsdialog|entrySearch">searchEntry</property>
- <property name="AtkObject::accessible-description" translatable="yes" context="additionsdialog|entrySearch">searchEntry</property>
+ <property name="AtkObject::accessible-name" translatable="yes" context="additionsdialog|searchEntry">searchEntry</property>
+ <property name="AtkObject::accessible-description" translatable="yes" context="additionsdialog|searchEntry">searchEntry</property>
</object>
</child>
</object>
@@ -95,7 +132,7 @@
</child>
<child internal-child="accessible">
<object class="AtkObject" id="buttonGear-atkobject">
- <property name="AtkObject::accessible-name" translatable="yes" context="additionsdialog|buttonGear">Gear Menu</property>
+ <property name="AtkObject::accessible-name" translatable="yes" context="additionsdialog|buttonGear">Gear Menu</property>
<property name="AtkObject::accessible-description" translatable="yes" context="additionsdialog|buttonGear">Contains commands to modify settings of the additions list such as sorting type or view type.</property>
</object>
</child>