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 "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(_T("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( _T("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, _T(""), 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, _T(""), 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}
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.
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.