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