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