OpenCPN Partial API docs
Loading...
Searching...
No Matches
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
60extern PlugInManager* g_pi_manager;
61extern ocpnStyle::StyleManager* g_StyleManager;
62extern OCPNPlatform* g_Platform;
63extern options* g_options;
64
65#undef major // walk around gnu's major() and minor() macros.
66#undef minor
67
68class HardBreakWrapper : public wxTextWrapper {
69public:
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
77protected:
78 virtual void OnOutputLine(const wxString& line) { m_wrapped += line; }
79 virtual void OnNewLine() {
80 m_wrapped += '\n';
81 m_lineCount++;
82 }
83
84private:
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
98static 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
109static 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
116static 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
137class PluginIconPanel : public wxPanel {
138public:
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
162protected:
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
201class InstallButton : public wxPanel {
202public:
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
231private:
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
252class UpdateWebsiteButton : public wxPanel {
253public:
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
265protected:
266 const std::string m_url;
267};
268
270class CandidateButtonsPanel : public wxPanel {
271public:
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
292private:
293 UpdateWebsiteButton* m_info_btn;
294};
295
297class PluginTextPanel : public wxPanel {
298public:
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()) MORE = "";
314
315 auto sum_hbox = new wxBoxSizer(wxHORIZONTAL);
316 m_widthDescription = g_options->GetSize().x * 4 / 10;
317
318 // m_summary = staticText(plugin->summary);
319 m_summary = new wxStaticText(
320 this, wxID_ANY, _T(""), wxDefaultPosition,
321 wxSize(m_widthDescription, -1) /*, wxST_NO_AUTORESIZE*/);
322 m_summaryText = wxString(plugin->summary.c_str());
323 m_summary->SetLabel(m_summaryText);
324 m_summary->Wrap(m_widthDescription);
325
326 HardBreakWrapper wrapper(this, m_summaryText, m_widthDescription);
327 m_summaryLineCount = wrapper.GetLineCount() + 1;
328
329 sum_hbox->Add(m_summary);
330 sum_hbox->AddSpacer(10);
331 m_more = staticText("4 Chars");
332 m_more->SetLabelMarkup(MORE);
333 sum_hbox->Add(m_more, wxSizerFlags());
334
335 auto vbox = new wxBoxSizer(wxVERTICAL);
336 SetSizer(vbox);
337
338 std::string name_reduced = plugin->name;
339 if (plugin->name.size() * GetCharWidth() >
340 (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 auto swin = dynamic_cast<UpdateDialog*>(GetGrandParent());
380 if (swin) {
381 swin->RecalculateSize();
382 }
383 }
384
385 int m_summaryLineCount;
386 bool m_isDesc;
387
388protected:
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
409class OcpnUpdateScrolledWindow : public wxScrolledWindow {
410public:
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
459private:
460 const std::vector<PluginMetadata> m_updates;
461 wxFlexGridSizer* m_grid;
462};
463
464//} // namespace update_mgr
465
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
485UpdateDialog::~UpdateDialog() {
486#ifdef __ANDROID__
487 androidEnableRotation();
488#endif
489}
490
491void 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 auto panel = dynamic_cast<PluginTextPanel*>(win);
500 if (panel) {
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 Fit();
519 SetMaxSize(g_Platform->getDisplaySize());
520 Layout();
521}
The two buttons 'install' and 'website', the latter optionally hidden.
Download and install a PluginMetadata item when clicked.
Provides platform-specific support utilities for OpenCPN.
wxSize getDisplaySize()
Get the display size in logical pixels.
The list of download candidates in a scrolled window + OK and Settings button.
void populateGrid(wxFlexGridSizer *grid)
Data for a loaded plugin, including dl-loaded library.
wxString m_version_str
Complete version as of semantic_vers.
A plugin icon, scaled to about 2/3 of available space.
Plugin name, version, summary + an optionally shown description.
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.
Invokes client browser on plugin info_url when clicked.
Plugin metadata, reflects the xml format directly.
Versions uses a modified semantic versioning scheme: major.minor.revision.post-tag+build.
static SemanticVersion parse(std::string s)
Parse a version string, sets major == -1 on errors.