OpenCPN Partial API docs
update_mgr.cpp
1 /******************************************************************************
2  *
3  * Project: OpenCPN
4  *
5  ***************************************************************************
6  * Copyright (C) 2019 Alec Leamas *
7  * *
8  * This program is free software; you can redistribute it and/or modify *
9  * it under the terms of the GNU General Public License as published by *
10  * the Free Software Foundation; either version 2 of the License, or *
11  * (at your option) any later version. *
12  * *
13  * This program is distributed in the hope that it will be useful, *
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of *
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
16  * GNU General Public License for more details. *
17  * *
18  * You should have received a copy of the GNU General Public License *
19  * along with this program; if not, write to the *
20  * Free Software Foundation, Inc., *
21  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. *
22  ***************************************************************************
23  */
24 
27 #include "config.h"
28 
29 #include <set>
30 #include <sstream>
31 
32 #include <wx/bitmap.h>
33 #include <wx/button.h>
34 #include <wx/debug.h>
35 #include <wx/file.h>
36 #include <wx/image.h>
37 #include <wx/log.h>
38 #include <wx/panel.h>
39 #include <wx/progdlg.h>
40 #include <wx/sizer.h>
41 #include <wx/statline.h>
42 #include <wx/textwrapper.h>
43 
44 #include "catalog_mgr.h"
45 #include "update_mgr.h"
46 #include "model/plugin_loader.h"
47 #include "model/downloader.h"
48 #include "OCPNPlatform.h"
49 #include "model/plugin_handler.h"
50 #include "pluginmanager.h"
51 #include "model/semantic_vers.h"
52 #include "styles.h"
53 #include "options.h"
54 #include "svg_utils.h"
55 
56 #ifdef __ANDROID__
57 #include "androidUTIL.h"
58 #endif
59 
60 extern PlugInManager* g_pi_manager;
61 extern ocpnStyle::StyleManager* g_StyleManager;
62 extern OCPNPlatform* g_Platform;
63 extern options* g_options;
64 
65 #undef major // walk around gnu's major() and minor() macros.
66 #undef minor
67 
68 class HardBreakWrapper : public wxTextWrapper {
69 public:
70  HardBreakWrapper(wxWindow* win, const wxString& text, int widthMax) {
71  m_lineCount = 0;
72  Wrap(win, text, widthMax);
73  }
74  wxString const& GetWrapped() const { return m_wrapped; }
75  int const GetLineCount() const { return m_lineCount; }
76 
77 protected:
78  virtual void OnOutputLine(const wxString& line) { m_wrapped += line; }
79  virtual void OnNewLine() {
80  m_wrapped += '\n';
81  m_lineCount++;
82  }
83 
84 private:
85  wxString m_wrapped;
86  int m_lineCount;
87 };
88 
89 // HardBreakWrapper wrapper(win, text, widthMax);
90 // return wrapper.GetWrapped();
91 
92 // namespace update_mgr {
93 
98 static ssize_t PlugInIxByName(const std::string name,
99  const ArrayOfPlugIns* plugins) {
100  for (unsigned i = 0; i < plugins->GetCount(); i += 1) {
101  if (name == plugins->Item(i)->m_common_name.Lower().ToStdString()) {
102  return i;
103  }
104  }
105  return -1;
106 }
107 
109 static PlugInContainer* PlugInByName(const std::string name,
110  const ArrayOfPlugIns* plugins) {
111  auto ix = PlugInIxByName(name, plugins);
112  return ix == -1 ? 0 : plugins->Item(ix);
113 }
114 
116 static void LoadPNGIcon(const char* path, int size, wxBitmap& bitmap) {
117  wxPNGHandler handler;
118  if (!wxImage::FindHandler(handler.GetName())) {
119  wxImage::AddHandler(new wxPNGHandler());
120  }
121  auto img = new wxImage();
122  bool ok = img->LoadFile(path, wxBITMAP_TYPE_PNG);
123  if (!ok) {
124  bitmap = wxBitmap();
125  return;
126  }
127  img->Rescale(size, size);
128  bitmap = wxBitmap(*img);
129 }
130 
137 class PluginIconPanel : public wxPanel {
138 public:
139  PluginIconPanel(wxWindow* parent, std::string plugin_name)
140  : wxPanel(parent), m_plugin_name(plugin_name) {
141  auto size = GetClientSize();
142  auto minsize = GetTextExtent("OpenCPN");
143  SetMinClientSize(wxSize(minsize.GetWidth(), size.GetHeight()));
144  Layout();
145  Bind(wxEVT_PAINT, &PluginIconPanel::OnPaint, this);
146  }
147 
148  void OnPaint(wxPaintEvent& event) {
149  auto size = GetClientSize();
150  int minsize = wxMin(size.GetHeight(), size.GetWidth());
151  auto offset = minsize / 10;
152 
153  LoadIcon("packageBox.svg", m_bitmap, 2 * minsize / 3);
154  wxPaintDC dc(this);
155  if (!m_bitmap.IsOk()) {
156  wxLogMessage("AddPluginPanel: bitmap is not OK!");
157  return;
158  }
159  dc.DrawBitmap(m_bitmap, offset, offset, true);
160  }
161 
162 protected:
163  wxBitmap m_bitmap;
164  const std::string m_plugin_name;
165 
166  void LoadIcon(const char* plugin_name, wxBitmap& bitmap, int size = 32) {
167  wxFileName path(g_Platform->GetSharedDataDir(), plugin_name);
168  path.AppendDir("uidata");
169  path.AppendDir("traditional");
170  bool ok = false;
171 
172  if (path.IsFileReadable()) {
173  bitmap = LoadSVG(path.GetFullPath(), size, size);
174  ok = bitmap.IsOk();
175  }
176 
177  if (!ok) {
178  auto style = g_StyleManager->GetCurrentStyle();
179  bitmap = wxBitmap(style->GetIcon(_T("default_pi"), size, size));
180  wxLogMessage("Icon: %s not found.", path.GetFullPath());
181  }
182 
183  /*
184  wxFileName path(g_Platform->GetSharedDataDir(), plugin_name);
185  path.AppendDir("uidata");
186  bool ok = false;
187  path.SetExt("png");
188  if (path.IsFileReadable()) {
189  LoadPNGIcon(path.GetFullPath(), size, bitmap);
190  ok = bitmap.IsOk();
191  }
192  if (!ok) {
193  auto style = g_StyleManager->GetCurrentStyle();
194  bitmap = wxBitmap(style->GetIcon( _T("default_pi")));
195  }
196  */
197  }
198 };
199 
201 class InstallButton : public wxPanel {
202 public:
203  InstallButton(wxWindow* parent, PluginMetadata metadata)
204  : wxPanel(parent), m_metadata(metadata), m_remove(false) {
205  auto loader = PluginLoader::getInstance();
206  PlugInContainer* found =
207  PlugInByName(metadata.name, loader->GetPlugInArray());
208  std::string label(_("Install"));
209  if (found &&
210  ((found->m_version_major > 0) || (found->m_version_minor > 0))) {
211  label = getUpdateLabel(found, metadata);
212  m_remove = true;
213  }
214  auto button = new wxButton(this, wxID_ANY, label);
215  auto pluginHandler = PluginHandler::getInstance();
216  auto box = new wxBoxSizer(wxHORIZONTAL);
217  box->Add(button);
218  SetSizer(box);
219  Bind(wxEVT_COMMAND_BUTTON_CLICKED, &InstallButton::OnClick, this);
220  }
221 
222  void OnClick(wxCommandEvent& event) {
223  wxLogMessage("Selected update: %s", m_metadata.name.c_str());
224  auto top_parent = GetParent()->GetParent()->GetParent();
225  auto dialog = dynamic_cast<UpdateDialog*>(top_parent);
226  wxASSERT(dialog != 0);
227  dialog->SetUpdate(m_metadata);
228  dialog->EndModal(wxID_OK);
229  }
230 
231 private:
232  PluginMetadata m_metadata;
233  bool m_remove;
234 
235  const char* getUpdateLabel(PlugInContainer* pic, PluginMetadata metadata) {
236  SemanticVersion currentVersion(pic->m_version_major, pic->m_version_minor);
237  if (pic->m_version_str != "") {
238  currentVersion = SemanticVersion::parse(pic->m_version_str.ToStdString());
239  }
240  auto newVersion = SemanticVersion::parse(metadata.version);
241  if (newVersion > currentVersion) {
242  return _("Update");
243  } else if (newVersion == currentVersion) {
244  return _("Reinstall");
245  } else {
246  return _("Downgrade");
247  }
248  }
249 };
250 
252 class UpdateWebsiteButton : public wxPanel {
253 public:
254  UpdateWebsiteButton(wxWindow* parent, const char* url)
255  : wxPanel(parent), m_url(url) {
256  auto vbox = new wxBoxSizer(wxVERTICAL);
257  auto button = new wxButton(this, wxID_ANY, _("Website"));
258  button->Enable(strlen(url) > 0);
259  vbox->Add(button);
260  SetSizer(vbox);
261  Bind(wxEVT_COMMAND_BUTTON_CLICKED,
262  [=](wxCommandEvent&) { wxLaunchDefaultBrowser(m_url); });
263  }
264 
265 protected:
266  const std::string m_url;
267 };
268 
270 class CandidateButtonsPanel : public wxPanel {
271 public:
272  CandidateButtonsPanel(wxWindow* parent, const PluginMetadata* plugin)
273  : wxPanel(parent) {
274  auto flags = wxSizerFlags().Border();
275 
276  auto vbox = new wxBoxSizer(wxVERTICAL);
277  vbox->Add(new InstallButton(this, *plugin),
278  flags.DoubleBorder().Top().Right());
279  vbox->Add(1, 1, 1, wxEXPAND); // Expanding, stretchable spacer
280  m_info_btn = new UpdateWebsiteButton(this, plugin->info_url.c_str());
281  m_info_btn->Hide();
282  vbox->Add(m_info_btn, flags.DoubleBorder().Right());
283  SetSizer(vbox);
284  Fit();
285  }
286 
287  void HideDetails(bool hide) {
288  m_info_btn->Show(!hide);
289  GetParent()->Layout();
290  }
291 
292 private:
293  UpdateWebsiteButton* m_info_btn;
294 };
295 
297 class PluginTextPanel : public wxPanel {
298 public:
299  PluginTextPanel(wxWindow* parent, const PluginMetadata* plugin,
300  CandidateButtonsPanel* buttons, bool bshowTuple = false)
301  : wxPanel(parent), m_descr(0), m_buttons(buttons) {
302  auto flags = wxSizerFlags().Border();
303  m_isDesc = false;
304 
305  MORE = "<span foreground=\'blue\'>";
306  MORE += _("More");
307  MORE += "...</span>";
308  LESS = "<span foreground=\'blue\'>";
309  LESS += _("Less");
310  LESS += "...</span>";
311 
312  // For small displays, skip the "More" text.
313  if (g_Platform->getDisplaySize().x < 80 * GetCharWidth())
314  MORE = "";
315 
316  auto sum_hbox = new wxBoxSizer(wxHORIZONTAL);
317  m_widthDescription = g_options->GetSize().x *4 / 10;
318 
319  // m_summary = staticText(plugin->summary);
320  m_summary = new wxStaticText(
321  this, wxID_ANY, _T(""), wxDefaultPosition,
322  wxSize(m_widthDescription, -1) /*, wxST_NO_AUTORESIZE*/);
323  m_summaryText = wxString(plugin->summary.c_str());
324  m_summary->SetLabel(m_summaryText);
325  m_summary->Wrap(m_widthDescription);
326 
327  HardBreakWrapper wrapper(this, m_summaryText, m_widthDescription);
328  m_summaryLineCount = wrapper.GetLineCount() + 1;
329 
330  sum_hbox->Add(m_summary);
331  sum_hbox->AddSpacer(10);
332  m_more = staticText("4 Chars");
333  m_more->SetLabelMarkup(MORE);
334  sum_hbox->Add(m_more, wxSizerFlags());
335 
336  auto vbox = new wxBoxSizer(wxVERTICAL);
337  SetSizer(vbox);
338 
339  std::string name_reduced = plugin->name;
340  if(plugin->name.size() * GetCharWidth() > (size_t)m_widthDescription * 7 / 10){
341  int nc = (m_widthDescription *7 / 10) / GetCharWidth();
342  if (nc > 3){
343  name_reduced = plugin->name.substr(0, nc-3) + "...";
344  }
345  }
346 
347  wxString nameText(name_reduced + " " + plugin->version);
348  if (bshowTuple) nameText += " " + plugin->target;
349 
350  auto name = staticText(nameText);
351 
352  m_descr = new wxStaticText(
353  this, wxID_ANY, _T(""), wxDefaultPosition,
354  wxSize(m_widthDescription, -1) /*, wxST_NO_AUTORESIZE*/);
355  m_descText = wxString(plugin->description.c_str());
356  m_descr->SetLabel(m_descText);
357  m_descr->Wrap(m_widthDescription);
358  m_descr->Hide();
359  vbox->Add(name, flags);
360  vbox->Add(sum_hbox, flags);
361  vbox->Add(m_descr, 0);
362  Fit();
363 
364  m_more->Bind(wxEVT_LEFT_DOWN, &PluginTextPanel::OnClick, this);
365  m_descr->Bind(wxEVT_LEFT_DOWN, &PluginTextPanel::OnClick, this);
366  }
367 
368  void OnClick(wxMouseEvent& event) {
369  m_descr->Show(!m_descr->IsShown());
370  m_descr->SetLabel(_T(""));
371  m_descr->SetLabel(m_descText);
372  m_descr->Wrap(m_widthDescription);
373  Layout();
374  wxSize asize = GetEffectiveMinSize();
375 
376  m_more->SetLabelMarkup(m_descr->IsShown() ? LESS : MORE);
377  m_buttons->HideDetails(!m_descr->IsShown());
378 
379  UpdateDialog* swin = wxDynamicCast(GetGrandParent(), UpdateDialog);
380  if (swin) {
381  swin->RecalculateSize();
382  }
383  }
384 
385  int m_summaryLineCount;
386  bool m_isDesc;
387 
388 protected:
389  wxString MORE, LESS;
390 
391  wxStaticText* staticText(const wxString& text) {
392  return new wxStaticText(this, wxID_ANY, text, wxDefaultPosition,
393  wxDefaultSize, wxALIGN_LEFT);
394  }
395 
396  wxStaticText* m_descr;
397  wxStaticText* m_more;
398  wxStaticText* m_summary;
399  CandidateButtonsPanel* m_buttons;
400  int m_widthDescription;
401  wxString m_descText;
402  wxString m_summaryText;
403 };
404 
409 class OcpnUpdateScrolledWindow : public wxScrolledWindow {
410 public:
411  OcpnUpdateScrolledWindow(wxWindow* parent,
412  const std::vector<PluginMetadata>& updates)
413  : wxScrolledWindow(parent),
414  m_updates(updates),
415  m_grid(new wxFlexGridSizer(3, 0, 0)) {
416  auto box = new wxBoxSizer(wxVERTICAL);
417  populateGrid(m_grid);
418  box->Add(m_grid, wxSizerFlags().Proportion(0).Expand());
419  auto butt_box = new wxBoxSizer(wxHORIZONTAL);
420  auto cancel_btn = new wxButton(this, wxID_CANCEL, _("Dismiss"));
421  butt_box->Add(1, 1, 1, wxEXPAND); // Expanding, stretchable spacer
422  butt_box->Add(cancel_btn, wxSizerFlags().Border());
423  box->Add(butt_box, wxSizerFlags().Proportion(0).Expand());
424 
425  SetSizer(box);
426  SetMinSize(GetEffectiveMinSize());
427  SetScrollRate(1, 1);
428  };
429 
430  void populateGrid(wxFlexGridSizer* grid) {
432  struct metadata_compare {
433  bool operator()(const PluginMetadata& lhs,
434  const PluginMetadata& rhs) const {
435  return lhs.key() < rhs.key();
436  }
437  };
438 
439  auto flags = wxSizerFlags();
440  grid->SetCols(3);
441  grid->AddGrowableCol(2);
442  for (auto plugin : m_updates) {
443  grid->Add(new PluginIconPanel(this, plugin.name), flags.Expand());
444  auto buttons = new CandidateButtonsPanel(this, &plugin);
445  bool b_show_tuple = false;
446  if (g_Platform->getDisplaySize().x > 80 * GetCharWidth())
447  b_show_tuple = m_updates.size() > 1;
448  PluginTextPanel* tpanel =
449  new PluginTextPanel(this, &plugin, buttons, b_show_tuple);
450  tpanel->m_isDesc = true;
451  grid->Add(tpanel, flags.Proportion(1).Right());
452  grid->Add(buttons, flags.DoubleBorder());
453  grid->Add(new wxStaticLine(this), wxSizerFlags(0).Expand());
454  grid->Add(new wxStaticLine(this), wxSizerFlags(0).Expand());
455  grid->Add(new wxStaticLine(this), wxSizerFlags(0).Expand());
456  }
457  }
458 
459 private:
460  const std::vector<PluginMetadata> m_updates;
461  wxFlexGridSizer* m_grid;
462 };
463 
464 //} // namespace update_mgr
465 
467 UpdateDialog::UpdateDialog(wxWindow* parent,
468  const std::vector<PluginMetadata>& updates)
469  : wxDialog(parent, wxID_ANY, _("Plugin Manager"), wxDefaultPosition,
470  wxDefaultSize, wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER) {
471  auto vbox = new wxBoxSizer(wxVERTICAL);
472  SetSizer(vbox);
473 
474  m_scrwin = new OcpnUpdateScrolledWindow(this, updates);
475  vbox->Add(m_scrwin, wxSizerFlags(1).Expand());
476 
477  RecalculateSize();
478 
479  Center();
480 #ifdef __ANDROID__
481  androidDisableRotation();
482 #endif
483 }
484 
485 UpdateDialog::~UpdateDialog() {
486 #ifdef __ANDROID__
487  androidEnableRotation();
488 #endif
489 }
490 
491 void UpdateDialog::RecalculateSize() {
492  int calcHeight = 0;
493  int calcWidth = 0;
494  wxWindowList& kids = m_scrwin->GetChildren();
495  for (unsigned int i = 0; i < kids.GetCount(); i++) {
496  wxWindowListNode* node = kids.Item(i);
497  wxWindow* win = node->GetData();
498 
499  if (win && win->IsKindOf(CLASSINFO(PluginTextPanel))) {
500  PluginTextPanel* panel = (PluginTextPanel*)win;
501  if (panel->m_isDesc) {
502  wxSize tsize = win->GetEffectiveMinSize();
503  calcHeight += tsize.y + GetCharHeight();
504  calcWidth = tsize.x * 2;
505  }
506  }
507  }
508 
509  calcHeight += 3 * GetCharHeight(); // "dismiss" button
510  calcWidth = wxMin(calcWidth, g_Platform->getDisplaySize().x);
511 
512  m_scrwin->SetMinSize(wxSize(calcWidth, calcHeight));
513 
514 #ifdef __OCPN__ANDROID__
515  SetMinSize(g_Platform->getDisplaySize());
516 #endif
517 
518 
519  Fit();
520  SetMaxSize(g_Platform->getDisplaySize());
521  Layout();
522 }
The two buttons 'install' and 'website', the latter optionally hidden.
Definition: update_mgr.cpp:270
Download and install a PluginMetadata item when clicked.
Definition: update_mgr.cpp:201
The list of download candidates in a scrolled window + OK and Settings button.
Definition: update_mgr.cpp:409
void populateGrid(wxFlexGridSizer *grid)
Definition: update_mgr.cpp:430
Data for a loaded plugin, including dl-loaded library.
Definition: plugin_loader.h:99
wxString m_version_str
Complete version as of semantic_vers.
Definition: plugin_loader.h:88
A plugin icon, scaled to about 2/3 of available space.
Definition: update_mgr.cpp:137
Plugin name, version, summary + an optionally shown description.
Definition: update_mgr.cpp:297
Modal dialog, displays available updates (possibly just one) and lets user select and eventually conf...
Definition: update_mgr.h:41
UpdateDialog(wxWindow *parent, const std::vector< PluginMetadata > &updates)
Top-level install plugins dialog.
Definition: update_mgr.cpp:467
Invokes client browser on plugin info_url when clicked.
Definition: update_mgr.cpp:252
Plugin metadata, reflects the xml format directly.
Versions uses a modified semantic versioning scheme: major.minor.revision.post-tag+build.
Definition: semantic_vers.h:51
static SemanticVersion parse(std::string s)
Parse a version string, sets major == -1 on errors.