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