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