LibreOffice Module cui (master)  1
AdditionsDialog.cxx
Go to the documentation of this file.
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
3  * This file is part of the LibreOffice project.
4  *
5  * This Source Code Form is subject to the terms of the Mozilla Public
6  * License, v. 2.0. If a copy of the MPL was not distributed with this
7  * file, You can obtain one at http://mozilla.org/MPL/2.0/.
8  *
9  */
10 
11 #include <sal/config.h>
12 
13 #include <algorithm>
14 #include <cmath>
15 
16 #include <config_folders.h>
17 
18 #include <AdditionsDialog.hxx>
19 #include <dialmgr.hxx>
20 #include <strings.hrc>
21 
22 #include <sal/log.hxx>
23 
24 #include <com/sun/star/graphic/GraphicProvider.hpp>
25 #include <com/sun/star/graphic/XGraphicProvider.hpp>
26 #include <com/sun/star/ucb/NameClash.hpp>
27 #include <com/sun/star/ucb/SimpleFileAccess.hpp>
28 #include <osl/file.hxx>
29 #include <rtl/bootstrap.hxx>
30 #include <tools/urlobj.hxx>
31 #include <tools/stream.hxx>
32 #include <tools/diagnose_ex.h>
34 #include <vcl/virdev.hxx>
35 #include <vcl/svapp.hxx>
36 #include <vcl/graphicfilter.hxx>
37 #include <cppuhelper/exc_hlp.hxx>
38 
39 #include <com/sun/star/util/SearchFlags.hpp>
40 #include <com/sun/star/util/SearchAlgorithms2.hpp>
41 #include <unotools/textsearch.hxx>
43 #include <ucbhelper/content.hxx>
44 
45 #include <com/sun/star/deployment/DeploymentException.hpp>
46 #include <com/sun/star/deployment/ExtensionManager.hpp>
47 #include <com/sun/star/lang/WrappedTargetRuntimeException.hpp>
48 #include <com/sun/star/ucb/CommandAbortedException.hpp>
49 #include <com/sun/star/ucb/CommandFailedException.hpp>
50 
51 #include <com/sun/star/task/XInteractionApprove.hpp>
52 
53 #include <orcus/json_document_tree.hpp>
54 #include <orcus/json_parser.hpp>
55 #include <orcus/config.hpp>
56 
57 #define PAGE_SIZE 30
58 
59 using namespace css;
60 using ::com::sun::star::uno::Reference;
61 using ::com::sun::star::uno::XComponentContext;
62 using ::com::sun::star::uno::UNO_QUERY_THROW;
63 using ::com::sun::star::uno::Exception;
64 using ::com::sun::star::graphic::GraphicProvider;
65 using ::com::sun::star::graphic::XGraphicProvider;
66 using ::com::sun::star::uno::Sequence;
67 using ::com::sun::star::beans::PropertyValue;
68 using ::com::sun::star::graphic::XGraphic;
69 
70 using namespace com::sun::star;
71 using namespace ::com::sun::star::uno;
72 using namespace ::com::sun::star::ucb;
73 using namespace ::com::sun::star::beans;
74 
75 namespace
76 {
77 // Gets the content of the given URL and returns as a standard string
78 std::string ucbGet(const OUString& rURL)
79 {
80  try
81  {
82  auto const s = utl::UcbStreamHelper::CreateStream(rURL, StreamMode::STD_READ);
83  if (!s)
84  {
85  SAL_WARN("cui.dialogs", "CreateStream <" << rURL << "> failed");
86  return {};
87  }
88  std::string response_body;
89  do
90  {
91  char buf[4096];
92  auto const n = s->ReadBytes(buf, sizeof buf);
93  response_body.append(buf, n);
94  } while (s->good());
95  if (s->bad())
96  {
97  SAL_WARN("cui.dialogs", "Reading <" << rURL << "> failed with " << s->GetError());
98  return {};
99  }
100  return response_body;
101  }
102  catch (css::uno::Exception&)
103  {
104  TOOLS_WARN_EXCEPTION("cui.dialogs", "Download failed");
105  return {};
106  }
107 }
108 
109 // Downloads and saves the file at the given rURL to a local path (sFolderURL/fileName)
110 void ucbDownload(const OUString& rURL, const OUString& sFolderURL, const OUString& fileName)
111 {
112  try
113  {
115  .transferContent(ucbhelper::Content(rURL, {}, comphelper::getProcessComponentContext()),
117  css::ucb::NameClash::OVERWRITE);
118  }
119  catch (css::uno::Exception&)
120  {
121  TOOLS_WARN_EXCEPTION("cui.dialogs", "Download failed");
122  }
123 }
124 
125 void parseResponse(const std::string& rResponse, std::vector<AdditionInfo>& aAdditions)
126 {
127  orcus::json::document_tree aJsonDoc;
128  orcus::json_config aConfig;
129 
130  if (rResponse.empty())
131  return;
132 
133  try
134  {
135  aJsonDoc.load(rResponse, aConfig);
136  }
137  catch (const orcus::json::parse_error&)
138  {
139  TOOLS_WARN_EXCEPTION("cui.dialogs", "Invalid JSON file from the extensions API");
140  return;
141  }
142 
143  auto aDocumentRoot = aJsonDoc.get_document_root();
144  if (aDocumentRoot.type() != orcus::json::node_t::object)
145  {
146  SAL_WARN("cui.dialogs", "invalid root entries: " << rResponse);
147  return;
148  }
149 
150  auto resultsArray = aDocumentRoot.child("extension");
151 
152  for (size_t i = 0; i < resultsArray.child_count(); ++i)
153  {
154  auto arrayElement = resultsArray.child(i);
155 
156  try
157  {
158  AdditionInfo aNewAddition = {
159  OStringToOUString(arrayElement.child("id").string_value(), RTL_TEXTENCODING_UTF8),
160  OStringToOUString(arrayElement.child("name").string_value(), RTL_TEXTENCODING_UTF8),
161  OStringToOUString(arrayElement.child("author").string_value(),
162  RTL_TEXTENCODING_UTF8),
163  OStringToOUString(arrayElement.child("url").string_value(), RTL_TEXTENCODING_UTF8),
164  OStringToOUString(arrayElement.child("screenshotURL").string_value(),
165  RTL_TEXTENCODING_UTF8),
166  OStringToOUString(arrayElement.child("extensionIntroduction").string_value(),
167  RTL_TEXTENCODING_UTF8),
168  OStringToOUString(arrayElement.child("extensionDescription").string_value(),
169  RTL_TEXTENCODING_UTF8),
170  OStringToOUString(
171  arrayElement.child("releases").child(0).child("compatibility").string_value(),
172  RTL_TEXTENCODING_UTF8),
173  OStringToOUString(
174  arrayElement.child("releases").child(0).child("releaseName").string_value(),
175  RTL_TEXTENCODING_UTF8),
176  OStringToOUString(
177  arrayElement.child("releases").child(0).child("license").string_value(),
178  RTL_TEXTENCODING_UTF8),
179  OStringToOUString(arrayElement.child("commentNumber").string_value(),
180  RTL_TEXTENCODING_UTF8),
181  OStringToOUString(arrayElement.child("commentURL").string_value(),
182  RTL_TEXTENCODING_UTF8),
183  OStringToOUString(arrayElement.child("rating").string_value(),
184  RTL_TEXTENCODING_UTF8),
185  OStringToOUString(arrayElement.child("downloadNumber").string_value(),
186  RTL_TEXTENCODING_UTF8),
187  OStringToOUString(
188  arrayElement.child("releases").child(0).child("downloadURL").string_value(),
189  RTL_TEXTENCODING_UTF8)
190  };
191 
192  aAdditions.push_back(aNewAddition);
193  }
194  catch (orcus::json::document_error& e)
195  {
196  // This usually happens when one of the values is null (type() == orcus::json::node_t::null)
197  // TODO: Allow null values in additions.
198  SAL_WARN("cui.dialogs", "Additions JSON parse error: " << e.what());
199  }
200  }
201 }
202 
203 bool getPreviewFile(const AdditionInfo& aAdditionInfo, OUString& sPreviewFile)
204 {
205  uno::Reference<ucb::XSimpleFileAccess3> xFileAccess
206  = ucb::SimpleFileAccess::create(comphelper::getProcessComponentContext());
207 
208  // copy the images to the user's additions folder
209  OUString userFolder = "${$BRAND_BASE_DIR/" LIBO_ETC_FOLDER
210  "/" SAL_CONFIGFILE("bootstrap") "::UserInstallation}";
211  rtl::Bootstrap::expandMacros(userFolder);
212  userFolder += "/user/additions/" + aAdditionInfo.sExtensionID + "/";
213 
214  OUString aPreviewFile(INetURLObject(aAdditionInfo.sScreenshotURL).getName());
215  OUString aPreviewURL = aAdditionInfo.sScreenshotURL;
216 
217  try
218  {
219  osl::Directory::createPath(userFolder);
220 
221  if (!xFileAccess->exists(userFolder + aPreviewFile))
222  ucbDownload(aPreviewURL, userFolder, aPreviewFile);
223  }
224  catch (const uno::Exception&)
225  {
226  return false;
227  }
228  sPreviewFile = userFolder + aPreviewFile;
229  return true;
230 }
231 
232 void LoadImage(const OUString& rPreviewFile, std::shared_ptr<AdditionsItem> pCurrentItem)
233 {
234  const sal_Int8 Margin = 6;
235 
236  SolarMutexGuard aGuard;
237 
238  GraphicFilter aFilter;
239  Graphic aGraphic;
240 
241  INetURLObject aURLObj(rPreviewFile);
242 
243  // for VCL to be able to create bitmaps / do visual changes in the thread
244  aFilter.ImportGraphic(aGraphic, aURLObj);
245  BitmapEx aBmp = aGraphic.GetBitmapEx();
246  Size aBmpSize = aBmp.GetSizePixel();
247  Size aThumbSize(pCurrentItem->m_xImageScreenshot->get_size_request());
248  if (!aBmp.IsEmpty())
249  {
250  double aScale;
251  if (aBmpSize.Width() > aThumbSize.Width() - 2 * Margin)
252  {
253  aScale = static_cast<double>(aBmpSize.Width()) / (aThumbSize.Width() - 2 * Margin);
254  aBmp.Scale(Size(aBmpSize.Width() / aScale, aBmpSize.Height() / aScale));
255  }
256  else if (aBmpSize.Height() > aThumbSize.Height() - 2 * Margin)
257  {
258  aScale = static_cast<double>(aBmpSize.Height()) / (aThumbSize.Height() - 2 * Margin);
259  aBmp.Scale(Size(aBmpSize.Width() / aScale, aBmpSize.Height() / aScale));
260  };
261  aBmpSize = aBmp.GetSizePixel();
262  }
263 
264  ScopedVclPtr<VirtualDevice> xVirDev = pCurrentItem->m_xImageScreenshot->create_virtual_device();
265  xVirDev->SetOutputSizePixel(aThumbSize);
266  //white background since images come with a white border
267  xVirDev->SetBackground(Wallpaper(COL_WHITE));
268  xVirDev->Erase();
269  xVirDev->DrawBitmapEx(Point(aThumbSize.Width() / 2 - aBmpSize.Width() / 2, Margin), aBmp);
270  pCurrentItem->m_xImageScreenshot->set_image(xVirDev.get());
271  xVirDev.disposeAndClear();
272 }
273 
274 } // End of the anonymous namespace
275 
277  : Thread("cuiAdditionsSearchThread")
278  , m_pAdditionsDialog(pDialog)
279  , m_bExecute(true)
280  , m_bIsFirstLoading(isFirstLoading)
281 {
282 }
283 
285 
287 {
288  if (!m_bExecute)
289  return;
290  OUString aPreviewFile;
291  bool bResult = getPreviewFile(additionInfo, aPreviewFile); // info vector json data
292 
293  if (!bResult)
294  {
295  SAL_INFO("cui.dialogs", "Couldn't get the preview file. Skipping: " << aPreviewFile);
296  return;
297  }
298 
299  SolarMutexGuard aGuard;
300 
301  auto newItem = std::make_shared<AdditionsItem>(m_pAdditionsDialog->m_xContentGrid.get(),
302  m_pAdditionsDialog, additionInfo);
303  m_pAdditionsDialog->m_aAdditionsItems.push_back(newItem);
304  std::shared_ptr<AdditionsItem> aCurrentItem = m_pAdditionsDialog->m_aAdditionsItems.back();
305 
306  LoadImage(aPreviewFile, aCurrentItem);
308 
310  {
313  aCurrentItem->m_xButtonShowMore->set_visible(true);
314  }
315 }
316 
318 {
320  = m_pAdditionsDialog->m_xEntrySearch->get_text();
322 
323  size_t nIteration = 0;
324  for (auto& rInfo : m_pAdditionsDialog->m_aAllExtensionsVector)
325  {
327  break;
328 
329  OUString sExtensionName = rInfo.sName;
330  OUString sExtensionDescription = rInfo.sDescription;
331 
332  if (!m_pAdditionsDialog->m_xEntrySearch->get_text().isEmpty()
333  && !textSearch.searchForward(sExtensionName)
334  && !textSearch.searchForward(sExtensionDescription))
335  {
336  continue;
337  }
338  else
339  {
340  if (nIteration >= m_pAdditionsDialog->m_nCurrentListItemCount)
341  Append(rInfo);
342  nIteration++;
343  }
344  }
346 }
347 
349 {
350  const uno::Sequence<uno::Sequence<uno::Reference<deployment::XPackage>>> xAllPackages
352 
353  if (!xAllPackages.hasElements())
354  return;
355 
356  OUString currentExtensionName;
357 
358  for (auto& package : xAllPackages)
359  {
360  for (auto& extensionVersion : package)
361  {
362  if (extensionVersion.is())
363  {
364  currentExtensionName = extensionVersion->getName();
365  if (currentExtensionName.isEmpty())
366  continue;
367 
368  m_pAdditionsDialog->m_searchOptions.searchString = currentExtensionName;
370 
371  for (auto& rInfo : m_pAdditionsDialog->m_aAdditionsItems)
372  {
373  OUString sExtensionDownloadURL = rInfo->m_sDownloadURL;
374 
375  if (!textSearch.searchForward(sExtensionDownloadURL))
376  {
377  continue;
378  }
379  else
380  {
381  SolarMutexGuard aGuard;
382  rInfo->m_xButtonInstall->set_sensitive(false);
383  rInfo->m_xButtonInstall->set_label(
384  CuiResId(RID_CUISTR_ADDITIONS_INSTALLEDBUTTON));
385  }
386  }
387  }
388  }
389  }
390 }
391 
393 {
394  OUString sProgress;
395  if (m_bIsFirstLoading)
396  sProgress = CuiResId(RID_CUISTR_ADDITIONS_LOADING);
397  else
398  sProgress = CuiResId(RID_CUISTR_ADDITIONS_SEARCHING);
399 
401  sProgress); // Loading or searching according to being first call or not
402 
403  if (m_bIsFirstLoading)
404  {
405  std::string sResponse = ucbGet(m_pAdditionsDialog->m_sURL);
406  parseResponse(sResponse, m_pAdditionsDialog->m_aAllExtensionsVector);
407  std::sort(m_pAdditionsDialog->m_aAllExtensionsVector.begin(),
410  Search();
411  }
412  else // Searching
413  {
414  Search();
415  }
416 
417  if (!m_bExecute)
418  return;
419 
420  SolarMutexGuard aGuard;
421  sProgress.clear();
422  m_pAdditionsDialog->SetProgress(sProgress);
423 }
424 
425 AdditionsDialog::AdditionsDialog(weld::Window* pParent, const OUString& sAdditionsTag)
426  : GenericDialogController(pParent, "cui/ui/additionsdialog.ui", "AdditionsDialog")
427  , m_aSearchDataTimer("AdditionsDialog SearchDataTimer")
428  , m_xEntrySearch(m_xBuilder->weld_entry("entrySearch"))
429  , m_xButtonClose(m_xBuilder->weld_button("buttonClose"))
430  , m_xMenuButtonSettings(m_xBuilder->weld_menu_button("buttonGear"))
431  , m_xContentWindow(m_xBuilder->weld_scrolled_window("contentWindow"))
432  , m_xContentGrid(m_xBuilder->weld_container("contentGrid"))
433  , m_xLabelProgress(m_xBuilder->weld_label("labelProgress"))
434  , m_xGearBtn(m_xBuilder->weld_menu_button("buttonGear"))
435 {
436  m_xGearBtn->connect_selected(LINK(this, AdditionsDialog, GearHdl));
437  m_xGearBtn->set_item_active("gear_sort_voting", true);
438 
439  m_aSearchDataTimer.SetInvokeHandler(LINK(this, AdditionsDialog, ImplUpdateDataHdl));
441 
442  m_xEntrySearch->connect_changed(LINK(this, AdditionsDialog, SearchUpdateHdl));
443  m_xEntrySearch->connect_focus_out(LINK(this, AdditionsDialog, FocusOut_Impl));
444  m_xButtonClose->connect_clicked(LINK(this, AdditionsDialog, CloseButtonHdl));
445 
446  m_sTag = sAdditionsTag;
447  m_nMaxItemCount = PAGE_SIZE; // Dialog initialization item count
448  m_nCurrentListItemCount = 0; // First, there is no item on the list.
449 
450  OUString titlePrefix = CuiResId(RID_CUISTR_ADDITIONS_DIALOG_TITLE_PREFIX);
451  if (!m_sTag.isEmpty())
452  { // tdf#142564 localize extension category names
453  OUString sDialogTitle = "";
454  if (sAdditionsTag == "Templates")
455  {
456  sDialogTitle = CuiResId(RID_CUISTR_ADDITIONS_TEMPLATES);
457  }
458  else if (sAdditionsTag == "Dictionary")
459  {
460  sDialogTitle = CuiResId(RID_CUISTR_ADDITIONS_DICTIONARY);
461  }
462  else if (sAdditionsTag == "Gallery")
463  {
464  sDialogTitle = CuiResId(RID_CUISTR_ADDITIONS_GALLERY);
465  }
466  else if (sAdditionsTag == "Icons")
467  {
468  sDialogTitle = CuiResId(RID_CUISTR_ADDITIONS_ICONS);
469  }
470  else if (sAdditionsTag == "Color Palette")
471  {
472  sDialogTitle = CuiResId(RID_CUISTR_ADDITIONS_PALETTES);
473  }
474  this->set_title(sDialogTitle);
475  }
476  else
477  {
478  this->set_title(titlePrefix);
479  m_sTag = "allextensions"; // Means empty parameter
480  }
481 
482  OUString sEncodedURLPart = INetURLObject::encode(m_sTag, INetURLObject::PART_PCHAR,
484 
485  //FIXME: Temporary URL - v0 is not using actual api
486  OUString rURL = "https://extensions.libreoffice.org/api/v0/" + sEncodedURLPart + ".json";
487  m_sURL = rURL;
488 
490  = deployment::ExtensionManager::get(::comphelper::getProcessComponentContext());
491 
492  //Initialize search util
493  m_searchOptions.AlgorithmType2 = css::util::SearchAlgorithms2::ABSOLUTE;
494  m_searchOptions.transliterateFlags |= TransliterationFlags::IGNORE_CASE;
495  m_searchOptions.searchFlag |= (css::util::SearchFlags::REG_NOT_BEGINOFLINE
496  | css::util::SearchFlags::REG_NOT_ENDOFLINE);
497  m_pSearchThread = new SearchAndParseThread(this, true);
498  m_pSearchThread->launch();
499 }
500 
502 {
503  if (m_pSearchThread.is())
504  {
505  m_pSearchThread->StopExecution();
506  // Release the solar mutex, so the thread is not affected by the race
507  // when it's after the m_bExecute check but before taking the solar
508  // mutex.
509  SolarMutexReleaser aReleaser;
510  m_pSearchThread->join();
511  }
512 }
513 
514 uno::Sequence<uno::Sequence<uno::Reference<deployment::XPackage>>>
516 {
517  uno::Sequence<uno::Sequence<uno::Reference<deployment::XPackage>>> xAllPackages;
518 
519  try
520  {
521  xAllPackages = m_xExtensionManager->getAllExtensions(
522  uno::Reference<task::XAbortChannel>(), uno::Reference<ucb::XCommandEnvironment>());
523  }
524  catch (const deployment::DeploymentException&)
525  {
526  TOOLS_WARN_EXCEPTION("cui.dialogs", "");
527  }
528  catch (const ucb::CommandFailedException&)
529  {
530  TOOLS_WARN_EXCEPTION("cui.dialogs", "");
531  }
532  catch (const ucb::CommandAbortedException&)
533  {
534  TOOLS_WARN_EXCEPTION("cui.dialogs", "");
535  }
536  catch (const lang::IllegalArgumentException& e)
537  {
538  css::uno::Any anyEx = cppu::getCaughtException();
539  throw css::lang::WrappedTargetRuntimeException(e.Message, e.Context, anyEx);
540  }
541  return xAllPackages;
542 }
543 
544 void AdditionsDialog::SetProgress(const OUString& rProgress)
545 {
546  if (rProgress.isEmpty())
547  {
548  m_xLabelProgress->hide();
549  m_xButtonClose->set_sensitive(true);
550  }
551  else
552  {
553  SolarMutexGuard aGuard;
554  m_xLabelProgress->show();
555  m_xLabelProgress->set_label(rProgress);
556  m_xDialog->resize_to_request(); //TODO
557  }
558 }
559 
561 {
562  // for VCL to be able to destroy bitmaps
563  SolarMutexGuard aGuard;
564 
565  for (auto& item : this->m_aAdditionsItems)
566  {
567  item->m_xContainer->hide();
568  }
569  this->m_aAdditionsItems.clear();
570 }
571 
573 {
574  if (m_pSearchThread.is())
575  m_pSearchThread->StopExecution();
576  ClearList();
579  m_pSearchThread = new SearchAndParseThread(this, false);
580  m_pSearchThread->launch();
581 }
582 
584 {
585  return a.sRating.toDouble() > b.sRating.toDouble();
586 }
587 
589 {
590  return a.sCommentNumber.toUInt32() > b.sCommentNumber.toUInt32();
591 }
592 
594 {
595  return a.sDownloadNumber.toUInt32() > b.sDownloadNumber.toUInt32();
596 }
597 
599  const AdditionInfo& additionInfo)
600  : m_xBuilder(Application::CreateBuilder(pParent, "cui/ui/additionsfragment.ui"))
601  , m_xContainer(m_xBuilder->weld_widget("additionsEntry"))
602  , m_xImageScreenshot(m_xBuilder->weld_image("imageScreenshot"))
603  , m_xButtonInstall(m_xBuilder->weld_button("buttonInstall"))
604  , m_xLinkButtonWebsite(m_xBuilder->weld_link_button("btnWebsite"))
605  , m_xLabelName(m_xBuilder->weld_label("lbName"))
606  , m_xLabelAuthor(m_xBuilder->weld_label("labelAuthor"))
607  , m_xLabelDesc(m_xBuilder->weld_label("labelDesc")) // no change (print description)
608  , m_xLabelDescription(m_xBuilder->weld_label("labelDescription"))
609  , m_xLabelLicense(m_xBuilder->weld_label("lbLicenseText"))
610  , m_xLabelVersion(m_xBuilder->weld_label("lbVersionText"))
611  , m_xLabelComments(m_xBuilder->weld_label("labelComments")) // no change
612  , m_xLinkButtonComments(m_xBuilder->weld_link_button("linkButtonComments"))
613  , m_xImageVoting1(m_xBuilder->weld_image("imageVoting1"))
614  , m_xImageVoting2(m_xBuilder->weld_image("imageVoting2"))
615  , m_xImageVoting3(m_xBuilder->weld_image("imageVoting3"))
616  , m_xImageVoting4(m_xBuilder->weld_image("imageVoting4"))
617  , m_xImageVoting5(m_xBuilder->weld_image("imageVoting5"))
618  , m_xLabelNoVoting(m_xBuilder->weld_label("votingLabel"))
619  , m_xImageDownloadNumber(m_xBuilder->weld_image("imageDownloadNumber"))
620  , m_xLabelDownloadNumber(m_xBuilder->weld_label("labelDownloadNumber"))
621  , m_xButtonShowMore(m_xBuilder->weld_button("buttonShowMore"))
622  , m_pParentDialog(pParentDialog)
623  , m_sDownloadURL("")
624  , m_sExtensionID("")
625 {
626  SolarMutexGuard aGuard;
627 
628  // AdditionsItem set location
629  m_xContainer->set_grid_left_attach(0);
630  m_xContainer->set_grid_top_attach(pParentDialog->m_aAdditionsItems.size());
631 
632  // Set maximum length of the extension title
633  OUString sExtensionName;
634  const sal_Int32 maxExtensionNameLength = 30;
635 
636  if (additionInfo.sName.getLength() > maxExtensionNameLength)
637  {
638  OUString sShortName = additionInfo.sName.copy(0, maxExtensionNameLength - 3);
639  sExtensionName = sShortName + "...";
640  }
641  else
642  {
643  sExtensionName = additionInfo.sName;
644  }
645 
646  m_xLabelName->set_label(sExtensionName);
647 
648  double aExtensionRating = additionInfo.sRating.toDouble();
649  switch (std::isnan(aExtensionRating) ? 0 : int(std::clamp(aExtensionRating, 0.0, 5.0)))
650  {
651  case 5:
652  m_xImageVoting5->set_from_icon_name("cmd/sc_stars-full.png");
653  [[fallthrough]];
654  case 4:
655  m_xImageVoting4->set_from_icon_name("cmd/sc_stars-full.png");
656  [[fallthrough]];
657  case 3:
658  m_xImageVoting3->set_from_icon_name("cmd/sc_stars-full.png");
659  [[fallthrough]];
660  case 2:
661  m_xImageVoting2->set_from_icon_name("cmd/sc_stars-full.png");
662  [[fallthrough]];
663  case 1:
664  m_xImageVoting1->set_from_icon_name("cmd/sc_stars-full.png");
665  break;
666  }
667 
668  m_xLinkButtonWebsite->set_uri(additionInfo.sExtensionURL);
669  m_xLabelDescription->set_label(additionInfo.sIntroduction);
670 
671  if (!additionInfo.sAuthorName.equalsIgnoreAsciiCase("null"))
672  m_xLabelAuthor->set_label(additionInfo.sAuthorName);
673 
674  m_xButtonInstall->set_label(CuiResId(RID_CUISTR_ADDITIONS_INSTALLBUTTON));
675  m_xLabelLicense->set_label(additionInfo.sLicense);
676  m_xLabelVersion->set_label(">=" + additionInfo.sCompatibleVersion);
677  m_xLinkButtonComments->set_label(additionInfo.sCommentNumber);
678  m_xLinkButtonComments->set_uri(additionInfo.sCommentURL);
679  m_xLabelDownloadNumber->set_label(additionInfo.sDownloadNumber);
680  m_pParentDialog = pParentDialog;
681  m_sDownloadURL = additionInfo.sDownloadURL;
682  m_sExtensionID = additionInfo.sExtensionID;
683 
684  m_xButtonShowMore->connect_clicked(LINK(this, AdditionsItem, ShowMoreHdl));
685  m_xButtonInstall->connect_clicked(LINK(this, AdditionsItem, InstallHdl));
686 }
687 
688 bool AdditionsItem::getExtensionFile(OUString& sExtensionFile)
689 {
690  uno::Reference<ucb::XSimpleFileAccess3> xFileAccess
691  = ucb::SimpleFileAccess::create(comphelper::getProcessComponentContext());
692 
693  // copy the extensions' files to the user's additions folder
694  OUString userFolder = "${$BRAND_BASE_DIR/" LIBO_ETC_FOLDER
695  "/" SAL_CONFIGFILE("bootstrap") "::UserInstallation}";
696  rtl::Bootstrap::expandMacros(userFolder);
697  userFolder += "/user/additions/" + m_sExtensionID + "/";
698 
699  OUString aExtensionsFile(INetURLObject(m_sDownloadURL).getName());
700  OUString aExtensionsURL = m_sDownloadURL;
701 
702  try
703  {
704  osl::Directory::createPath(userFolder);
705 
706  if (!xFileAccess->exists(userFolder + aExtensionsFile))
707  ucbDownload(aExtensionsURL, userFolder, aExtensionsFile);
708  }
709  catch (const uno::Exception&)
710  {
711  return false;
712  }
713  sExtensionFile = userFolder + aExtensionsFile;
714  return true;
715 }
716 
717 IMPL_LINK_NOARG(AdditionsDialog, ImplUpdateDataHdl, Timer*, void) { RefreshUI(); }
718 
719 IMPL_LINK_NOARG(AdditionsDialog, SearchUpdateHdl, weld::Entry&, void)
720 {
721  m_aSearchDataTimer.Start();
722 }
723 
725 {
726  if (m_aSearchDataTimer.IsActive())
727  {
728  m_aSearchDataTimer.Stop();
729  m_aSearchDataTimer.Invoke();
730  }
731 }
732 
734 {
735  if (m_pSearchThread.is())
736  m_pSearchThread->StopExecution();
737  this->response(RET_CLOSE);
738 }
739 
741 {
742  this->m_xButtonShowMore->set_visible(false);
743  m_pParentDialog->m_nMaxItemCount += PAGE_SIZE;
744  if (m_pParentDialog->m_pSearchThread.is())
745  m_pParentDialog->m_pSearchThread->StopExecution();
746  m_pParentDialog->m_pSearchThread = new SearchAndParseThread(m_pParentDialog, false);
747  m_pParentDialog->m_pSearchThread->launch();
748 }
749 
751 {
752  m_xButtonInstall->set_label(CuiResId(RID_CUISTR_ADDITIONS_INSTALLING));
753  m_xButtonInstall->set_sensitive(false);
754  OUString aExtensionFile;
755  bool bResult = getExtensionFile(aExtensionFile); // info vector json data
756 
757  if (!bResult)
758  {
759  m_xButtonInstall->set_label(CuiResId(RID_CUISTR_ADDITIONS_INSTALLBUTTON));
760  m_xButtonInstall->set_sensitive(true);
761 
762  SAL_INFO("cui.dialogs", "Couldn't get the extension file.");
763  return;
764  }
765 
767  uno::Reference<task::XAbortChannel> xAbortChannel;
768  try
769  {
770  m_pParentDialog->m_xExtensionManager->addExtension(
771  aExtensionFile, uno::Sequence<beans::NamedValue>(), "user", xAbortChannel, pCmdEnv);
772  m_xButtonInstall->set_label(CuiResId(RID_CUISTR_ADDITIONS_INSTALLEDBUTTON));
773  }
774  catch (const ucb::CommandFailedException)
775  {
776  TOOLS_WARN_EXCEPTION("cui.dialogs", "");
777  m_xButtonInstall->set_label(CuiResId(RID_CUISTR_ADDITIONS_INSTALLBUTTON));
778  m_xButtonInstall->set_sensitive(true);
779  }
780  catch (const ucb::CommandAbortedException)
781  {
782  TOOLS_WARN_EXCEPTION("cui.dialogs", "");
783  m_xButtonInstall->set_label(CuiResId(RID_CUISTR_ADDITIONS_INSTALLBUTTON));
784  m_xButtonInstall->set_sensitive(true);
785  }
786  catch (const deployment::DeploymentException)
787  {
788  TOOLS_WARN_EXCEPTION("cui.dialogs", "");
789  m_xButtonInstall->set_label(CuiResId(RID_CUISTR_ADDITIONS_INSTALLBUTTON));
790  m_xButtonInstall->set_sensitive(true);
791  }
792  catch (const lang::IllegalArgumentException)
793  {
794  TOOLS_WARN_EXCEPTION("cui.dialogs", "");
795  m_xButtonInstall->set_label(CuiResId(RID_CUISTR_ADDITIONS_INSTALLBUTTON));
796  m_xButtonInstall->set_sensitive(true);
797  }
798  catch (const css::uno::Exception)
799  {
800  TOOLS_WARN_EXCEPTION("cui.dialogs", "");
801  m_xButtonInstall->set_label(CuiResId(RID_CUISTR_ADDITIONS_INSTALLBUTTON));
802  m_xButtonInstall->set_sensitive(true);
803  }
804 }
805 
806 // TmpRepositoryCommandEnv
807 
809 
811 // XCommandEnvironment
812 
813 uno::Reference<task::XInteractionHandler> TmpRepositoryCommandEnv::getInteractionHandler()
814 {
815  return this;
816 }
817 
818 uno::Reference<ucb::XProgressHandler> TmpRepositoryCommandEnv::getProgressHandler() { return this; }
819 
820 // XInteractionHandler
821 void TmpRepositoryCommandEnv::handle(uno::Reference<task::XInteractionRequest> const& xRequest)
822 {
823  OSL_ASSERT(xRequest->getRequest().getValueTypeClass() == uno::TypeClass_EXCEPTION);
824 
825  bool approve = true;
826 
827  // select:
828  uno::Sequence<Reference<task::XInteractionContinuation>> conts(xRequest->getContinuations());
829  Reference<task::XInteractionContinuation> const* pConts = conts.getConstArray();
830  sal_Int32 len = conts.getLength();
831  for (sal_Int32 pos = 0; pos < len; ++pos)
832  {
833  if (approve)
834  {
835  uno::Reference<task::XInteractionApprove> xInteractionApprove(pConts[pos],
836  uno::UNO_QUERY);
837  if (xInteractionApprove.is())
838  {
839  xInteractionApprove->select();
840  // don't query again for ongoing continuations:
841  approve = false;
842  }
843  }
844  }
845 }
846 
847 // XProgressHandler
848 void TmpRepositoryCommandEnv::push(uno::Any const& /*Status*/) {}
849 
850 void TmpRepositoryCommandEnv::update(uno::Any const& /*Status */) {}
851 
853 
854 IMPL_LINK(AdditionsDialog, GearHdl, const OString&, rIdent, void)
855 {
856  if (rIdent == "gear_sort_voting")
857  {
858  std::sort(m_aAllExtensionsVector.begin(), m_aAllExtensionsVector.end(), sortByRating);
859  }
860  else if (rIdent == "gear_sort_comments")
861  {
862  std::sort(m_aAllExtensionsVector.begin(), m_aAllExtensionsVector.end(), sortByComment);
863  }
864  else if (rIdent == "gear_sort_downloads")
865  {
866  std::sort(m_aAllExtensionsVector.begin(), m_aAllExtensionsVector.end(), sortByDownload);
867  }
868  // After the sorting, UI will be refreshed to update extension list.
869  RefreshUI();
870 }
871 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
std::shared_ptr< weld::Dialog > m_xDialog
AdditionsDialog * m_pAdditionsDialog
virtual css::uno::Reference< css::ucb::XProgressHandler > SAL_CALL getProgressHandler() override
std::unique_ptr< weld::Label > m_xLabelProgress
void set_title(const OUString &rTitle)
std::unique_ptr< weld::Image > m_xImageVoting4
virtual css::uno::Reference< css::task::XInteractionHandler > SAL_CALL getInteractionHandler() override
ErrCode ImportGraphic(Graphic &rGraphic, const INetURLObject &rPath, sal_uInt16 nFormat=GRFILTER_FORMAT_DONTKNOW, sal_uInt16 *pDeterminedFormat=nullptr, GraphicFilterImportFlags nImportFlags=GraphicFilterImportFlags::NONE)
SearchAndParseThread(AdditionsDialog *pDialog, bool bIsFirstLoading)
signed char sal_Int8
std::unique_ptr< weld::MenuButton > m_xGearBtn
bool getExtensionFile(OUString &sExtensionFile)
bool Scale(const Size &rNewSize, BmpScaleFlag nScaleFlag=BmpScaleFlag::Default)
OUString sCommentURL
virtual void SAL_CALL push(css::uno::Any const &Status) override
sal_Int64 n
size_t m_nCurrentListItemCount
static bool sortByComment(const AdditionInfo &a, const AdditionInfo &b)
::rtl::Reference< SearchAndParseThread > m_pSearchThread
OUString sScreenshotURL
std::unique_ptr< weld::Image > m_xImageVoting3
std::unique_ptr< weld::Button > m_xButtonClose
i18nutil::SearchOptions2 m_searchOptions
static bool sortByRating(const AdditionInfo &a, const AdditionInfo &b)
OUString sIntroduction
std::unique_ptr< weld::Image > m_xImageVoting5
std::unique_ptr< weld::LinkButton > m_xLinkButtonWebsite
constexpr tools::Long Width() const
Reference< XNameAccess > m_xContainer
OUString sExtensionURL
static std::unique_ptr< SvStream > CreateStream(const OUString &rFileName, StreamMode eOpenMode, css::uno::Reference< css::awt::XWindow > xParentWin=nullptr)
std::unique_ptr< weld::Button > m_xButtonShowMore
Any SAL_CALL getCaughtException()
std::atomic< bool > m_bExecute
size_t pos
virtual void SAL_CALL pop() override
std::unique_ptr< weld::Button > m_xButtonInstall
std::unique_ptr< weld::Label > m_xLabelAuthor
OUString CuiResId(TranslateId aKey)
Definition: cuiresmgr.cxx:23
std::unique_ptr< weld::Entry > m_xEntrySearch
bool IsEmpty() const
#define SAL_CONFIGFILE(name)
#define TOOLS_WARN_EXCEPTION(area, stream)
OUString sDownloadNumber
int i
std::vector< std::shared_ptr< AdditionsItem > > m_aAdditionsItems
OUString getName(sal_Int32 nIndex=LAST_SEGMENT, bool bIgnoreFinalSlash=true, DecodeMechanism eMechanism=DecodeMechanism::ToIUri, rtl_TextEncoding eCharset=RTL_TEXTENCODING_UTF8) const
TransliterationFlags transliterateFlags
AdditionsItem(weld::Widget *pParent, AdditionsDialog *pParentDialog, const AdditionInfo &additionInfo)
OUString sExtensionID
~AdditionsDialog() override
std::unique_ptr< weld::Label > m_xLabelVersion
bool searchForward(const OUString &rStr)
std::unique_ptr< weld::Label > m_xLabelLicense
BitmapEx GetBitmapEx(const GraphicConversionParameters &rParameters=GraphicConversionParameters()) const
std::unique_ptr< weld::Label > m_xLabelDownloadNumber
void SetTimeout(sal_uInt64 nTimeoutMs)
OUString sCompatibleVersion
OUString m_sDownloadURL
AdditionsDialog * m_pParentDialog
virtual void SAL_CALL handle(css::uno::Reference< css::task::XInteractionRequest > const &xRequest) override
IMPL_LINK(AdditionsDialog, GearHdl, const OString &, rIdent, void)
std::unique_ptr< weld::Widget > m_xContainer
css::uno::Sequence< css::uno::Sequence< css::uno::Reference< css::deployment::XPackage > > > getInstalledExtensions()
RET_CLOSE
virtual ~TmpRepositoryCommandEnv() override
virtual void SAL_CALL update(css::uno::Any const &Status) override
std::unique_ptr< weld::Image > m_xImageVoting2
void Append(AdditionInfo &additionInfo)
constexpr tools::Long Height() const
#define EDIT_UPDATEDATA_TIMEOUT
const ::std::vector< Color > ImpSvNumberformatScan::StandardColor COL_WHITE
#define SAL_INFO(area, stream)
IMPL_LINK_NOARG(AdditionsDialog, ImplUpdateDataHdl, Timer *, void)
std::unique_ptr< weld::Container > m_xContentGrid
css::uno::Reference< css::deployment::XExtensionManager > m_xExtensionManager
Reference< XComponentContext > getProcessComponentContext()
void SetInvokeHandler(const Link< Timer *, void > &rLink)
virtual ~SearchAndParseThread() override
std::unique_ptr< weld::LinkButton > m_xLinkButtonComments
std::unique_ptr< weld::Label > m_xLabelName
#define PAGE_SIZE
void SetProgress(const OUString &rProgress)
VirtualDevice * get() const
std::unique_ptr< weld::Image > m_xImageVoting1
#define SAL_WARN(area, stream)
std::unique_ptr< weld::Label > m_xLabelDescription
OUString sAuthorName
static bool sortByDownload(const AdditionInfo &a, const AdditionInfo &b)
AdditionsDialog(weld::Window *pParent, const OUString &sAdditionsTag)
OUString sCommentNumber
OUString sDownloadURL
const Size & GetSizePixel() const
virtual void execute() override
OUString m_sExtensionID
std::vector< AdditionInfo > m_aAllExtensionsVector
static OUString encode(std::u16string_view rText, Part ePart, EncodeMechanism eMechanism, rtl_TextEncoding eCharset=RTL_TEXTENCODING_UTF8)