OpenCPN Partial API docs
Loading...
Searching...
No Matches
update_mgr.cpp
Go to the documentation of this file.
1/**************************************************************************
2 * Copyright (C) 2019 Alec Leamas *
3 * *
4 * This program is free software; you can redistribute it and/or modify *
5 * it under the terms of the GNU General Public License as published by *
6 * the Free Software Foundation; either version 2 of the License, or *
7 * (at your option) any later version. *
8 * *
9 * This program is distributed in the hope that it will be useful, *
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of *
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
12 * GNU General Public License for more details. *
13 * *
14 * You should have received a copy of the GNU General Public License *
15 * along with this program; if not, see <https://www.gnu.org/licenses/>. *
16 ***************************************************************************/
17
24#include "config.h"
25
26#include <set>
27#include <sstream>
28
29#include <wx/bitmap.h>
30#include <wx/button.h>
31#include <wx/debug.h>
32#include <wx/file.h>
33#include <wx/image.h>
34#include <wx/log.h>
35#include <wx/panel.h>
36#include <wx/progdlg.h>
37#include <wx/sizer.h>
38#include <wx/statline.h>
39#include <wx/textwrapper.h>
40
41#include "update_mgr.h"
42
43#include "model/downloader.h"
45#include "model/plugin_loader.h"
46#include "model/semantic_vers.h"
47#include "model/svg_utils.h"
48
49#include "catalog_mgr.h"
50#include "expand_icon.h"
51#include "ocpn_platform.h"
52#include "options.h"
53#include "pluginmanager.h"
54#include "styles.h"
55
56#ifdef __ANDROID__
57#include "androidUTIL.h"
58#endif
59
60#undef major // work around gnu's major() and minor() macros.
61#undef minor
62
63class HardBreakWrapper : public wxTextWrapper {
64public:
65 HardBreakWrapper(wxWindow* win, const wxString& text, int widthMax) {
66 m_lineCount = 0;
67 Wrap(win, text, widthMax);
68 }
69 wxString const& GetWrapped() const { return m_wrapped; }
70 int const GetLineCount() const { return m_lineCount; }
71
72protected:
73 virtual void OnOutputLine(const wxString& line) { m_wrapped += line; }
74 virtual void OnNewLine() {
75 m_wrapped += '\n';
76 m_lineCount++;
77 }
78
79private:
80 wxString m_wrapped;
81 int m_lineCount;
82};
83
88static ssize_t PlugInIxByName(const std::string name,
89 const ArrayOfPlugIns* plugins) {
90 for (unsigned i = 0; i < plugins->GetCount(); i += 1) {
91 if (name == plugins->Item(i)->m_common_name.Lower().ToStdString()) {
92 return i;
93 }
94 }
95 return -1;
96}
97
99static PlugInContainer* PlugInByName(const std::string name,
100 const ArrayOfPlugIns* plugins) {
101 auto ix = PlugInIxByName(name, plugins);
102 return ix == -1 ? 0 : plugins->Item(ix);
103}
104
111class PluginIconPanel : public wxPanel {
112public:
113 PluginIconPanel(wxWindow* parent, std::string plugin_name)
114 : wxPanel(parent), m_plugin_name(plugin_name) {
115 auto size = GetClientSize();
116 auto minsize = GetTextExtent("OpenCPN");
117 SetMinClientSize(wxSize(minsize.GetWidth(), size.GetHeight()));
118 Layout();
119 Bind(wxEVT_PAINT, &PluginIconPanel::OnPaint, this);
120 }
121
122 void OnPaint(wxPaintEvent&) {
123 auto size = GetClientSize();
124 int minsize = wxMin(size.GetHeight(), size.GetWidth());
125 auto offset = minsize / 10;
126
127 LoadIcon("packageBox.svg", m_bitmap, 2 * minsize / 3);
128 wxPaintDC dc(this);
129 if (!m_bitmap.IsOk()) {
130 wxLogMessage("AddPluginPanel: bitmap is not OK!");
131 return;
132 }
133 dc.DrawBitmap(m_bitmap, offset, offset, true);
134 }
135
136protected:
137 wxBitmap m_bitmap;
138 const std::string m_plugin_name;
139
140 void LoadIcon(const char* plugin_name, wxBitmap& bitmap, int size = 32) {
141 wxFileName path(g_Platform->GetSharedDataDir(), plugin_name);
142 path.AppendDir("uidata");
143 path.AppendDir("traditional");
144 bool ok = false;
145
146 if (path.IsFileReadable()) {
147 bitmap = LoadSVG(path.GetFullPath(), size, size);
148 ok = bitmap.IsOk();
149 }
150
151 if (!ok) {
152 auto style = g_StyleManager->GetCurrentStyle();
153 bitmap = wxBitmap(style->GetIcon("default_pi", size, size));
154 wxLogMessage("Icon: %s not found.", path.GetFullPath());
155 }
156 }
157};
158
160class InstallButton : public wxPanel {
161public:
162 InstallButton(wxWindow* parent, PluginMetadata metadata)
163 : wxPanel(parent), m_metadata(metadata) {
164 auto loader = PluginLoader::GetInstance();
165 PlugInContainer* found =
166 PlugInByName(metadata.name, loader->GetPlugInArray());
167 std::string label(_("Install"));
168 if (found &&
169 ((found->m_version_major > 0) || (found->m_version_minor > 0))) {
170 label = getUpdateLabel(found, metadata);
171 }
172 auto button = new wxButton(this, wxID_ANY, label);
173 auto box = new wxBoxSizer(wxHORIZONTAL);
174 box->Add(button);
175 SetSizer(box);
176 Bind(wxEVT_COMMAND_BUTTON_CLICKED, &InstallButton::OnClick, this);
177 }
178
179 void OnClick(wxCommandEvent&) {
180 wxLogMessage("Selected update: %s", m_metadata.name.c_str());
181 auto top_parent = GetParent()->GetParent()->GetParent();
182 auto dialog = dynamic_cast<UpdateDialog*>(top_parent);
183 wxASSERT(dialog != 0);
184 dialog->SetUpdate(m_metadata);
185 dialog->EndModal(wxID_OK);
186 }
187
188private:
189 PluginMetadata m_metadata;
190
191 const char* getUpdateLabel(PlugInContainer* pic, PluginMetadata metadata) {
192 SemanticVersion currentVersion(pic->m_version_major, pic->m_version_minor);
193 if (pic->m_version_str != "") {
194 currentVersion = SemanticVersion::parse(pic->m_version_str.ToStdString());
195 }
196 auto newVersion = SemanticVersion::parse(metadata.version);
197 if (newVersion > currentVersion) {
198 return _("Update");
199 } else if (newVersion == currentVersion) {
200 return _("Reinstall");
201 } else {
202 return _("Downgrade");
203 }
204 }
205};
206
208class UpdateWebsiteButton : public wxPanel {
209public:
210 UpdateWebsiteButton(wxWindow* parent, const char* url)
211 : wxPanel(parent), m_url(url) {
212 auto vbox = new wxBoxSizer(wxVERTICAL);
213 auto button = new wxButton(this, wxID_ANY, _("Website"));
214 button->Enable(strlen(url) > 0);
215 vbox->Add(button);
216 SetSizer(vbox);
217 Bind(wxEVT_COMMAND_BUTTON_CLICKED,
218 [=](wxCommandEvent&) { wxLaunchDefaultBrowser(m_url); });
219 }
220
221protected:
222 const std::string m_url;
223};
224
226class CandidateButtonsPanel : public wxPanel {
227public:
228 CandidateButtonsPanel(wxWindow* parent, const PluginMetadata* plugin)
229 : wxPanel(parent) {
230 auto flags = wxSizerFlags().Border();
231
232 auto vbox = new wxBoxSizer(wxVERTICAL);
233 vbox->Add(new InstallButton(this, *plugin),
234 flags.DoubleBorder().Top().Right());
235 vbox->Add(1, 1, 1, wxEXPAND); // Expanding, stretchable spacer
236 m_info_btn = new UpdateWebsiteButton(this, plugin->info_url.c_str());
237 m_info_btn->Hide();
238 vbox->Add(m_info_btn, flags.DoubleBorder().Right());
239 SetSizer(vbox);
240 Fit();
241 }
242
243 void HideDetails(bool hide) {
244 m_info_btn->Show(!hide);
245 GetParent()->Layout();
246 }
247
248private:
249 UpdateWebsiteButton* m_info_btn;
250};
251
253class PluginTextPanel : public wxPanel {
254public:
255 PluginTextPanel(wxWindow* parent, const PluginMetadata* plugin,
256 CandidateButtonsPanel* buttons, bool bshowTuple = false)
257 : wxPanel(parent),
258 m_isDesc(false),
259 m_descr(0),
260 m_more(new ExpandableIcon(this,
261 [&](bool collapsed) { OnClick(collapsed); })),
262 m_buttons(buttons) {
263 auto flags = wxSizerFlags().Border();
264 auto sum_hbox = new wxBoxSizer(wxHORIZONTAL);
265 m_widthDescription = g_options->GetSize().x * 4 / 10;
266
267 // m_summary = staticText(plugin->summary);
268 m_summary = new wxStaticText(
269 this, wxID_ANY, "", wxDefaultPosition,
270 wxSize(m_widthDescription, -1) /*, wxST_NO_AUTORESIZE*/);
271 m_summaryText = wxString(plugin->summary.c_str());
272 m_summary->SetLabel(m_summaryText);
273 m_summary->Wrap(m_widthDescription);
274
275 HardBreakWrapper wrapper(this, m_summaryText, m_widthDescription);
276
277 sum_hbox->Add(m_summary);
278 sum_hbox->AddSpacer(10);
279 sum_hbox->Add(m_more, wxSizerFlags());
280
281 auto vbox = new wxBoxSizer(wxVERTICAL);
282 SetSizer(vbox);
283
284 std::string name_reduced = plugin->name;
285 if (plugin->name.size() * GetCharWidth() >
286 (size_t)m_widthDescription * 7 / 10) {
287 int nc = (m_widthDescription * 7 / 10) / GetCharWidth();
288 if (nc > 3) {
289 name_reduced = plugin->name.substr(0, nc - 3) + "...";
290 }
291 }
292
293 wxString nameText(name_reduced + " " + plugin->version);
294 if (bshowTuple) nameText += " " + plugin->target;
295
296 auto name = staticText(nameText);
297
298 m_descr = new wxStaticText(
299 this, wxID_ANY, "", wxDefaultPosition,
300 wxSize(m_widthDescription, -1) /*, wxST_NO_AUTORESIZE*/);
301 m_descText = wxString(plugin->description.c_str());
302 m_descr->SetLabel(m_descText);
303 m_descr->Wrap(m_widthDescription);
304 m_descr->Hide();
305 vbox->Add(name, flags);
306 vbox->Add(sum_hbox, flags);
307 vbox->Add(m_descr, 0);
308 Fit();
309 }
310
311 void OnClick() {
312 m_descr->SetLabel(m_descText);
313 m_descr->Wrap(m_widthDescription);
314 GetParent()->Layout();
315 m_buttons->HideDetails(!m_descr->IsShown());
316
317 auto swin = dynamic_cast<UpdateDialog*>(GetGrandParent());
318 if (swin) {
319 swin->RecalculateSize();
320 }
321 }
322
323 void OnClick(wxMouseEvent&) {
324 m_descr->Show(!m_descr->IsShown());
325 OnClick();
326 }
327
328 void OnClick(bool collapsed) {
329 m_descr->Show(!collapsed);
330 m_isDesc = !collapsed;
331 OnClick();
332 }
333 bool m_isDesc;
334
335protected:
336 wxStaticText* staticText(const wxString& text) {
337 return new wxStaticText(this, wxID_ANY, text, wxDefaultPosition,
338 wxDefaultSize, wxALIGN_LEFT);
339 }
340
341 wxStaticText* m_descr;
342 ExpandableIcon* m_more;
343 wxStaticText* m_summary;
344 CandidateButtonsPanel* m_buttons;
345 int m_widthDescription;
346 wxString m_descText;
347 wxString m_summaryText;
348};
349
354class OcpnUpdateScrolledWindow : public wxScrolledWindow {
355public:
356 OcpnUpdateScrolledWindow(wxWindow* parent,
357 const std::vector<PluginMetadata>& updates)
358 : wxScrolledWindow(parent),
359 m_updates(updates),
360 m_grid(new wxFlexGridSizer(3, 0, 0)) {
361 auto box = new wxBoxSizer(wxVERTICAL);
362 populateGrid(m_grid);
363 box->Add(m_grid, wxSizerFlags().Proportion(0).Expand());
364 auto butt_box = new wxBoxSizer(wxHORIZONTAL);
365 auto cancel_btn = new wxButton(this, wxID_CANCEL, _("Dismiss"));
366 butt_box->Add(1, 1, 1, wxEXPAND); // Expanding, stretchable spacer
367 butt_box->Add(cancel_btn, wxSizerFlags().Border());
368 box->Add(butt_box, wxSizerFlags().Proportion(0).Expand());
369
370 SetSizer(box);
371 SetMinSize(GetEffectiveMinSize());
372 SetScrollRate(1, 1);
373 SetAutoLayout(true);
374 };
375
376 void populateGrid(wxFlexGridSizer* grid) {
377 auto flags = wxSizerFlags();
378 grid->SetCols(3);
379 grid->AddGrowableCol(2);
380 for (auto plugin : m_updates) {
381 grid->Add(new PluginIconPanel(this, plugin.name), flags.Expand());
382 auto buttons = new CandidateButtonsPanel(this, &plugin);
383 bool b_show_tuple = false;
384 if (g_Platform->getDisplaySize().x > 80 * GetCharWidth())
385 b_show_tuple = m_updates.size() > 1;
386 PluginTextPanel* tpanel =
387 new PluginTextPanel(this, &plugin, buttons, b_show_tuple);
388 tpanel->m_isDesc = true;
389 grid->Add(tpanel, flags.Proportion(1).Right());
390 grid->Add(buttons, flags.DoubleBorder());
391 grid->Add(new wxStaticLine(this), wxSizerFlags(0).Expand());
392 grid->Add(new wxStaticLine(this), wxSizerFlags(0).Expand());
393 grid->Add(new wxStaticLine(this), wxSizerFlags(0).Expand());
394 }
395 }
396
397private:
398 const std::vector<PluginMetadata> m_updates;
399 wxFlexGridSizer* m_grid;
400};
401
404 const std::vector<PluginMetadata>& updates)
405 : wxDialog(parent, wxID_ANY, _("Plugin Manager"), wxDefaultPosition,
406 wxDefaultSize, wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER) {
407 auto vbox = new wxBoxSizer(wxVERTICAL);
408 SetSizer(vbox);
409
410 m_scrwin = new OcpnUpdateScrolledWindow(this, updates);
411 vbox->Add(m_scrwin, wxSizerFlags(1).Expand());
412
413 RecalculateSize();
414
415 Center();
416#ifdef __ANDROID__
417 androidDisableRotation();
418#endif
419}
420
421UpdateDialog::~UpdateDialog() {
422#ifdef __ANDROID__
423 androidEnableRotation();
424#endif
425}
426
427void UpdateDialog::RecalculateSize() {
428 m_scrwin->SetMinClientSize(m_scrwin->GetSizer()->GetMinSize());
429#ifdef __ANDROID__
430 SetMinSize(g_Platform->getDisplaySize());
431#endif
432 SetMaxSize(g_Platform->getDisplaySize());
433 Fit();
434}
Catalog options dialog, by default disabled.
The two buttons 'install' and 'website', the latter optionally hidden.
Simple panel showing either an "expand" or "collapse" icon, state switches when clicked.
Definition expand_icon.h:35
Download and install a PluginMetadata item when clicked.
wxSize getDisplaySize()
Get the display size in logical pixels.
The list of download candidates in a scrolled window + OK and Settings button.
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:40
UpdateDialog(wxWindow *parent, const std::vector< PluginMetadata > &updates)
Top-level install plugins dialog.
Invokes client browser on plugin info_url when clicked.
Handle downloading of files from remote urls.
OpenCPN Platform specific support utilities.
options * g_options
Global instance.
Definition options.cpp:179
Options dialog.
Plugin remote repositories installation and Uninstall/list operations.
Low level code to load plugins from disk, notably the PluginLoader class.
PlugInManager and helper classes – Mostly gui parts (dialogs) and plugin API stuff.
Semantic version encode/decode object.
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.
Chart Symbols.
wxBitmap LoadSVG(const wxString filename, const unsigned int width, const unsigned int height, wxBitmap *default_bitmap, bool use_cache)
Load SVG file and return it's bitmap representation of requested size In case file can't be loaded an...
Definition svg_utils.cpp:59
SVG utilities.
Plugin update dialog.