31 #include <wx/button.h>
33 #include <wx/filename.h>
35 #include <wx/msgdlg.h>
37 #include <wx/progdlg.h>
39 #include <wx/statline.h>
41 #include "model/catalog_handler.h"
42 #include "catalog_mgr.h"
43 #include "model/downloader.h"
44 #include "model/ocpn_utils.h"
45 #include "OCPNPlatform.h"
46 #include "model/plugin_handler.h"
47 #include "download_mgr.h"
61 static const std::string SEP(
"\\");
63 static const std::string SEP(
"/");
66 namespace catalog_mgr {
70 Helpers(wxWindow* _parent) : parent(_parent) {}
72 wxStaticText* staticText(
const char* text) {
73 return new wxStaticText(parent, wxID_ANY, text);
76 wxButton* makeButton(
const char* label,
int id = wxID_ANY) {
77 return new wxButton(parent, wxID_ANY, label);
92 : wxDialog(parent, wxID_ANY, _(
"Manage Plugin Catalog"),
93 wxDefaultPosition, wxDefaultSize,
94 wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER | wxSTAY_ON_TOP),
98 HIDE =
"<span foreground=\'blue\'>";
100 HIDE +=
" <<<</span>";
101 ADVANCED =
"<span foreground=\'blue\'>";
102 ADVANCED += _(
"Ultra advanced");
103 ADVANCED +=
" >>></span>";
105 auto sizer =
new wxBoxSizer(wxVERTICAL);
106 auto flags = wxSizerFlags().Expand().Border();
110 sizer->Add(
new UrlChannel(
this, m_catalog_grid), flags);
111 sizer->Add(
new wxStaticLine(
this), flags);
113 sizer->Add(m_catalog_grid, flags);
114 sizer->Add(
new wxStaticLine(
this), flags);
116 m_advanced =
new wxStaticText(
this, wxID_ANY,
"");
117 m_advanced->Bind(wxEVT_LEFT_DOWN,
118 [=](wxMouseEvent& e) { toggleUrlEdit(); });
119 sizer->Add(m_advanced, flags);
121 m_url_box =
new wxBoxSizer(wxVERTICAL);
122 m_url_edit =
new UrlEdit(
this);
123 m_url_box->Add(m_url_edit, flags);
124 m_url_box->Add(
new Buttons(
this, m_catalog_grid),
125 wxSizerFlags().Border().Right());
126 sizer->Add(m_url_box, flags);
127 sizer->Add(
new wxStaticLine(
this), flags);
129 auto done = makeButton(_(
"Done"));
130 sizer->Add(done, wxSizerFlags().Border().Right());
131 done->Bind(wxEVT_COMMAND_BUTTON_CLICKED, [=](wxCommandEvent e) {
133 wxCommandEvent evt(CATALOG_DLG_CLOSE);
134 wxPostEvent(GetParent(), evt);
139 Bind(wxEVT_CLOSE_WINDOW, [=](wxCloseEvent& e) {
141 wxCommandEvent cmd_evt(CATALOG_DLG_CLOSE);
142 wxPostEvent(GetParent(), cmd_evt);
149 SetMinClientSize(size);
155 wxString HIDE, ADVANCED;
157 wxBoxSizer* m_url_box;
160 wxStaticText* m_advanced;
170 auto size = GetTextExtent(uri);
171 size.SetWidth(size.GetWidth() * 120 / 100);
172 size.SetHeight(size.GetHeight() * 130 / 100);
176 void toggleUrlEdit() {
177 m_show_edit = !m_show_edit;
178 m_url_box->GetItem((
size_t)0)->GetWindow()->Show(m_show_edit);
179 m_url_box->GetItem((
size_t)1)->GetWindow()->Show(m_show_edit);
180 m_advanced->SetLabelMarkup(m_show_edit ? HIDE : ADVANCED);
187 auto sizer =
new wxBoxSizer(wxHORIZONTAL);
189 wxSizerFlags().Expand().Border().Align(wxALIGN_CENTER_VERTICAL);
190 sizer->Add(staticText(_(
"Catalog URL status: ")), flags);
191 auto catalog = CatalogHandler::getInstance();
192 int channels = catalog->GetChannels().size();
193 auto text = staticText(channels > 0 ? _(
"OK") : _(
"Error"));
194 sizer->Add(text, flags);
208 using CmdEvt = wxCommandEvent;
210 auto grid =
new wxFlexGridSizer(4, 0, 0);
211 auto plugin_handler = PluginHandler::getInstance();
212 grid->AddGrowableCol(0);
213 grid->AddGrowableCol(1);
214 grid->AddGrowableCol(2);
215 grid->AddGrowableCol(3);
216 auto flags = wxSizerFlags().Align(wxALIGN_CENTER_VERTICAL);
217 flags = flags.DoubleBorder();
222 grid->Add(staticText(_(
"Current active plugin catalog")), flags);
223 grid->Add(staticText(
""), flags);
224 grid->Add(staticText(
""), flags);
225 grid->Add(staticText(
""), flags);
229 grid->Add(staticText(_(
"Default catalog")), flags);
230 grid->Add(staticText(
""), flags);
231 grid->Add(staticText(
""), flags);
232 auto use_default = makeButton(_(
"Use as active catalog"));
233 grid->Add(use_default, wxSizerFlags().Border());
234 use_default->Bind(wxEVT_COMMAND_BUTTON_CLICKED,
235 [=](CmdEvt& e) { useDefaultCatalog(); });
239 grid->Add(staticText(_(
"Latest available catalog:")), flags);
240 grid->Add(staticText(
""), flags);
241 grid->Add(staticText(
""), flags);
242 auto use_latest = makeButton(_(
"Use as active catalog"));
243 use_latest->Bind(wxEVT_COMMAND_BUTTON_CLICKED,
244 [=](CmdEvt& e) { UseLatestCatalog(); });
246 grid->Add(use_latest, wxSizerFlags().Border());
254 void UpdateVersion(wxSizer* grid,
CatalogData data,
size_t ix) {
256 dynamic_cast<wxStaticText*
>(grid->GetItem(ix)->GetWindow());
257 version->SetLabel(data.version);
259 dynamic_cast<wxStaticText*
>(grid->GetItem(ix + 1)->GetWindow());
260 date->SetLabel(data.date);
269 auto grid =
dynamic_cast<wxSizer*
>(GetSizer());
270 UpdateVersion(grid, data, 1);
272 UpdateVersion(grid, data, 5);
274 UpdateVersion(grid, data, 9);
279 std::string GetDefaultCatalogPath() {
280 std::string path = g_Platform->GetSharedDataDir().ToStdString();
282 path +=
"ocpn-plugins.xml";
286 void ReloadAvailableVersion() {
287 auto handler = CatalogHandler::getInstance();
288 std::ostringstream xml;
289 auto status = handler->DownloadCatalog(&xml);
291 if (status != CatalogHandler::ServerStatus::OK) {
292 message = _(
"Cannot download data from url");
294 status = handler->ParseCatalog(xml.str(),
true);
295 if (status != CatalogHandler::ServerStatus::OK) {
296 message = _(
"Cannot parse downloaded data");
299 wxMessageBox(message, _(
"Catalog update problem"), wxICON_ERROR);
305 std::string GetPrivateCatalogPath() {
306 auto plugin_handler = PluginHandler::getInstance();
308 path += SEP +
"ocpn-plugins.xml";
312 void useDefaultCatalog() {
313 auto src = GetDefaultCatalogPath();
314 auto dest = GetPrivateCatalogPath();
315 ocpn::copy_file(src, dest);
320 void UseLatestCatalog() {
321 auto catalog = CatalogHandler::getInstance();
322 std::ofstream dest(GetPrivateCatalogPath());
323 catalog->DownloadCatalog(&dest);
325 catalog->ClearCatalogData();
335 m_catalog_grid(catalog_grid),
337 auto sizer =
new wxBoxSizer(wxHORIZONTAL);
338 auto flags = wxSizerFlags().Right().Bottom().Border();
340 auto clear = makeButton(_(
"Clear"));
341 clear->Bind(wxEVT_COMMAND_BUTTON_CLICKED,
342 [=](wxCommandEvent& ev) { clearUrl(); });
343 sizer->Add(clear, flags);
345 auto use_default = makeButton(_(
"Use default location"));
346 use_default->Bind(wxEVT_COMMAND_BUTTON_CLICKED,
347 [=](wxCommandEvent& ev) { useDefaultUrl(); });
348 sizer->Add(use_default, flags);
350 auto update = makeButton(_(
"Save"));
351 sizer->Add(update, flags);
352 update->Bind(wxEVT_COMMAND_BUTTON_CLICKED,
353 [=](wxCommandEvent& ev) { updateUrl(); });
360 void useDefaultUrl() {
362 m_parent->m_url_edit->setText(url);
365 void clearUrl() { m_parent->m_url_edit->clear(); }
368 auto text = m_parent->m_url_edit->getText();
369 CatalogHandler::getInstance()->
SetCustomUrl(text.c_str());
370 m_catalog_grid->ReloadAvailableVersion();
380 : wxPanel(parent),
Helpers(
this), m_catalog_grid(catalog_grid) {
381 auto sizer =
new wxBoxSizer(wxHORIZONTAL);
383 wxSizerFlags().Expand().Border().Align(wxALIGN_CENTER_VERTICAL);
384 sizer->Add(staticText(_(
"Catalog channel: ")), flags);
385 auto catalog = CatalogHandler::getInstance();
386 wxArrayString channel_list;
387 for (
auto channel : catalog->GetChannels()) {
388 channel_list.Add(channel.c_str());
390 auto channels =
new wxChoice(
this, wxID_ANY, wxDefaultPosition,
391 wxDefaultSize, channel_list);
393 int ix = channels->FindString(current.c_str());
394 channels->SetSelection(ix != wxNOT_FOUND ? ix : 0);
395 channels->Bind(wxEVT_CHOICE,
396 [=](wxCommandEvent& e) { onChannelChange(e); });
397 sizer->Add(channels, flags);
403 void onChannelChange(wxCommandEvent& ev) {
405 m_catalog_grid->ReloadAvailableVersion();
408 std::string m_active_channel;
415 auto sizer =
new wxBoxSizer(wxVERTICAL);
416 auto url_location =
new wxBoxSizer(wxHORIZONTAL);
417 auto flags = wxSizerFlags().Align(wxALIGN_CENTER_VERTICAL);
418 url_location->Add(staticText(_(
"Custom catalog URL: ")), flags);
419 flags = wxSizerFlags().Expand().Border();
420 sizer->Add(url_location, flags);
422 auto url_edit =
new wxBoxSizer(wxHORIZONTAL);
423 auto uri = CatalogHandler::getInstance()->
GetCustomUrl();
424 m_url_ctrl =
new wxTextCtrl(
this, wxID_ANY, uri);
426 m_url_ctrl->SetMinClientSize(the_parent->getWindowSize());
427 url_edit->Add(m_url_ctrl, wxSizerFlags().Expand());
429 sizer->Add(url_edit, flags);
435 void setText(std::string text) {
438 m_url_ctrl->Refresh();
441 void clear() { m_url_ctrl->Clear(); }
443 std::string getText() {
444 return std::string(m_url_ctrl->GetLineText(0).ToStdString());
447 wxTextCtrl* m_url_ctrl;
456 CatalogLoad(wxWindow* parent,
bool use_latest =
false)
457 : wxPanel(parent),
Helpers(
this), m_simple(use_latest) {
458 auto sizer =
new wxBoxSizer(wxVERTICAL);
459 auto flags = wxSizerFlags().Expand().Border();
461 sizer->Add(m_grid, flags);
462 sizer->Add(1, 1, 1, wxEXPAND);
465 sizer->Add(m_buttons, flags.Bottom().Right());
467 auto size = GetTextExtent(_(
"Check latest release..."));
468 size.SetHeight(size.GetHeight() * 10);
469 size.SetWidth(size.GetWidth() * 5 / 2);
470 SetMinClientSize(size);
471 std::thread worker([=]() {
Worker(); });
478 Bind(CHANNELS_DL_DONE,
479 [=](wxCommandEvent& ev) { m_grid->CellDone(ev, 1); });
480 Bind(CHANNELS_PARSE_DONE,
481 [=](wxCommandEvent& ev) { m_grid->CellDone(ev, 3); });
482 Bind(CATALOG_DL_DONE, [=](wxCommandEvent& ev) { m_grid->CellDone(ev, 5); });
483 Bind(CATALOG_PARSE_DONE, [=](wxCommandEvent& ev) {
workerDone(ev); });
486 void PostEvent(
int evt_id, catalog_status status, std::string message) {
487 wxCommandEvent evt(evt_id);
488 evt.SetInt(
static_cast<int>(status));
489 evt.SetString(message);
490 wxPostEvent(
this, evt);
495 m_grid->CellDone(ev, 5);
497 std::string message = _(
"Catalog updated").ToStdString();
500 path +=
"ocpn-plugins.xml";
501 std::ofstream stream;
502 stream.open(path, std::ios::out);
503 if (stream.is_open()) {
504 stream << m_xml.str();
506 ev.SetString(
"Catalog updated");
507 ev.SetInt(
static_cast<int>(catalog_status::OK_MSG));
509 ev.SetString(
"Update error (cannot writé to file)");
510 ev.SetInt(
static_cast<int>(catalog_status::OS_ERROR));
511 wxLogMessage(
"Update error: Cannot write to %s", path.c_str());
513 m_grid->CellDone(ev, 6);
514 m_buttons->ActivateOk();
517 auto handler = CatalogHandler::getInstance();
518 catalog_data = handler->LatestCatalogData();
528 auto catalog = CatalogHandler::getInstance();
529 std::ostringstream json;
531 auto status = catalog->LoadChannels(&json);
532 PostEvent(CHANNELS_DL_DONE, status, catalog->LastErrorMsg());
534 status = catalog->LoadChannels(json.str());
535 PostEvent(CHANNELS_PARSE_DONE, status, catalog->LastErrorMsg());
537 if (status == catalog_status::OK) {
538 auto channel = catalog->GetActiveChannel();
539 status = catalog_status::OK_MSG;
540 PostEvent(CHANNELS_PARSE_DONE, status, channel);
543 status = catalog->DownloadCatalog(&m_xml);
544 PostEvent(CATALOG_DL_DONE, status, catalog->LastErrorMsg());
546 status = catalog->ParseCatalog(m_xml.str(),
true);
547 if (status == catalog_status::OK) {
548 PostEvent(CATALOG_PARSE_DONE, catalog_status::OK_MSG,
549 catalog->LatestCatalogData().version);
551 PostEvent(CATALOG_PARSE_DONE, status, catalog->LastErrorMsg());
558 auto grid =
new wxFlexGridSizer(2, 0, 0);
560 wxSizerFlags().Expand().Border().Align(wxALIGN_CENTER_VERTICAL);
561 grid->Add(staticText(_(
"Server is reachable...")), flags);
562 grid->Add(staticText(
""), flags);
563 grid->Add(staticText(_(
"Check channel...")), flags);
564 grid->Add(staticText(
""), flags);
565 grid->Add(staticText(_(
"Check latest release...")), flags);
566 grid->Add(staticText(
""), flags);
567 grid->Add(staticText(
""), flags);
568 grid->Add(staticText(
""), flags);
569 auto url = CatalogHandler::getInstance()->
GetCustomUrl();
571 grid->Add(staticText(_(
"Custom URL")), flags);
572 grid->Add(staticText(url.c_str()), flags);
580 void CellDone(
const wxCommandEvent& event,
size_t index) {
582 auto cell = GetSizer()->GetItem(index)->GetWindow();
583 auto code =
static_cast<catalog_status
>(
event.GetInt());
584 if (code == catalog_status::OK) {
585 cell->SetLabel(_(
"OK"));
586 }
else if (code == catalog_status::OK_MSG) {
587 cell->SetLabel(event.GetString().ToStdString());
589 auto msg = std::string(_(
"Fail: ")) +
event.GetString().ToStdString();
590 cell->SetLabel(msg.c_str());
598 Buttons(wxWindow* parent) : wxPanel(parent) {
599 using CmdEvt = wxCommandEvent;
601 auto sizer =
new wxBoxSizer(wxHORIZONTAL);
602 auto flags = wxSizerFlags().Right().Bottom().Border();
603 sizer->Add(1, 1, 100, wxEXPAND);
604 auto cancel =
new wxButton(
this, wxID_CANCEL, _(
"Cancel"));
605 sizer->Add(cancel, flags);
606 m_ok =
new wxButton(
this, wxID_OK, _(
"OK"));
608 sizer->Add(m_ok, flags);
615 void ActivateOk() { m_ok->Enable(
true); }
620 std::ostringstream m_xml;
630 : wxFrame(parent, wxID_ANY, _(
"Catalog Manager"), wxDefaultPosition,
631 wxDefaultSize, wxDEFAULT_FRAME_STYLE | wxRESIZE_BORDER) {
632 auto vbox =
new wxBoxSizer(wxHORIZONTAL);
634 wxSizerFlags(1).Expand());
635 Bind(wxEVT_CLOSE_WINDOW, [
this](wxCloseEvent&) {
636 wxCommandEvent evt(EVT_PLUGINS_RELOAD);
637 wxPostEvent(GetParent(), evt);
640 Bind(CATALOG_DLG_CLOSE, [
this](wxCommandEvent&) {
641 wxCommandEvent evt(EVT_PLUGINS_RELOAD);
642 wxPostEvent(GetParent(), evt);
655 : wxDialog(parent, wxID_ANY, _(
"Catalog Manager"), wxDefaultPosition,
656 wxDefaultSize, wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER) {
657 auto vbox =
new wxBoxSizer(wxHORIZONTAL);
659 Bind(wxEVT_CLOSE_WINDOW, [&](wxCloseEvent& e) { EndModal(wxID_OK); });
660 Bind(CATALOG_DLG_CLOSE, [&](wxCommandEvent& ev) { EndModal(wxID_OK); });
AdvancedCatalogDialog(wxWindow *parent)
Invoke the advanced catalog dialog after a status check.
CatalogData DefaultCatalogData()
Data for default version, installed with main opencpn.
std::string GetDefaultUrl()
Get the default URL, with actual channel included.
CatalogData LatestCatalogData()
Data for latest parsed data marked as latest.
CatalogData UserCatalogData()
Data for user catalog which overrides the default one.
std::string GetActiveChannel()
Get the branch (a.
void SetCustomUrl(const char *url)
Set a custom url, overrides also channel settings.
void ClearCatalogData()
Invalidate *CatalogData caches.
std::string GetCustomUrl()
Set a custom url, overrides also channel settings.
bool SetActiveChannel(const char *channel)
Set the active channel used when downloading catalog.
SimpleCatalogDialog(wxWindow *parent)
Perform a simple catalog update without options.
Download status/progress window, always shown at start-up.
void Worker()
Runs in separate, detached thread, started from ctor.
void workerDone(wxCommandEvent &ev)
Last part of initial Worker() call, in main thread.
The advanced dialog showing channels, possible updates, rollback etc.
wxSize getWindowSize()
The window width is determined by the normally hidden custom url text control.
wxDEFINE_EVENT(REST_IO_EVT, ObservedEvt)
Event from IO thread to main.
Datatypes and methods to parse ocpn-plugins.xml XML data, either complete catalog or a single plugin.
Grid with Server is Reachable..., Check channel...
Active catalog: The current active, the default and latest downloaded + buttons to use default or lat...
void UpdateVersions()
Update version and date for default, latest and active catalog.
Combobox where user selects active catalog channel.
Custom url edit control, a text line.
The Url Status line at top.