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  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("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));
467  m_aSearchDataTimer.SetDebugName("AdditionsDialog SearchDataTimer");
469 
470  m_xEntrySearch->connect_changed(LINK(this, AdditionsDialog, SearchUpdateHdl));
471  m_xEntrySearch->connect_focus_out(LINK(this, AdditionsDialog, FocusOut_Impl));
472  m_xButtonClose->connect_clicked(LINK(this, AdditionsDialog, CloseButtonHdl));
473 
474  m_sTag = sAdditionsTag;
475  m_nMaxItemCount = PAGE_SIZE; // Dialog initialization item count
476  m_nCurrentListItemCount = 0; // First, there is no item on the list.
477 
478  OUString titlePrefix = CuiResId(RID_SVXSTR_ADDITIONS_DIALOG_TITLE_PREFIX);
479  if (!m_sTag.isEmpty())
480  {
481  this->set_title(titlePrefix + ": " + sAdditionsTag);
482  }
483  else
484  {
485  this->set_title(titlePrefix);
486  m_sTag = "allextensions"; // Means empty parameter
487  }
488  //FIXME: Temporary URL - v0 is not using actual api
489  OUString rURL = "https://extensions.libreoffice.org/api/v0/" + m_sTag + ".json";
490  m_sURL = rURL;
491 
493  = deployment::ExtensionManager::get(::comphelper::getProcessComponentContext());
494 
495  //Initialize search util
496  m_searchOptions.AlgorithmType2 = css::util::SearchAlgorithms2::ABSOLUTE;
497  m_searchOptions.transliterateFlags |= TransliterationFlags::IGNORE_CASE;
498  m_searchOptions.searchFlag |= (css::util::SearchFlags::REG_NOT_BEGINOFLINE
499  | css::util::SearchFlags::REG_NOT_ENDOFLINE);
500  m_pSearchThread = new SearchAndParseThread(this, true);
501  m_pSearchThread->launch();
502 }
503 
505 {
506  if (m_pSearchThread.is())
507  {
508  m_pSearchThread->StopExecution();
509  // Release the solar mutex, so the thread is not affected by the race
510  // when it's after the m_bExecute check but before taking the solar
511  // mutex.
512  SolarMutexReleaser aReleaser;
513  m_pSearchThread->join();
514  }
515 }
516 
517 uno::Sequence<uno::Sequence<uno::Reference<deployment::XPackage>>>
519 {
520  uno::Sequence<uno::Sequence<uno::Reference<deployment::XPackage>>> xAllPackages;
521 
522  try
523  {
524  xAllPackages = m_xExtensionManager->getAllExtensions(
525  uno::Reference<task::XAbortChannel>(), uno::Reference<ucb::XCommandEnvironment>());
526  }
527  catch (const deployment::DeploymentException&)
528  {
529  TOOLS_WARN_EXCEPTION("cui.dialogs", "");
530  }
531  catch (const ucb::CommandFailedException&)
532  {
533  TOOLS_WARN_EXCEPTION("cui.dialogs", "");
534  }
535  catch (const ucb::CommandAbortedException&)
536  {
537  TOOLS_WARN_EXCEPTION("cui.dialogs", "");
538  }
539  catch (const lang::IllegalArgumentException& e)
540  {
541  css::uno::Any anyEx = cppu::getCaughtException();
542  throw css::lang::WrappedTargetRuntimeException(e.Message, e.Context, anyEx);
543  }
544  return xAllPackages;
545 }
546 
547 void AdditionsDialog::SetProgress(const OUString& rProgress)
548 {
549  if (rProgress.isEmpty())
550  {
551  m_xLabelProgress->hide();
552  m_xButtonClose->set_sensitive(true);
553  }
554  else
555  {
556  SolarMutexGuard aGuard;
557  m_xLabelProgress->show();
558  m_xLabelProgress->set_label(rProgress);
559  m_xDialog->resize_to_request(); //TODO
560  }
561 }
562 
564 {
565  // for VCL to be able to destroy bitmaps
566  SolarMutexGuard aGuard;
567 
568  for (auto& item : this->m_aAdditionsItems)
569  {
570  item->m_xContainer->hide();
571  }
572  this->m_aAdditionsItems.clear();
573 }
574 
576 {
577  if (m_pSearchThread.is())
578  m_pSearchThread->StopExecution();
579  ClearList();
582  m_pSearchThread = new SearchAndParseThread(this, false);
583  m_pSearchThread->launch();
584 }
585 
587 {
588  return a.sRating.toDouble() > b.sRating.toDouble();
589 }
590 
592 {
593  return a.sCommentNumber.toUInt32() > b.sCommentNumber.toUInt32();
594 }
595 
597 {
598  return a.sDownloadNumber.toUInt32() > b.sDownloadNumber.toUInt32();
599 }
600 
602  const AdditionInfo& additionInfo)
603  : m_xBuilder(Application::CreateBuilder(pParent, "cui/ui/additionsfragment.ui"))
604  , m_xContainer(m_xBuilder->weld_widget("additionsEntry"))
605  , m_xImageScreenshot(m_xBuilder->weld_image("imageScreenshot"))
606  , m_xButtonInstall(m_xBuilder->weld_button("buttonInstall"))
607  , m_xLinkButtonWebsite(m_xBuilder->weld_link_button("btnWebsite"))
608  , m_xLabelName(m_xBuilder->weld_label("lbName"))
609  , m_xLabelAuthor(m_xBuilder->weld_label("labelAuthor"))
610  , m_xLabelDesc(m_xBuilder->weld_label("labelDesc")) // no change (print description)
611  , m_xLabelDescription(m_xBuilder->weld_label("labelDescription"))
612  , m_xLabelLicense(m_xBuilder->weld_label("lbLicenseText"))
613  , m_xLabelVersion(m_xBuilder->weld_label("lbVersionText"))
614  , m_xLabelComments(m_xBuilder->weld_label("labelComments")) // no change
615  , m_xLinkButtonComments(m_xBuilder->weld_link_button("linkButtonComments"))
616  , m_xImageVoting1(m_xBuilder->weld_image("imageVoting1"))
617  , m_xImageVoting2(m_xBuilder->weld_image("imageVoting2"))
618  , m_xImageVoting3(m_xBuilder->weld_image("imageVoting3"))
619  , m_xImageVoting4(m_xBuilder->weld_image("imageVoting4"))
620  , m_xImageVoting5(m_xBuilder->weld_image("imageVoting5"))
621  , m_xLabelNoVoting(m_xBuilder->weld_label("votingLabel"))
622  , m_xImageDownloadNumber(m_xBuilder->weld_image("imageDownloadNumber"))
623  , m_xLabelDownloadNumber(m_xBuilder->weld_label("labelDownloadNumber"))
624  , m_xButtonShowMore(m_xBuilder->weld_button("buttonShowMore"))
625  , m_pParentDialog(pParentDialog)
626  , m_sDownloadURL("")
627  , m_sExtensionID("")
628 {
629  SolarMutexGuard aGuard;
630 
631  // AdditionsItem set location
632  m_xContainer->set_grid_left_attach(0);
633  m_xContainer->set_grid_top_attach(pParentDialog->m_aAdditionsItems.size());
634 
635  // Set maximum length of the extension title
636  OUString sExtensionName;
637  const sal_Int32 maxExtensionNameLength = 30;
638 
639  if (additionInfo.sName.getLength() > maxExtensionNameLength)
640  {
641  OUString sShortName = additionInfo.sName.copy(0, maxExtensionNameLength - 3);
642  sExtensionName = sShortName + "...";
643  }
644  else
645  {
646  sExtensionName = additionInfo.sName;
647  }
648 
649  m_xLabelName->set_label(sExtensionName);
650 
651  double aExtensionRating = additionInfo.sRating.toDouble();
652  switch (std::isnan(aExtensionRating) ? 0 : int(std::clamp(aExtensionRating, 0.0, 5.0)))
653  {
654  case 5:
655  m_xImageVoting5->set_from_icon_name("cmd/sc_stars-full.png");
656  [[fallthrough]];
657  case 4:
658  m_xImageVoting4->set_from_icon_name("cmd/sc_stars-full.png");
659  [[fallthrough]];
660  case 3:
661  m_xImageVoting3->set_from_icon_name("cmd/sc_stars-full.png");
662  [[fallthrough]];
663  case 2:
664  m_xImageVoting2->set_from_icon_name("cmd/sc_stars-full.png");
665  [[fallthrough]];
666  case 1:
667  m_xImageVoting1->set_from_icon_name("cmd/sc_stars-full.png");
668  break;
669  }
670 
671  m_xLinkButtonWebsite->set_uri(additionInfo.sExtensionURL);
672  m_xLabelDescription->set_label(additionInfo.sIntroduction);
673 
674  if (!additionInfo.sAuthorName.equalsIgnoreAsciiCase("null"))
675  m_xLabelAuthor->set_label(additionInfo.sAuthorName);
676 
677  m_xButtonInstall->set_label(CuiResId(RID_SVXSTR_ADDITIONS_INSTALLBUTTON));
678  m_xLabelLicense->set_label(additionInfo.sLicense);
679  m_xLabelVersion->set_label(">=" + additionInfo.sCompatibleVersion);
680  m_xLinkButtonComments->set_label(additionInfo.sCommentNumber);
681  m_xLinkButtonComments->set_uri(additionInfo.sCommentURL);
682  m_xLabelDownloadNumber->set_label(additionInfo.sDownloadNumber);
683  m_pParentDialog = pParentDialog;
684  m_sDownloadURL = additionInfo.sDownloadURL;
685  m_sExtensionID = additionInfo.sExtensionID;
686 
687  m_xButtonShowMore->connect_clicked(LINK(this, AdditionsItem, ShowMoreHdl));
688  m_xButtonInstall->connect_clicked(LINK(this, AdditionsItem, InstallHdl));
689 }
690 
691 bool AdditionsItem::getExtensionFile(OUString& sExtensionFile)
692 {
693  uno::Reference<ucb::XSimpleFileAccess3> xFileAccess
694  = ucb::SimpleFileAccess::create(comphelper::getProcessComponentContext());
695 
696  // copy the extensions' files to the user's additions folder
697  OUString userFolder = "${$BRAND_BASE_DIR/" LIBO_ETC_FOLDER
698  "/" SAL_CONFIGFILE("bootstrap") "::UserInstallation}";
699  rtl::Bootstrap::expandMacros(userFolder);
700  userFolder += "/user/additions/" + m_sExtensionID + "/";
701 
702  OUString aExtesionsFile(INetURLObject(m_sDownloadURL).getName());
703  OUString aExtesionsURL = m_sDownloadURL;
704 
705  try
706  {
707  osl::Directory::createPath(userFolder);
708 
709  if (!xFileAccess->exists(userFolder + aExtesionsFile))
710  ucbDownload(aExtesionsURL, userFolder, aExtesionsFile);
711  }
712  catch (const uno::Exception&)
713  {
714  return false;
715  }
716  sExtensionFile = userFolder + aExtesionsFile;
717  return true;
718 }
719 
720 IMPL_LINK_NOARG(AdditionsDialog, ImplUpdateDataHdl, Timer*, void) { RefreshUI(); }
721 
722 IMPL_LINK_NOARG(AdditionsDialog, SearchUpdateHdl, weld::Entry&, void)
723 {
724  m_aSearchDataTimer.Start();
725 }
726 
728 {
729  if (m_aSearchDataTimer.IsActive())
730  {
731  m_aSearchDataTimer.Stop();
732  m_aSearchDataTimer.Invoke();
733  }
734 }
735 
737 {
738  if (m_pSearchThread.is())
739  m_pSearchThread->StopExecution();
740  this->response(RET_CLOSE);
741 }
742 
744 {
745  this->m_xButtonShowMore->set_visible(false);
746  m_pParentDialog->m_nMaxItemCount += PAGE_SIZE;
747  if (m_pParentDialog->m_pSearchThread.is())
748  m_pParentDialog->m_pSearchThread->StopExecution();
749  m_pParentDialog->m_pSearchThread = new SearchAndParseThread(m_pParentDialog, false);
750  m_pParentDialog->m_pSearchThread->launch();
751 }
752 
754 {
755  m_xButtonInstall->set_label(CuiResId(RID_SVXSTR_ADDITIONS_INSTALLING));
756  m_xButtonInstall->set_sensitive(false);
757  OUString aExtensionFile;
758  bool bResult = getExtensionFile(aExtensionFile); // info vector json data
759 
760  if (!bResult)
761  {
762  m_xButtonInstall->set_label(CuiResId(RID_SVXSTR_ADDITIONS_INSTALLBUTTON));
763  m_xButtonInstall->set_sensitive(true);
764 
765  SAL_INFO("cui.dialogs", "Couldn't get the extension file.");
766  return;
767  }
768 
770  uno::Reference<task::XAbortChannel> xAbortChannel;
771  try
772  {
773  m_pParentDialog->m_xExtensionManager->addExtension(
774  aExtensionFile, uno::Sequence<beans::NamedValue>(), "user", xAbortChannel, pCmdEnv);
775  m_xButtonInstall->set_label(CuiResId(RID_SVXSTR_ADDITIONS_INSTALLEDBUTTON));
776  }
777  catch (const ucb::CommandFailedException)
778  {
779  TOOLS_WARN_EXCEPTION("cui.dialogs", "");
780  m_xButtonInstall->set_label(CuiResId(RID_SVXSTR_ADDITIONS_INSTALLBUTTON));
781  m_xButtonInstall->set_sensitive(true);
782  }
783  catch (const ucb::CommandAbortedException)
784  {
785  TOOLS_WARN_EXCEPTION("cui.dialogs", "");
786  m_xButtonInstall->set_label(CuiResId(RID_SVXSTR_ADDITIONS_INSTALLBUTTON));
787  m_xButtonInstall->set_sensitive(true);
788  }
789  catch (const deployment::DeploymentException)
790  {
791  TOOLS_WARN_EXCEPTION("cui.dialogs", "");
792  m_xButtonInstall->set_label(CuiResId(RID_SVXSTR_ADDITIONS_INSTALLBUTTON));
793  m_xButtonInstall->set_sensitive(true);
794  }
795  catch (const lang::IllegalArgumentException)
796  {
797  TOOLS_WARN_EXCEPTION("cui.dialogs", "");
798  m_xButtonInstall->set_label(CuiResId(RID_SVXSTR_ADDITIONS_INSTALLBUTTON));
799  m_xButtonInstall->set_sensitive(true);
800  }
801  catch (const css::uno::Exception)
802  {
803  TOOLS_WARN_EXCEPTION("cui.dialogs", "");
804  m_xButtonInstall->set_label(CuiResId(RID_SVXSTR_ADDITIONS_INSTALLBUTTON));
805  m_xButtonInstall->set_sensitive(true);
806  }
807 }
808 
809 // TmpRepositoryCommandEnv
810 
812 
814 // XCommandEnvironment
815 
816 uno::Reference<task::XInteractionHandler> TmpRepositoryCommandEnv::getInteractionHandler()
817 {
818  return this;
819 }
820 
821 uno::Reference<ucb::XProgressHandler> TmpRepositoryCommandEnv::getProgressHandler() { return this; }
822 
823 // XInteractionHandler
824 void TmpRepositoryCommandEnv::handle(uno::Reference<task::XInteractionRequest> const& xRequest)
825 {
826  OSL_ASSERT(xRequest->getRequest().getValueTypeClass() == uno::TypeClass_EXCEPTION);
827 
828  bool approve = true;
829 
830  // select:
831  uno::Sequence<Reference<task::XInteractionContinuation>> conts(xRequest->getContinuations());
832  Reference<task::XInteractionContinuation> const* pConts = conts.getConstArray();
833  sal_Int32 len = conts.getLength();
834  for (sal_Int32 pos = 0; pos < len; ++pos)
835  {
836  if (approve)
837  {
838  uno::Reference<task::XInteractionApprove> xInteractionApprove(pConts[pos],
839  uno::UNO_QUERY);
840  if (xInteractionApprove.is())
841  {
842  xInteractionApprove->select();
843  // don't query again for ongoing continuations:
844  approve = false;
845  }
846  }
847  }
848 }
849 
850 // XProgressHandler
851 void TmpRepositoryCommandEnv::push(uno::Any const& /*Status*/) {}
852 
853 void TmpRepositoryCommandEnv::update(uno::Any const& /*Status */) {}
854 
856 
857 IMPL_LINK(AdditionsDialog, GearHdl, const OString&, rIdent, void)
858 {
859  if (rIdent == "gear_sort_voting")
860  {
861  std::sort(m_aAllExtensionsVector.begin(), m_aAllExtensionsVector.end(), sortByRating);
862  }
863  else if (rIdent == "gear_sort_comments")
864  {
865  std::sort(m_aAllExtensionsVector.begin(), m_aAllExtensionsVector.end(), sortByComment);
866  }
867  else if (rIdent == "gear_sort_downloads")
868  {
869  std::sort(m_aAllExtensionsVector.begin(), m_aAllExtensionsVector.end(), sortByDownload);
870  }
871  // After the sorting, UI will be refreshed to update extension list.
872  RefreshUI();
873 }
874 /* 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
void SetDebugName(const char *pDebugName)
std::unique_ptr< weld::Label > m_xLabelAuthor
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
OUString CuiResId(const char *pKey)
Definition: cuiresmgr.cxx:23
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