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 "plugin_loader.h"
47#include "downloader.h"
48#include "OCPNPlatform.h"
49#include "plugin_handler.h"
50#include "pluginmanager.h"
51#include "semantic_vers.h"
52#include "styles.h"
53#include "options.h"
54#include "svg_utils.h"
55
56extern PlugInManager* g_pi_manager;
57extern ocpnStyle::StyleManager* g_StyleManager;
58extern OCPNPlatform* g_Platform;
59extern options* g_options;
60
61#undef major // walk 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
85// HardBreakWrapper wrapper(win, text, widthMax);
86// return wrapper.GetWrapped();
87
88// namespace update_mgr {
89
94static ssize_t PlugInIxByName(const std::string name, ArrayOfPlugIns* plugins) {
95 for (unsigned i = 0; i < plugins->GetCount(); i += 1) {
96 if (name == plugins->Item(i)->m_common_name.Lower().ToStdString()) {
97 return i;
98 }
99 }
100 return -1;
101}
102
104static PlugInContainer* PlugInByName(const std::string name,
105 ArrayOfPlugIns* plugins) {
106 auto ix = PlugInIxByName(name, plugins);
107 return ix == -1 ? 0 : plugins->Item(ix);
108}
109
111static void LoadPNGIcon(const char* path, int size, wxBitmap& bitmap) {
112 wxPNGHandler handler;
113 if (!wxImage::FindHandler(handler.GetName())) {
114 wxImage::AddHandler(new wxPNGHandler());
115 }
116 auto img = new wxImage();
117 bool ok = img->LoadFile(path, wxBITMAP_TYPE_PNG);
118 if (!ok) {
119 bitmap = wxBitmap();
120 return;
121 }
122 img->Rescale(size, size);
123 bitmap = wxBitmap(*img);
124}
125
132class PluginIconPanel : public wxPanel {
133public:
134 PluginIconPanel(wxWindow* parent, std::string plugin_name)
135 : wxPanel(parent), m_plugin_name(plugin_name) {
136 auto size = GetClientSize();
137 auto minsize = GetTextExtent("OpenCPN");
138 SetMinClientSize(wxSize(minsize.GetWidth(), size.GetHeight()));
139 Layout();
140 Bind(wxEVT_PAINT, &PluginIconPanel::OnPaint, this);
141 }
142
143 void OnPaint(wxPaintEvent& event) {
144 auto size = GetClientSize();
145 int minsize = wxMin(size.GetHeight(), size.GetWidth());
146 auto offset = minsize / 10;
147
148 LoadIcon("packageBox.svg", m_bitmap, 2 * minsize / 3);
149 wxPaintDC dc(this);
150 if (!m_bitmap.IsOk()) {
151 wxLogMessage("AddPluginPanel: bitmap is not OK!");
152 return;
153 }
154 dc.DrawBitmap(m_bitmap, offset, offset, true);
155 }
156
157protected:
158 wxBitmap m_bitmap;
159 const std::string m_plugin_name;
160
161 void LoadIcon(const char* plugin_name, wxBitmap& bitmap, int size = 32) {
162 wxFileName path(g_Platform->GetSharedDataDir(), plugin_name);
163 path.AppendDir("uidata");
164 path.AppendDir("traditional");
165 bool ok = false;
166
167 if (path.IsFileReadable()) {
168 bitmap = LoadSVG(path.GetFullPath(), size, size);
169 ok = bitmap.IsOk();
170 }
171
172 if (!ok) {
173 auto style = g_StyleManager->GetCurrentStyle();
174 bitmap = wxBitmap(style->GetIcon(_T("default_pi"), size, size));
175 wxLogMessage("Icon: %s not found.", path.GetFullPath());
176 }
177
178 /*
179 wxFileName path(g_Platform->GetSharedDataDir(), plugin_name);
180 path.AppendDir("uidata");
181 bool ok = false;
182 path.SetExt("png");
183 if (path.IsFileReadable()) {
184 LoadPNGIcon(path.GetFullPath(), size, bitmap);
185 ok = bitmap.IsOk();
186 }
187 if (!ok) {
188 auto style = g_StyleManager->GetCurrentStyle();
189 bitmap = wxBitmap(style->GetIcon( _T("default_pi")));
190 }
191 */
192 }
193};
194
196class InstallButton : public wxPanel {
197public:
198 InstallButton(wxWindow* parent, PluginMetadata metadata)
199 : wxPanel(parent), m_metadata(metadata), m_remove(false) {
200 auto loader = PluginLoader::getInstance();
201 PlugInContainer* found =
202 PlugInByName(metadata.name, loader->GetPlugInArray());
203 std::string label(_("Install"));
204 if (found &&
205 ((found->m_version_major > 0) || (found->m_version_minor > 0))) {
206 label = getUpdateLabel(found, metadata);
207 m_remove = true;
208 }
209 auto button = new wxButton(this, wxID_ANY, label);
210 auto pluginHandler = PluginHandler::getInstance();
211 auto box = new wxBoxSizer(wxHORIZONTAL);
212 box->Add(button);
213 SetSizer(box);
214 Bind(wxEVT_COMMAND_BUTTON_CLICKED, &InstallButton::OnClick, this);
215 }
216
217 void OnClick(wxCommandEvent& event) {
218 wxLogMessage("Selected update: %s", m_metadata.name.c_str());
219 auto top_parent = GetParent()->GetParent()->GetParent();
220 auto dialog = dynamic_cast<UpdateDialog*>(top_parent);
221 wxASSERT(dialog != 0);
222 dialog->SetUpdate(m_metadata);
223 dialog->EndModal(wxID_OK);
224 }
225
226private:
227 PluginMetadata m_metadata;
228 bool m_remove;
229
230 const char* getUpdateLabel(PlugInContainer* pic, PluginMetadata metadata) {
231 SemanticVersion currentVersion(pic->m_version_major, pic->m_version_minor);
232 if (pic->m_version_str != "") {
233 currentVersion = SemanticVersion::parse(pic->m_version_str.ToStdString());
234 }
235 auto newVersion = SemanticVersion::parse(metadata.version);
236 if (newVersion > currentVersion) {
237 return _("Update");
238 } else if (newVersion == currentVersion) {
239 return _("Reinstall");
240 } else {
241 return _("Downgrade");
242 }
243 }
244};
245
247class UpdateWebsiteButton : public wxPanel {
248public:
249 UpdateWebsiteButton(wxWindow* parent, const char* url)
250 : wxPanel(parent), m_url(url) {
251 auto vbox = new wxBoxSizer(wxVERTICAL);
252 auto button = new wxButton(this, wxID_ANY, _("Website"));
253 button->Enable(strlen(url) > 0);
254 vbox->Add(button);
255 SetSizer(vbox);
256 Bind(wxEVT_COMMAND_BUTTON_CLICKED,
257 [=](wxCommandEvent&) { wxLaunchDefaultBrowser(m_url); });
258 }
259
260protected:
261 const std::string m_url;
262};
263
265class CandidateButtonsPanel : public wxPanel {
266public:
267 CandidateButtonsPanel(wxWindow* parent, const PluginMetadata* plugin)
268 : wxPanel(parent) {
269 auto flags = wxSizerFlags().Border();
270
271 auto vbox = new wxBoxSizer(wxVERTICAL);
272 vbox->Add(new InstallButton(this, *plugin),
273 flags.DoubleBorder().Top().Right());
274 vbox->Add(1, 1, 1, wxEXPAND); // Expanding, stretchable spacer
275 m_info_btn = new UpdateWebsiteButton(this, plugin->info_url.c_str());
276 m_info_btn->Hide();
277 vbox->Add(m_info_btn, flags.DoubleBorder().Right());
278 SetSizer(vbox);
279 Fit();
280 }
281
282 void HideDetails(bool hide) {
283 m_info_btn->Show(!hide);
284 GetParent()->Layout();
285 }
286
287private:
288 UpdateWebsiteButton* m_info_btn;
289};
290
292class PluginTextPanel : public wxPanel {
293public:
294 PluginTextPanel(wxWindow* parent, const PluginMetadata* plugin,
295 CandidateButtonsPanel* buttons, bool bshowTuple = false)
296 : wxPanel(parent), m_descr(0), m_buttons(buttons) {
297 auto flags = wxSizerFlags().Border();
298 m_isDesc = false;
299
300 MORE = "<span foreground=\'blue\'>";
301 MORE += _("More");
302 MORE += "...</span>";
303 LESS = "<span foreground=\'blue\'>";
304 LESS += _("Less");
305 LESS += "...</span>";
306
307 auto sum_hbox = new wxBoxSizer(wxHORIZONTAL);
308 m_widthDescription = g_options->GetSize().x / 2;
309
310 // m_summary = staticText(plugin->summary);
311 m_summary = new wxStaticText(
312 this, wxID_ANY, _T(""), wxDefaultPosition,
313 wxSize(m_widthDescription, -1) /*, wxST_NO_AUTORESIZE*/);
314 m_summaryText = wxString(plugin->summary.c_str());
315 m_summary->SetLabel(m_summaryText);
316 m_summary->Wrap(m_widthDescription);
317
318 HardBreakWrapper wrapper(this, m_summaryText, m_widthDescription);
319 m_summaryLineCount = wrapper.GetLineCount() + 1;
320
321 sum_hbox->Add(m_summary);
322 sum_hbox->AddSpacer(10);
323 m_more = staticText("");
324 m_more->SetLabelMarkup(MORE);
325 sum_hbox->Add(m_more, wxSizerFlags());
326
327 auto vbox = new wxBoxSizer(wxVERTICAL);
328 SetSizer(vbox);
329
330 wxString nameText(plugin->name + " " + plugin->version);
331 if (bshowTuple) nameText += " " + plugin->target;
332
333 auto name = staticText(nameText);
334
335 m_descr = new wxStaticText(
336 this, wxID_ANY, _T(""), wxDefaultPosition,
337 wxSize(m_widthDescription, -1) /*, wxST_NO_AUTORESIZE*/);
338 m_descText = wxString(plugin->description.c_str());
339 m_descr->SetLabel(m_descText);
340 m_descr->Wrap(m_widthDescription);
341 m_descr->Hide();
342 vbox->Add(name, flags);
343 vbox->Add(sum_hbox, flags);
344 vbox->Add(m_descr, 0);
345 Fit();
346
347 m_more->Bind(wxEVT_LEFT_DOWN, &PluginTextPanel::OnClick, this);
348 m_descr->Bind(wxEVT_LEFT_DOWN, &PluginTextPanel::OnClick, this);
349 }
350
351 void OnClick(wxMouseEvent& event) {
352 m_descr->Show(!m_descr->IsShown());
353 m_descr->SetLabel(_T(""));
354 m_descr->SetLabel(m_descText);
355 m_descr->Wrap(m_widthDescription);
356 Layout();
357 wxSize asize = GetEffectiveMinSize();
358
359 m_more->SetLabelMarkup(m_descr->IsShown() ? LESS : MORE);
360 m_buttons->HideDetails(!m_descr->IsShown());
361
362 UpdateDialog* swin = wxDynamicCast(GetGrandParent(), UpdateDialog);
363 if (swin) {
364 swin->RecalculateSize();
365 }
366 }
367
368 int m_summaryLineCount;
369 bool m_isDesc;
370
371protected:
372 wxString MORE, LESS;
373
374 wxStaticText* staticText(const wxString& text) {
375 return new wxStaticText(this, wxID_ANY, text, wxDefaultPosition,
376 wxDefaultSize, wxALIGN_LEFT);
377 }
378
379 wxStaticText* m_descr;
380 wxStaticText* m_more;
381 wxStaticText* m_summary;
382 CandidateButtonsPanel* m_buttons;
383 int m_widthDescription;
384 wxString m_descText;
385 wxString m_summaryText;
386};
387
392class OcpnUpdateScrolledWindow : public wxScrolledWindow {
393public:
394 OcpnUpdateScrolledWindow(wxWindow* parent,
395 const std::vector<PluginMetadata>& updates)
396 : wxScrolledWindow(parent),
397 m_updates(updates),
398 m_grid(new wxFlexGridSizer(3, 0, 0)) {
399 auto box = new wxBoxSizer(wxVERTICAL);
400 populateGrid(m_grid);
401 box->Add(m_grid, wxSizerFlags().Proportion(0).Expand());
402 auto butt_box = new wxBoxSizer(wxHORIZONTAL);
403 auto cancel_btn = new wxButton(this, wxID_CANCEL, _("Dismiss"));
404 butt_box->Add(1, 1, 1, wxEXPAND); // Expanding, stretchable spacer
405 butt_box->Add(cancel_btn, wxSizerFlags().Border());
406 box->Add(butt_box, wxSizerFlags().Proportion(0).Expand());
407
408 SetSizer(box);
409 SetMinSize(GetEffectiveMinSize());
410 SetScrollRate(1, 1);
411 };
412
413 void populateGrid(wxFlexGridSizer* grid) {
415 struct metadata_compare {
416 bool operator()(const PluginMetadata& lhs,
417 const PluginMetadata& rhs) const {
418 return lhs.key() < rhs.key();
419 }
420 };
421
422 auto flags = wxSizerFlags();
423 grid->SetCols(3);
424 grid->AddGrowableCol(2);
425 for (auto plugin : m_updates) {
426 grid->Add(new PluginIconPanel(this, plugin.name), flags.Expand());
427 auto buttons = new CandidateButtonsPanel(this, &plugin);
428 PluginTextPanel* tpanel =
429 new PluginTextPanel(this, &plugin, buttons, m_updates.size() > 1);
430 tpanel->m_isDesc = true;
431 grid->Add(tpanel, flags.Proportion(1).Right());
432 grid->Add(buttons, flags.DoubleBorder());
433 grid->Add(new wxStaticLine(this), wxSizerFlags(0).Expand());
434 grid->Add(new wxStaticLine(this), wxSizerFlags(0).Expand());
435 grid->Add(new wxStaticLine(this), wxSizerFlags(0).Expand());
436 }
437 }
438
439private:
440 const std::vector<PluginMetadata> m_updates;
441 wxFlexGridSizer* m_grid;
442};
443
444//} // namespace update_mgr
445
448 const std::vector<PluginMetadata>& updates)
449 : wxDialog(parent, wxID_ANY, _("Plugin Manager"), wxDefaultPosition,
450 wxDefaultSize, wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER) {
451 auto vbox = new wxBoxSizer(wxVERTICAL);
452 SetSizer(vbox);
453
454 m_scrwin = new OcpnUpdateScrolledWindow(this, updates);
455 vbox->Add(m_scrwin, wxSizerFlags(1).Expand());
456
457 RecalculateSize();
458
459 Center();
460}
461
462void UpdateDialog::RecalculateSize() {
463 int calcHeight = 0;
464 int calcWidth = 0;
465 wxWindowList& kids = m_scrwin->GetChildren();
466 for (unsigned int i = 0; i < kids.GetCount(); i++) {
467 wxWindowListNode* node = kids.Item(i);
468 wxWindow* win = node->GetData();
469
470 if (win && win->IsKindOf(CLASSINFO(PluginTextPanel))) {
471 PluginTextPanel* panel = (PluginTextPanel*)win;
472 if (panel->m_isDesc) {
473 wxSize tsize = win->GetEffectiveMinSize();
474 calcHeight += tsize.y + GetCharHeight();
475 calcWidth = tsize.x * 2;
476 }
477 }
478 }
479
480 calcHeight += 3 * GetCharHeight(); // "dismiss" button
481 calcWidth = wxMin(calcWidth, g_Platform->getDisplaySize().x);
482
483 m_scrwin->SetMinSize(wxSize(calcWidth, calcHeight));
484
485#ifdef __OCPN__ANDROID__
486 SetMinSize(g_Platform->getDisplaySize());
487#endif
488
489 SetMaxSize(g_Platform->getDisplaySize());
490
491 Fit();
492 Layout();
493}
The two buttons 'install' and 'website', the latter optionally hidden.
Definition: update_mgr.cpp:265
Download and install a PluginMetadata item when clicked.
Definition: update_mgr.cpp:196
The list of download candidates in a scrolled window + OK and Settings button.
Definition: update_mgr.cpp:392
void populateGrid(wxFlexGridSizer *grid)
Definition: update_mgr.cpp:413
A plugin icon, scaled to about 2/3 of available space.
Definition: update_mgr.cpp:132
Plugin name, version, summary + an optionally shown description.
Definition: update_mgr.cpp:292
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.
Definition: update_mgr.cpp:447
Invokes client browser on plugin info_url when clicked.
Definition: update_mgr.cpp:247
Plugin metadata, reflects the xml format directly.
Versions uses a modified semantic versioning scheme: major.minor.revision.post-tag+build.
Definition: semantic_vers.h:51
static SemanticVersion parse(std::string s)
Parse a version string, sets major == -1 on errors.