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#include "gl_headers.h" // Must be included before anything using GL stuff
26
27#include <set>
28#include <sstream>
29
30#include <wx/bitmap.h>
31#include <wx/button.h>
32#include <wx/debug.h>
33#include <wx/file.h>
34#include <wx/image.h>
35#include <wx/log.h>
36#include <wx/panel.h>
37#include <wx/progdlg.h>
38#include <wx/sizer.h>
39#include <wx/statline.h>
40#include <wx/textwrapper.h>
41
42#include "update_mgr.h"
43
44#include "libgui/expand_icon.h"
45
46#include "model/downloader.h"
48#include "model/plugin_loader.h"
49#include "model/semantic_vers.h"
50#include "model/svg_utils.h"
51
52#include "catalog_mgr.h"
53#include "ocpn_platform.h"
54#include "options.h"
55#include "pluginmanager.h"
56#include "styles.h"
57
58#ifdef __ANDROID__
59#include "androidUTIL.h"
60#endif
61
62#undef major // work around gnu's major() and minor() macros.
63#undef minor
64
65class HardBreakWrapper : public wxTextWrapper {
66public:
67 HardBreakWrapper(wxWindow* win, const wxString& text, int widthMax) {
68 m_lineCount = 0;
69 Wrap(win, text, widthMax);
70 }
71 wxString const& GetWrapped() const { return m_wrapped; }
72 int const GetLineCount() const { return m_lineCount; }
73
74protected:
75 virtual void OnOutputLine(const wxString& line) { m_wrapped += line; }
76 virtual void OnNewLine() {
77 m_wrapped += '\n';
78 m_lineCount++;
79 }
80
81private:
82 wxString m_wrapped;
83 int m_lineCount;
84};
85
90static ssize_t PlugInIxByName(const std::string name,
91 const ArrayOfPlugIns* plugins) {
92 for (unsigned i = 0; i < plugins->GetCount(); i += 1) {
93 if (name == plugins->Item(i)->m_common_name.Lower().ToStdString()) {
94 return i;
95 }
96 }
97 return -1;
98}
99
101static PlugInContainer* PlugInByName(const std::string name,
102 const ArrayOfPlugIns* plugins) {
103 auto ix = PlugInIxByName(name, plugins);
104 return ix == -1 ? 0 : plugins->Item(ix);
105}
106
113class PluginIconPanel : public wxPanel {
114public:
115 PluginIconPanel(wxWindow* parent, std::string plugin_name)
116 : wxPanel(parent), m_plugin_name(plugin_name) {
117 auto size = GetClientSize();
118 auto minsize = GetTextExtent("OpenCPN");
119 SetMinClientSize(wxSize(minsize.GetWidth(), size.GetHeight()));
120 Layout();
121 Bind(wxEVT_PAINT, &PluginIconPanel::OnPaint, this);
122 }
123
124 void OnPaint(wxPaintEvent&) {
125 auto size = GetClientSize();
126 int minsize = wxMin(size.GetHeight(), size.GetWidth());
127 auto offset = minsize / 10;
128
129 LoadIcon("packageBox.svg", m_bitmap, 2 * minsize / 3);
130 wxPaintDC dc(this);
131 if (!m_bitmap.IsOk()) {
132 wxLogMessage("AddPluginPanel: bitmap is not OK!");
133 return;
134 }
135 dc.DrawBitmap(m_bitmap, offset, offset, true);
136 }
137
138protected:
139 wxBitmap m_bitmap;
140 const std::string m_plugin_name;
141
142 void LoadIcon(const char* plugin_name, wxBitmap& bitmap, int size = 32) {
143 wxFileName path(g_Platform->GetSharedDataDir(), plugin_name);
144 path.AppendDir("uidata");
145 path.AppendDir("traditional");
146 bool ok = false;
147
148 if (path.IsFileReadable()) {
149 bitmap = LoadSVG(path.GetFullPath(), size, size);
150 ok = bitmap.IsOk();
151 }
152
153 if (!ok) {
154 auto style = g_StyleManager->GetCurrentStyle();
155 bitmap = wxBitmap(style->GetIcon("default_pi", size, size));
156 wxLogMessage("Icon: %s not found.", path.GetFullPath());
157 }
158 }
159};
160
162class InstallButton : public wxPanel {
163public:
164 InstallButton(wxWindow* parent, PluginMetadata metadata)
165 : wxPanel(parent), m_metadata(metadata) {
166 auto loader = PluginLoader::GetInstance();
167 PlugInContainer* found =
168 PlugInByName(metadata.name, loader->GetPlugInArray());
169 std::string label(_("Install"));
170 if (found &&
171 ((found->m_version_major > 0) || (found->m_version_minor > 0))) {
172 label = getUpdateLabel(found, metadata);
173 }
174 auto button = new wxButton(this, wxID_ANY, label);
175 auto box = new wxBoxSizer(wxHORIZONTAL);
176 box->Add(button);
177 SetSizer(box);
178 Bind(wxEVT_COMMAND_BUTTON_CLICKED, &InstallButton::OnClick, this);
179 }
180
181 void OnClick(wxCommandEvent&) {
182 wxLogMessage("Selected update: %s", m_metadata.name.c_str());
183 auto top_parent = GetParent()->GetParent()->GetParent();
184 auto dialog = dynamic_cast<UpdateDialog*>(top_parent);
185 wxASSERT(dialog != 0);
186 dialog->SetUpdate(m_metadata);
187 dialog->EndModal(wxID_OK);
188 }
189
190private:
191 PluginMetadata m_metadata;
192
193 const char* getUpdateLabel(PlugInContainer* pic, PluginMetadata metadata) {
194 SemanticVersion currentVersion(pic->m_version_major, pic->m_version_minor);
195 if (pic->m_version_str != "") {
196 currentVersion = SemanticVersion::parse(pic->m_version_str.ToStdString());
197 }
198 auto newVersion = SemanticVersion::parse(metadata.version);
199 if (newVersion > currentVersion) {
200 return _("Update");
201 } else if (newVersion == currentVersion) {
202 return _("Reinstall");
203 } else {
204 return _("Downgrade");
205 }
206 }
207};
208
210class UpdateWebsiteButton : public wxPanel {
211public:
212 UpdateWebsiteButton(wxWindow* parent, const char* url)
213 : wxPanel(parent), m_url(url) {
214 auto vbox = new wxBoxSizer(wxVERTICAL);
215 auto button = new wxButton(this, wxID_ANY, _("Website"));
216 button->Enable(strlen(url) > 0);
217 vbox->Add(button);
218 SetSizer(vbox);
219 Bind(wxEVT_COMMAND_BUTTON_CLICKED,
220 [=](wxCommandEvent&) { wxLaunchDefaultBrowser(m_url); });
221 }
222
223protected:
224 const std::string m_url;
225};
226
228class CandidateButtonsPanel : public wxPanel {
229public:
230 CandidateButtonsPanel(wxWindow* parent, const PluginMetadata* plugin)
231 : wxPanel(parent) {
232 auto flags = wxSizerFlags().Border();
233
234 auto vbox = new wxBoxSizer(wxVERTICAL);
235 vbox->Add(new InstallButton(this, *plugin),
236 flags.DoubleBorder().Top().Right());
237 vbox->Add(1, 1, 1, wxEXPAND); // Expanding, stretchable spacer
238 m_info_btn = new UpdateWebsiteButton(this, plugin->info_url.c_str());
239 m_info_btn->Hide();
240 vbox->Add(m_info_btn, flags.DoubleBorder().Right());
241 SetSizer(vbox);
242 Fit();
243 }
244
245 void HideDetails(bool hide) {
246 m_info_btn->Show(!hide);
247 GetParent()->Layout();
248 }
249
250private:
251 UpdateWebsiteButton* m_info_btn;
252};
253
255class PluginTextPanel : public wxPanel {
256public:
257 PluginTextPanel(wxWindow* parent, const PluginMetadata* plugin,
258 CandidateButtonsPanel* buttons, bool bshowTuple = false)
259 : wxPanel(parent),
260 m_isDesc(false),
261 m_descr(0),
262 m_more(new ExpandableIcon(this,
263 [&](bool collapsed) { OnClick(collapsed); })),
264 m_buttons(buttons) {
265 auto flags = wxSizerFlags().Border();
266 auto sum_hbox = new wxBoxSizer(wxHORIZONTAL);
267 m_widthDescription = g_options->GetSize().x * 4 / 10;
268
269 // m_summary = staticText(plugin->summary);
270 m_summary = new wxStaticText(
271 this, wxID_ANY, "", wxDefaultPosition,
272 wxSize(m_widthDescription, -1) /*, wxST_NO_AUTORESIZE*/);
273 m_summaryText = wxString(plugin->summary.c_str());
274 m_summary->SetLabel(m_summaryText);
275 m_summary->Wrap(m_widthDescription);
276
277 HardBreakWrapper wrapper(this, m_summaryText, m_widthDescription);
278
279 sum_hbox->Add(m_summary);
280 sum_hbox->AddSpacer(10);
281 sum_hbox->Add(m_more, wxSizerFlags());
282
283 auto vbox = new wxBoxSizer(wxVERTICAL);
284 SetSizer(vbox);
285
286 std::string name_reduced = plugin->name;
287 if (plugin->name.size() * GetCharWidth() >
288 (size_t)m_widthDescription * 7 / 10) {
289 int nc = (m_widthDescription * 7 / 10) / GetCharWidth();
290 if (nc > 3) {
291 name_reduced = plugin->name.substr(0, nc - 3) + "...";
292 }
293 }
294
295 wxString nameText(name_reduced + " " + plugin->version);
296 if (bshowTuple) nameText += " " + plugin->target;
297
298 auto name = staticText(nameText);
299
300 m_descr = new wxStaticText(
301 this, wxID_ANY, "", wxDefaultPosition,
302 wxSize(m_widthDescription, -1) /*, wxST_NO_AUTORESIZE*/);
303 m_descText = wxString(plugin->description.c_str());
304 m_descr->SetLabel(m_descText);
305 m_descr->Wrap(m_widthDescription);
306 m_descr->Hide();
307 vbox->Add(name, flags);
308 vbox->Add(sum_hbox, flags);
309 vbox->Add(m_descr, 0);
310 Fit();
311 }
312
313 void OnClick() {
314 m_descr->SetLabel(m_descText);
315 m_descr->Wrap(m_widthDescription);
316 GetParent()->Layout();
317 m_buttons->HideDetails(!m_descr->IsShown());
318
319 auto swin = dynamic_cast<UpdateDialog*>(GetGrandParent());
320 if (swin) {
321 swin->RecalculateSize();
322 }
323 }
324
325 void OnClick(wxMouseEvent&) {
326 m_descr->Show(!m_descr->IsShown());
327 OnClick();
328 }
329
330 void OnClick(bool collapsed) {
331 m_descr->Show(!collapsed);
332 m_isDesc = !collapsed;
333 OnClick();
334 }
335 bool m_isDesc;
336
337protected:
338 wxStaticText* staticText(const wxString& text) {
339 return new wxStaticText(this, wxID_ANY, text, wxDefaultPosition,
340 wxDefaultSize, wxALIGN_LEFT);
341 }
342
343 wxStaticText* m_descr;
344 ExpandableIcon* m_more;
345 wxStaticText* m_summary;
346 CandidateButtonsPanel* m_buttons;
347 int m_widthDescription;
348 wxString m_descText;
349 wxString m_summaryText;
350};
351
356class OcpnUpdateScrolledWindow : public wxScrolledWindow {
357public:
358 OcpnUpdateScrolledWindow(wxWindow* parent,
359 const std::vector<PluginMetadata>& updates)
360 : wxScrolledWindow(parent),
361 m_updates(updates),
362 m_grid(new wxFlexGridSizer(3, 0, 0)) {
363 auto box = new wxBoxSizer(wxVERTICAL);
364 populateGrid(m_grid);
365 box->Add(m_grid, wxSizerFlags().Proportion(0).Expand());
366 auto butt_box = new wxBoxSizer(wxHORIZONTAL);
367 auto cancel_btn = new wxButton(this, wxID_CANCEL, _("Dismiss"));
368 butt_box->Add(1, 1, 1, wxEXPAND); // Expanding, stretchable spacer
369 butt_box->Add(cancel_btn, wxSizerFlags().Border());
370 box->Add(butt_box, wxSizerFlags().Proportion(0).Expand());
371
372 SetSizer(box);
373 SetMinSize(GetEffectiveMinSize());
374 SetScrollRate(1, 1);
375 SetAutoLayout(true);
376 };
377
378 void populateGrid(wxFlexGridSizer* grid) {
379 auto flags = wxSizerFlags();
380 grid->SetCols(3);
381 grid->AddGrowableCol(2);
382 for (auto plugin : m_updates) {
383 grid->Add(new PluginIconPanel(this, plugin.name), flags.Expand());
384 auto buttons = new CandidateButtonsPanel(this, &plugin);
385 bool b_show_tuple = false;
386 if (g_Platform->getDisplaySize().x > 80 * GetCharWidth())
387 b_show_tuple = m_updates.size() > 1;
388 PluginTextPanel* tpanel =
389 new PluginTextPanel(this, &plugin, buttons, b_show_tuple);
390 tpanel->m_isDesc = true;
391 grid->Add(tpanel, flags.Proportion(1).Right());
392 grid->Add(buttons, flags.DoubleBorder());
393 grid->Add(new wxStaticLine(this), wxSizerFlags(0).Expand());
394 grid->Add(new wxStaticLine(this), wxSizerFlags(0).Expand());
395 grid->Add(new wxStaticLine(this), wxSizerFlags(0).Expand());
396 }
397 }
398
399private:
400 const std::vector<PluginMetadata> m_updates;
401 wxFlexGridSizer* m_grid;
402};
403
406 const std::vector<PluginMetadata>& updates)
407 : wxDialog(parent, wxID_ANY, _("Plugin Manager"), wxDefaultPosition,
408 wxDefaultSize, wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER) {
409 auto vbox = new wxBoxSizer(wxVERTICAL);
410 SetSizer(vbox);
411
412 m_scrwin = new OcpnUpdateScrolledWindow(this, updates);
413 vbox->Add(m_scrwin, wxSizerFlags(1).Expand());
414
415 RecalculateSize();
416
417 Center();
418#ifdef __ANDROID__
419 androidDisableRotation();
420#endif
421}
422
423UpdateDialog::~UpdateDialog() {
424#ifdef __ANDROID__
425 androidEnableRotation();
426#endif
427}
428
429void UpdateDialog::RecalculateSize() {
430 m_scrwin->SetMinClientSize(m_scrwin->GetSizer()->GetMinSize());
431#ifdef __ANDROID__
432 SetMinSize(g_Platform->getDisplaySize());
433#endif
434 SetMaxSize(g_Platform->getDisplaySize());
435 Fit();
436}
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:38
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.
Platform independent GL includes.
OpenCPN Platform specific support utilities.
options * g_options
Global instance.
Definition options.cpp:181
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.