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 
25 
26 #include "config.h"
27 
28 #include <fstream>
29 #include <sstream>
30 #include <thread>
31 
32 #include <wx/button.h>
33 #include <wx/debug.h>
34 #include <wx/filename.h>
35 #include <wx/log.h>
36 #include <wx/msgdlg.h>
37 #include <wx/panel.h>
38 #include <wx/progdlg.h>
39 #include <wx/sizer.h>
40 #include <wx/statline.h>
41 
42 #include "catalog_handler.h"
43 #include "catalog_mgr.h"
44 #include "Downloader.h"
45 #include "ocpn_utils.h"
46 #include "OCPNPlatform.h"
47 #include "PluginHandler.h"
48 #include "download_mgr.h"
49 
50 extern OCPNPlatform*            g_Platform;
51 
52 
53 /** Posted by Worker() in CatalogLoad, consumed by CatalogLoad::DialogGrid. */
54 wxDEFINE_EVENT(CHANNELS_DL_DONE, wxCommandEvent);
55 wxDEFINE_EVENT(CHANNELS_PARSE_DONE, wxCommandEvent);
56 wxDEFINE_EVENT(CATALOG_DL_DONE, wxCommandEvent);
57 wxDEFINE_EVENT(CATALOG_PARSE_DONE, wxCommandEvent);
58 
59 
60 /** Posted by  CatalogUpdate on close. */
61 wxDEFINE_EVENT(CATALOG_DLG_CLOSE, wxCommandEvent);
62 
63 #ifdef _WIN32
64 static const std::string SEP("\\");
65 #else
66 static const std::string SEP("/");
67 #endif
68 
69 namespace catalog_mgr
70 {
71 
72 class Helpers
73 {
74     public:
Helpers(wxWindow * _parent)75         Helpers(wxWindow* _parent): parent(_parent)
76         {}
77 
staticText(const char * text)78         wxStaticText* staticText(const char* text)
79         {
80             return new wxStaticText(parent, wxID_ANY, text);
81         }
82 
makeButton(const char * label,int id=wxID_ANY)83         wxButton* makeButton(const char* label, int id = wxID_ANY)
84         {
85             return new wxButton(parent, wxID_ANY, label);
86         }
87 
88     private:
89         wxWindow* parent;
90 };
91 
92 
93 /** The advanced dialog showing channels, possible updates, rollback etc. */
94 class CatalogUpdate: public wxDialog, Helpers
95 {
96     protected:
97         class UrlEdit; //forward
98         class ActiveCatalogGrid; //forward
99 
100     public:
CatalogUpdate(wxWindow * parent)101         CatalogUpdate(wxWindow* parent)
102             :wxDialog(parent, wxID_ANY, _("Manage Plugin Catalog"),
103                       wxDefaultPosition , wxDefaultSize,
104                       wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER | wxSTAY_ON_TOP),
105             Helpers(this),
106             m_show_edit(true)
107         {
108             auto sizer = new wxBoxSizer(wxVERTICAL);
109             auto flags = wxSizerFlags().Expand().Border();
110 
111             m_catalog_grid = new ActiveCatalogGrid(this);
112             sizer->Add(new UrlStatus(this), flags);
113             sizer->Add(new UrlChannel(this, m_catalog_grid), flags);
114             sizer->Add(new wxStaticLine(this), flags);
115 
116             sizer->Add(m_catalog_grid, flags);
117             sizer->Add(new wxStaticLine(this), flags);
118 
119             m_advanced  = new wxStaticText(this, wxID_ANY, "");
120             m_advanced->Bind(wxEVT_LEFT_DOWN,
121                              [=](wxMouseEvent& e) { toggleUrlEdit(); } );
122             sizer->Add(m_advanced, flags);
123 
124             m_url_box = new wxBoxSizer(wxVERTICAL);
125             m_url_edit = new UrlEdit(this);
126             m_url_box->Add(m_url_edit, flags);
127             m_url_box->Add(new Buttons(this, m_catalog_grid),
128                            wxSizerFlags().Border().Right());
129             sizer->Add(m_url_box, flags);
130             sizer->Add(new wxStaticLine(this), flags);
131 
132             auto done = makeButton(_("Done"));
133             sizer->Add(done, wxSizerFlags().Border().Right());
134             done->Bind(wxEVT_COMMAND_BUTTON_CLICKED, [=] (wxCommandEvent e) {
135                 EndModal(wxID_OK);
136                 wxCommandEvent evt(CATALOG_DLG_CLOSE);
137                 wxPostEvent(GetParent(), evt);
138                 e.Skip();
139             });
140             toggleUrlEdit();
141 
142             Bind(wxEVT_CLOSE_WINDOW, [=](wxCloseEvent& e) {
143                 EndModal(wxID_OK);
144                 wxCommandEvent cmd_evt(CATALOG_DLG_CLOSE);
145                 wxPostEvent(GetParent(), cmd_evt);
146                 e.Skip();
147             });
148 
149             SetSizer(sizer);
150             auto size = getWindowSize();
151             size.SetHeight(1);
152             SetMinClientSize(size);
153             Fit();
154             ShowModal();
155         }
156 
157     protected:
158         const char* const HIDE =
159             _("<span foreground='blue'>Hide &lt;&lt;&lt;</span>");
160         const char* const ADVANCED =
161             _("<span foreground='blue'>Ultra advanced &gt;&gt;&gt;</span>");
162 
163         wxBoxSizer* m_url_box;
164         ActiveCatalogGrid* m_catalog_grid;
165         UrlEdit* m_url_edit;
166         wxStaticText* m_advanced;
167         bool m_show_edit;
168 
169         /**
170          * The window width  is determined by the normally hidden custom
171          * url text control. Return the size of the main window including
172          * the hidden part.
173          */
getWindowSize()174         wxSize getWindowSize()
175         {
176             auto uri = CatalogHandler::getInstance()->GetDefaultUrl();
177             auto size = GetTextExtent(uri);
178             size.SetWidth(size.GetWidth()  * 120 / 100);
179             size.SetHeight(size.GetHeight() * 130 / 100);
180             return size;
181         }
182 
toggleUrlEdit()183         void toggleUrlEdit()
184         {
185             m_show_edit = !m_show_edit;
186             m_url_box->GetItem((size_t) 0)->GetWindow()->Show(m_show_edit);
187             m_url_box->GetItem((size_t) 1)->GetWindow()->Show(m_show_edit);
188             m_advanced->SetLabelMarkup(m_show_edit ? HIDE : ADVANCED);
189             Fit();
190         }
191 
192         /** The Url Status line at top */
193         struct UrlStatus: public wxPanel, public Helpers
194         {
UrlStatuscatalog_mgr::CatalogUpdate::UrlStatus195             UrlStatus(wxWindow* parent) : wxPanel(parent), Helpers(this)
196             {
197                 auto sizer = new wxBoxSizer(wxHORIZONTAL);
198                 auto flags = wxSizerFlags()
199                     .Expand().Border().Align(wxALIGN_CENTER_VERTICAL);
200                 sizer->Add(staticText(_("Catalog URL status: ")), flags);
201                 auto catalog = CatalogHandler::getInstance();
202                 int channels = catalog->GetChannels().size();
203                 auto text = staticText(channels > 0? _("OK"): _("Error"));
204                 sizer->Add(text, flags);
205 
206                 SetSizer(sizer);
207                 Fit();
208                 Show();
209             }
210         };
211 
212         /**
213          * Active catalog: The current active, the default and latest
214          * downloaded + buttons to use default or latest.
215          */
216         struct ActiveCatalogGrid: public wxPanel, public Helpers
217         {
ActiveCatalogGridcatalog_mgr::CatalogUpdate::ActiveCatalogGrid218             ActiveCatalogGrid(wxWindow* parent)
219                 :wxPanel(parent), Helpers(this)
220             {
221                 using CmdEvt = wxCommandEvent;
222 
223                 auto grid = new wxFlexGridSizer(4, 0, 0);
224                 auto plugin_handler = PluginHandler::getInstance();
225                 grid->AddGrowableCol(0);
226                 grid->AddGrowableCol(1);
227                 grid->AddGrowableCol(2);
228                 grid->AddGrowableCol(3);
229                 auto flags = wxSizerFlags().Align(wxALIGN_CENTER_VERTICAL);
230                 flags = flags.DoubleBorder();
231 
232                 /* Cell 0..3 */
233                 CatalogData catalog_data =
234                     CatalogHandler::getInstance()->UserCatalogData();
235                 grid->Add(staticText(
236                             _("Current active plugin catalog")), flags);
237                 grid->Add(staticText(""), flags);
238                 grid->Add(staticText(""), flags);
239                 grid->Add(staticText(""), flags);
240 
241                 /* Cell 4..7 */
242                 catalog_data =
243                     CatalogHandler::getInstance()->DefaultCatalogData();
244                 grid->Add(staticText(_("Default catalog")), flags);
245                 grid->Add(staticText(""), flags);
246                 grid->Add(staticText(""), flags);
247                 auto use_default = makeButton(_("Use as active catalog"));
248                 grid->Add(use_default, wxSizerFlags().Border());
249                 use_default->Bind(wxEVT_COMMAND_BUTTON_CLICKED,
250                                   [=](CmdEvt& e) { useDefaultCatalog(); });
251 
252                 /* Cell 8..11 */
253                 catalog_data =
254                     CatalogHandler::getInstance()->LatestCatalogData();
255                 grid->Add(staticText(_("Latest available catalog:")), flags);
256                 grid->Add(staticText(""), flags);
257                 grid->Add(staticText(""), flags);
258                 auto use_latest = makeButton(_("Use as active catalog"));
259                 use_latest->Bind(wxEVT_COMMAND_BUTTON_CLICKED,
260                                  [=](CmdEvt& e) { UseLatestCatalog(); });
261 
262                 grid->Add(use_latest, wxSizerFlags().Border());
263 
264                 SetSizer(grid);
265                 UpdateVersions();
266                 Fit();
267                 Show();
268             }
269 
UpdateVersioncatalog_mgr::CatalogUpdate::ActiveCatalogGrid270             void UpdateVersion(wxSizer* grid, CatalogData data, size_t ix)
271             {
272                 auto version = dynamic_cast<wxStaticText*>(
273                         grid->GetItem(ix)->GetWindow());
274                 version->SetLabel(data.version);
275                 auto date = dynamic_cast<wxStaticText*>(
276                         grid->GetItem(ix + 1)->GetWindow());
277                 date->SetLabel(data.date);
278             }
279 
280             /**
281              * Update version and date for default, latest and active
282              * catalog.
283              */
UpdateVersionscatalog_mgr::CatalogUpdate::ActiveCatalogGrid284             void UpdateVersions()
285             {
286                 CatalogData data =
287                     CatalogHandler::getInstance()->UserCatalogData();
288                 auto grid = dynamic_cast<wxSizer*>(GetSizer());
289                 UpdateVersion(grid, data, 1);
290                 data = CatalogHandler::getInstance()->DefaultCatalogData();
291                 UpdateVersion(grid, data, 5);
292                 data = CatalogHandler::getInstance()->LatestCatalogData();
293                 UpdateVersion(grid, data, 9);
294                 Refresh(true);
295                 Update();
296             }
297 
GetDefaultCatalogPathcatalog_mgr::CatalogUpdate::ActiveCatalogGrid298             std::string GetDefaultCatalogPath()
299             {
300                 std::string path
301                     = g_Platform->GetSharedDataDir().ToStdString();
302                 path += SEP ;
303                 path += "ocpn-plugins.xml";
304                 return path;
305             }
306 
ReloadAvailableVersioncatalog_mgr::CatalogUpdate::ActiveCatalogGrid307             void ReloadAvailableVersion()
308             {
309                 auto handler = CatalogHandler::getInstance();
310                 std::ostringstream xml;
311                 auto status = handler->DownloadCatalog(&xml);
312                 std::string message;
313                 if (status != CatalogHandler::ServerStatus::OK) {
314                     message = _("Cannot download data from url");
315                 }
316                 status = handler->ParseCatalog(xml.str(), true);
317                 if (status != CatalogHandler::ServerStatus::OK) {
318                     message = _("Cannot parse downloaded data");
319                 }
320                 if (message != "") {
321                     wxMessageBox(message,
322                                  _("Catalog update problem"),
323                                  wxICON_ERROR);
324                 }
325                 else {
326                     UpdateVersions();
327                 }
328             }
329 
GetPrivateCatalogPathcatalog_mgr::CatalogUpdate::ActiveCatalogGrid330             std::string GetPrivateCatalogPath()
331             {
332                 auto plugin_handler = PluginHandler::getInstance();
333                 std::string path =
334                     g_Platform->GetPrivateDataDir().ToStdString();
335                 path += SEP + "ocpn-plugins.xml";
336                 return path;
337             }
338 
useDefaultCatalogcatalog_mgr::CatalogUpdate::ActiveCatalogGrid339             void useDefaultCatalog()
340             {
341                 auto src = GetDefaultCatalogPath();
342                 auto dest = GetPrivateCatalogPath();
343                 ocpn::copy_file(src, dest );
344                 CatalogHandler::getInstance()->ClearCatalogData();
345                 UpdateVersions();
346             }
347 
UseLatestCatalogcatalog_mgr::CatalogUpdate::ActiveCatalogGrid348             void UseLatestCatalog()
349             {
350                auto catalog = CatalogHandler::getInstance();
351                std::ofstream dest(GetPrivateCatalogPath());
352                catalog->DownloadCatalog(&dest);
353                dest.close();
354                catalog->ClearCatalogData();
355                UpdateVersions();
356             }
357         };
358 
359         /** The buttons below custom url: Use Default and Update. */
360         struct Buttons: public wxPanel, public Helpers
361         {
Buttonscatalog_mgr::CatalogUpdate::Buttons362             Buttons(wxWindow* parent, ActiveCatalogGrid* catalog_grid)
363                 : wxPanel(parent), Helpers(this),
364                 m_catalog_grid(catalog_grid),
365                 m_parent(dynamic_cast<CatalogUpdate*>(GetParent()))
366             {
367                 auto sizer = new wxBoxSizer(wxHORIZONTAL);
368                 auto flags = wxSizerFlags().Right().Bottom().Border();
369 
370                 auto clear = makeButton(_("Clear"));
371                 clear->Bind(wxEVT_COMMAND_BUTTON_CLICKED,
372                             [=](wxCommandEvent& ev) { clearUrl(); });
373                 sizer->Add(clear, flags);
374 
375                 auto use_default = makeButton(_("Use default location"));
376                 use_default->Bind(wxEVT_COMMAND_BUTTON_CLICKED,
377                                   [=](wxCommandEvent& ev) { useDefaultUrl(); });
378                 sizer->Add(use_default, flags);
379 
380                 auto update = makeButton(_("Save"));
381                 sizer->Add(update, flags);
382                 update->Bind(wxEVT_COMMAND_BUTTON_CLICKED,
383                              [=](wxCommandEvent& ev) { updateUrl(); });
384 
385                 SetSizer(sizer);
386                 Fit();
387                 Show();
388             }
389 
useDefaultUrlcatalog_mgr::CatalogUpdate::Buttons390             void useDefaultUrl()
391             {
392                 auto url = CatalogHandler::getInstance()->GetDefaultUrl();
393                 m_parent->m_url_edit->setText(url);
394             }
395 
clearUrlcatalog_mgr::CatalogUpdate::Buttons396             void clearUrl()
397             {
398                 m_parent->m_url_edit->clear();
399             }
400 
updateUrlcatalog_mgr::CatalogUpdate::Buttons401             void updateUrl()
402             {
403                 auto text =  m_parent->m_url_edit->getText();
404                 CatalogHandler::getInstance()->SetCustomUrl(text.c_str());
405                 m_catalog_grid->ReloadAvailableVersion();
406             }
407 
408             ActiveCatalogGrid* m_catalog_grid;
409             CatalogUpdate* m_parent;
410         };
411 
412 
413         /** Combobox where user selects active catalog channel. */
414         struct UrlChannel: public wxPanel, public Helpers
415         {
UrlChannelcatalog_mgr::CatalogUpdate::UrlChannel416             UrlChannel(wxWindow* parent, ActiveCatalogGrid* catalog_grid)
417                 : wxPanel(parent), Helpers(this), m_catalog_grid(catalog_grid)
418             {
419                 auto sizer = new wxBoxSizer(wxHORIZONTAL);
420                 auto flags = wxSizerFlags()
421                     .Expand().Border().Align(wxALIGN_CENTER_VERTICAL);
422                 sizer->Add(staticText(_("Catalog channel: ")), flags);
423                 auto catalog = CatalogHandler::getInstance();
424                 wxArrayString channel_list;
425                 for (auto channel: catalog->GetChannels()) {
426                     channel_list.Add(channel.c_str());
427                 }
428                 auto channels = new wxChoice(this, wxID_ANY,
429                                              wxDefaultPosition,
430                                              wxDefaultSize,
431                                              channel_list);
432                 auto current =
433                     CatalogHandler::getInstance()->GetActiveChannel();
434                 int ix = channels->FindString(current.c_str());
435                 channels->SetSelection(ix != wxNOT_FOUND ? ix : 0);
436                 channels->Bind(
437                         wxEVT_CHOICE,
438                        [=](wxCommandEvent& e) { onChannelChange(e); });
439                 sizer->Add(channels, flags);
440                 SetSizer(sizer);
441                 Fit();
442                 Show();
443             }
444 
onChannelChangecatalog_mgr::CatalogUpdate::UrlChannel445             void onChannelChange(wxCommandEvent& ev)
446             {
447                 CatalogHandler::getInstance()
448                     ->SetActiveChannel(ev.GetString());
449                 m_catalog_grid->ReloadAvailableVersion();
450             };
451 
452             std::string m_active_channel;
453             ActiveCatalogGrid* m_catalog_grid;
454         };
455 
456 
457         /** Custom url edit control, a text line. */
458         struct UrlEdit: public wxPanel, public Helpers
459         {
UrlEditcatalog_mgr::CatalogUpdate::UrlEdit460             UrlEdit(wxWindow* parent):  wxPanel(parent), Helpers(this)
461             {
462                 auto sizer = new wxBoxSizer(wxVERTICAL);
463                 auto url_location = new wxBoxSizer(wxHORIZONTAL);
464                 auto flags = wxSizerFlags().Align(wxALIGN_CENTER_VERTICAL);
465                 url_location->Add(staticText(_("Custom catalog URL: ")),
466                                   flags);
467                 flags = wxSizerFlags().Expand().Border();
468                 sizer->Add(url_location, flags);
469 
470                 auto url_edit = new wxBoxSizer(wxHORIZONTAL);
471                 auto uri = CatalogHandler::getInstance()->GetCustomUrl();
472                 m_url_ctrl = new wxTextCtrl(this, wxID_ANY, uri);
473                 auto the_parent = dynamic_cast<CatalogUpdate*>(GetParent());
474                 m_url_ctrl->SetMinClientSize(the_parent->getWindowSize());
475                 url_edit->Add(m_url_ctrl, wxSizerFlags().Expand());
476 
477                 sizer->Add(url_edit, flags);
478                 SetSizer(sizer);
479                 Fit();
480                 Show();
481             }
482 
setTextcatalog_mgr::CatalogUpdate::UrlEdit483             void setText(std::string text)
484             {
485                 m_url_ctrl->Clear();
486                 *m_url_ctrl  << text;
487                 m_url_ctrl->Refresh();
488             }
489 
clearcatalog_mgr::CatalogUpdate::UrlEdit490             void clear() { m_url_ctrl->Clear(); }
491 
getTextcatalog_mgr::CatalogUpdate::UrlEdit492             std::string getText()
493             {
494                 return std::string(m_url_ctrl->GetLineText(0).ToStdString());
495             }
496 
497             wxTextCtrl* m_url_ctrl;
498         };
499 };
500 
501 
502 /** Download status/progress window, always shown at start-up. */
503 class CatalogLoad: public wxPanel, public Helpers
504 {
505     public:
506 
507         class DialogGrid; // forward
508 
CatalogLoad(wxWindow * parent,bool use_latest=false)509         CatalogLoad(wxWindow* parent, bool use_latest = false)
510             :wxPanel(parent), Helpers(this), m_simple(use_latest)
511         {
512             auto sizer = new wxBoxSizer(wxVERTICAL);
513             auto flags = wxSizerFlags().Expand().Border() ;
514             m_grid = new DialogGrid(this);
515             sizer->Add(m_grid, flags);
516             sizer->Add(1, 1, 1, wxEXPAND);   // Expanding spacer
517             if (m_simple) {
518                 m_buttons = new Buttons(this);
519                 sizer->Add(m_buttons, flags.Bottom().Right());
520             }
521             auto size = GetTextExtent(_("Check latest release..."));
522             size.SetHeight(size.GetHeight() * 10);
523             size.SetWidth(size.GetWidth() * 5 / 2);
524             SetMinClientSize(size);
525             std::thread worker([=]() { Worker(); });
526             worker.detach();
527 
528             SetSizer(sizer);
529             Fit();
530             Show();
531 
532             Bind(CHANNELS_DL_DONE,
533                  [=](wxCommandEvent& ev) { m_grid->CellDone(ev, 1); });
534             Bind(CHANNELS_PARSE_DONE,
535                  [=](wxCommandEvent& ev) { m_grid->CellDone(ev, 3); });
536             Bind(CATALOG_DL_DONE,
537                  [=](wxCommandEvent& ev) { m_grid->CellDone(ev, 5); });
538             Bind(CATALOG_PARSE_DONE,
539                  [=](wxCommandEvent& ev) { workerDone(ev); });
540         }
541 
PostEvent(int evt_id,catalog_status status,std::string message)542         void PostEvent(int evt_id, catalog_status status, std::string message)
543         {
544             wxCommandEvent evt(evt_id);
545             evt.SetInt(static_cast<int>(status));
546             evt.SetString(message);
547             wxPostEvent(this, evt);
548         }
549 
550         /** Last part of initial Worker() call, in main thread. */
workerDone(wxCommandEvent & ev)551         void workerDone(wxCommandEvent& ev)
552         {
553             m_grid->CellDone(ev, 5);
554             if (m_simple) {
555                 std::string message =_("Catalog updated").ToStdString();
556                 std::string path =
557                     g_Platform->GetPrivateDataDir().ToStdString();
558                 path += SEP;
559                 path += "ocpn-plugins.xml";
560                 std::ofstream stream;
561                 stream.open(path, std::ios::out);
562                 if (stream.is_open()) {
563                     stream << m_xml.str();
564                     stream.close();
565                     ev.SetString("Catalog updated");
566                     ev.SetInt(static_cast<int>(catalog_status::OK_MSG));
567                 }
568                 else {
569                     ev.SetString("Update error (cannot writé to file)");
570                     ev.SetInt(static_cast<int>(catalog_status::OS_ERROR));
571                     wxLogMessage("Update error: Cannot write to %s",
572                                  path.c_str());
573                 }
574                 m_grid->CellDone(ev, 6);
575                 m_buttons->ActivateOk();
576             }
577             else {
578                 CatalogData catalog_data;
579                 auto handler = CatalogHandler::getInstance();
580                 catalog_data = handler->LatestCatalogData();
581                 Hide();
582                 Refresh(true);
583                 Update();
584                 new CatalogUpdate(this);
585             }
586         }
587 
588         /** Runs in separate, detached thread, started from ctor. */
Worker()589         void Worker()
590         {
591             auto catalog = CatalogHandler::getInstance();
592             std::ostringstream json;
593 
594             auto status = catalog->LoadChannels(&json);
595             PostEvent(CHANNELS_DL_DONE, status, catalog->LastErrorMsg());
596 
597             status = catalog->LoadChannels(json.str());
598             PostEvent(CHANNELS_PARSE_DONE, status, catalog->LastErrorMsg());
599 
600             if (status == catalog_status::OK) {
601                 auto channel = catalog->GetActiveChannel();
602                 status = catalog_status::OK_MSG;
603                 PostEvent(CHANNELS_PARSE_DONE, status, channel);
604             }
605             m_xml.clear();
606             status = catalog->DownloadCatalog(&m_xml);
607             PostEvent(CATALOG_DL_DONE, status, catalog->LastErrorMsg());
608 
609             status = catalog->ParseCatalog(m_xml.str(), true);
610             if (status == catalog_status::OK) {
611                 PostEvent(CATALOG_PARSE_DONE,
612                           catalog_status::OK_MSG,
613                           catalog->LatestCatalogData().version);
614             }
615             else {
616                 PostEvent(CATALOG_PARSE_DONE,
617                           status,
618                           catalog->LastErrorMsg());
619             }
620         }
621 
622         /** Grid with  Server is Reachable..., Check channel... etc. */
623         struct DialogGrid: public wxPanel, public Helpers
624         {
DialogGridcatalog_mgr::CatalogLoad::DialogGrid625             DialogGrid(wxWindow* parent): wxPanel(parent), Helpers(this)
626             {
627                 auto grid = new wxFlexGridSizer(2, 0, 0);
628                 auto flags = wxSizerFlags()
629                     .Expand().Border().Align(wxALIGN_CENTER_VERTICAL);
630                 grid->Add(staticText(_("Server is reachable...")), flags);
631                 grid->Add(staticText(_("")), flags);
632                 grid->Add(staticText(_("Check channel...")), flags);
633                 grid->Add(staticText(_("")), flags);
634                 grid->Add(staticText(_("Check latest release...")), flags);
635                 grid->Add(staticText(_("")), flags);
636                 grid->Add(staticText(_("")), flags);
637                 grid->Add(staticText(_("")), flags);
638                 auto url = CatalogHandler::getInstance()->GetCustomUrl();
639                 if (url != "") {
640                     grid->Add(staticText(_("Custom URL")), flags);
641                     grid->Add(staticText(url.c_str()), flags);
642                 }
643                 SetSizer(grid);
644                 Fit();
645                 Show();
646             }
647 
648             /* Update status values in grid. */
CellDonecatalog_mgr::CatalogLoad::DialogGrid649             void CellDone(const wxCommandEvent& event, size_t index)
650             {
651                 //wxLogMessage("CellDone: event %d", event.GetInt());
652                 auto cell = GetSizer()->GetItem(index)->GetWindow();
653                 auto code = static_cast<catalog_status>(event.GetInt());
654                 if (code == catalog_status::OK) {
655                     cell->SetLabel(_("OK"));
656                 }
657                 else if (code == catalog_status::OK_MSG) {
658                     cell->SetLabel(event.GetString().ToStdString());
659                 }
660                 else {
661                     auto msg = std::string(_("Fail: "))
662                         + event.GetString().ToStdString();
663                     cell->SetLabel(_(msg.c_str()));
664                 }
665                 Fit();
666             }
667         };
668 
669         /** OK/Cancel buttons for the download progress window. */
670         struct Buttons: public wxPanel
671         {
Buttonscatalog_mgr::CatalogLoad::Buttons672             Buttons(wxWindow* parent) : wxPanel(parent)
673             {
674                 using CmdEvt = wxCommandEvent;
675 
676                 auto sizer = new wxBoxSizer(wxHORIZONTAL);
677                 auto flags = wxSizerFlags().Right().Bottom().Border();
678                 sizer->Add(1, 1, 100, wxEXPAND);   // Expanding spacer
679                 auto cancel = new wxButton(this, wxID_CANCEL, _("Cancel"));
680                 sizer->Add(cancel, flags);
681                 m_ok = new wxButton(this, wxID_OK, _("OK"));
682                 m_ok->Enable(false);
683                 sizer->Add(m_ok, flags);
684                 SetSizer(sizer);
685                 Fit();
686                 SetFocus();
687                 Show();
688             }
689 
ActivateOkcatalog_mgr::CatalogLoad::Buttons690             void ActivateOk()
691             {
692                 m_ok->Enable(true);
693             }
694 
695             wxButton* m_ok;
696         };
697 
698         std::ostringstream m_xml;
699         DialogGrid* m_grid;
700         Buttons* m_buttons;
701         const bool m_simple;   // Simple means just install, no advanced dialog
702 };
703 
704 
705 }     // namespace catalog_mgr
706 
707 
708 /** Top-level plugin catalog dialog. */
AdvancedCatalogDialog(wxWindow * parent)709 AdvancedCatalogDialog::AdvancedCatalogDialog(wxWindow* parent)
710     :wxFrame(parent, wxID_ANY, _("Catalog Manager"),
711               wxDefaultPosition , wxDefaultSize,
712               wxDEFAULT_FRAME_STYLE | wxRESIZE_BORDER)
713 {
714     auto vbox = new wxBoxSizer(wxHORIZONTAL);
715     vbox->Add(new catalog_mgr::CatalogLoad(this, false),
716               wxSizerFlags(1).Expand());
717     Bind(wxEVT_CLOSE_WINDOW, [this](wxCloseEvent&) {
718             wxCommandEvent evt(EVT_PLUGINS_RELOAD);
719             wxPostEvent(GetParent(), evt);
720             Destroy();
721     });
722     Bind(CATALOG_DLG_CLOSE, [this](wxCommandEvent&) {
723             wxCommandEvent evt(EVT_PLUGINS_RELOAD);
724             wxPostEvent(GetParent(), evt);
725             Destroy();
726     });
727     SetSizer(vbox);
728 
729     Fit();
730     Center();
731     Raise();
732     SetFocus();
733     Show();
734 }
735 
SimpleCatalogDialog(wxWindow * parent)736 SimpleCatalogDialog::SimpleCatalogDialog(wxWindow* parent)
737     :wxDialog(parent, wxID_ANY, _("Catalog Manager"),
738               wxDefaultPosition , wxDefaultSize,
739               wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER)
740 {
741     auto vbox = new wxBoxSizer(wxHORIZONTAL);
742     vbox->Add(new catalog_mgr::CatalogLoad(this, true),
743               wxSizerFlags(1).Expand());
744     Bind(wxEVT_CLOSE_WINDOW, [&](wxCloseEvent& e) { EndModal(wxID_OK); });
745     Bind(CATALOG_DLG_CLOSE, [&](wxCommandEvent& ev) { EndModal(wxID_OK); });
746     SetSizer(vbox);
747 
748     Fit();
749     ShowModal();
750     Destroy();
751 }
752