OpenCPN Partial API docs
Loading...
Searching...
No Matches
chartdldr_pi.cpp
1/******************************************************************************
2 * $Id: chartdldr_pi.cpp,v 1.0 2011/02/26 01:54:37 nohal Exp $
3 *
4 * Project: OpenCPN
5 * Purpose: Chart downloader Plugin
6 * Author: Pavel Kalian
7 *
8 ***************************************************************************
9 * Copyright (C) 2011 by Pavel Kalian *
10 * $EMAIL$ *
11 * *
12 * This program is free software; you can redistribute it and/or modify *
13 * it under the terms of the GNU General Public License as published by *
14 * the Free Software Foundation; either version 2 of the License, or *
15 * (at your option) any later version. *
16 * *
17 * This program is distributed in the hope that it will be useful, *
18 * but WITHOUT ANY WARRANTY; without even the implied warranty of *
19 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
20 * GNU General Public License for more details. *
21 * *
22 * You should have received a copy of the GNU General Public License *
23 * along with this program; if not, write to the *
24 * Free Software Foundation, Inc., *
25 * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *
26 ***************************************************************************
27 */
28
29#include "wx/wxprec.h"
30
31#ifndef WX_PRECOMP
32#include "wx/wx.h"
33#endif // precompiled headers
34
35#include "chartdldr_pi.h"
36#include "wxWTranslateCatalog.h"
37#include <wx/stdpaths.h>
38#include <wx/url.h>
39#include <wx/progdlg.h>
40#include <wx/sstream.h>
41#include <wx/wfstream.h>
42#include <wx/filename.h>
43#include <wx/listctrl.h>
44#include <wx/dir.h>
45#include <wx/filesys.h>
46#include <wx/zipstrm.h>
47#include <wx/wfstream.h>
48#include <memory>
49#include <wx/regex.h>
50#include <wx/debug.h>
51
52#ifdef DLDR_USE_LIBARCHIVE
53#include <archive.h>
54#include <archive_entry.h>
55#ifdef CHARTDLDR_RAR_UNARR
56#include "unarr.h"
57#endif
58#else
59#include "unarr.h"
60#endif
61
62#ifdef __OCPN__ANDROID__
63#define _LIBCPP_HAS_NO_OFF_T_FUNCTIONS
64#endif
65
66#include <fstream>
67
68#ifdef __ANDROID__
69#include "androidSupport.h"
70#include "android_jvm.h"
71#include <jni.h>
72#endif
73
74#ifdef __WXMAC__
75#define CATALOGS_NAME_WIDTH 300
76#define CATALOGS_DATE_WIDTH 120
77#define CATALOGS_PATH_WIDTH 100
78#define CHARTS_NAME_WIDTH 300
79#define CHARTS_STATUS_WIDTH 100
80#define CHARTS_DATE_WIDTH 120
81#else
82#ifdef __OCPN__ANDROID__
83
84#define CATALOGS_NAME_WIDTH 350
85#define CATALOGS_DATE_WIDTH 500
86#define CATALOGS_PATH_WIDTH 1000
87#define CHARTS_NAME_WIDTH 520
88#define CHARTS_STATUS_WIDTH 150
89#define CHARTS_DATE_WIDTH 200
90
91#else
92
93#define CATALOGS_NAME_WIDTH 200
94#define CATALOGS_DATE_WIDTH 130
95#define CATALOGS_PATH_WIDTH 250
96#define CHARTS_NAME_WIDTH 320
97#define CHARTS_STATUS_WIDTH 150
98#define CHARTS_DATE_WIDTH 130
99
100#endif
101#endif // __WXMAC__
102
103#ifdef __OCPN__ANDROID__
104#include <QtAndroidExtras/QAndroidJniObject>
105#include "qdebug.h"
106
107#endif
108
109bool getDisplayMetrics();
110
111#define CHART_DIR "Charts"
112
113void write_file(const wxString extract_file, char *data,
114 unsigned long datasize) {
115 wxFileName fn(extract_file);
116 if (wxDirExists(fn.GetPath())) {
117 if (!wxFileName::Mkdir(fn.GetPath(), 0755, wxPATH_MKDIR_FULL)) {
118 wxLogError(_T("Can not create directory '") + fn.GetPath() + _T("'."));
119 return;
120 }
121 }
122 wxFileOutputStream f(extract_file);
123 f.Write(data, datasize);
124 f.Close();
125}
126
127int g_Android_SDK_Version;
128
129bool IsDLDirWritable(wxFileName fn) {
130#ifndef __OCPN__ANDROID__
131 return fn.IsDirWritable();
132#else
133 if (g_Android_SDK_Version >= 30) { // scoped storage?
134 // Use a simple test here
135 return (fn.GetFullPath().Contains("org.opencpn.opencpn")); // fast test
136 } else
137 return fn.IsDirWritable();
138
139#endif
140}
141
142// the class factories, used to create and destroy instances of the PlugIn
143
144extern "C" DECL_EXP opencpn_plugin *create_pi(void *ppimgr) {
145 return new chartdldr_pi(ppimgr);
146}
147
148extern "C" DECL_EXP void destroy_pi(opencpn_plugin *p) { delete p; }
149
150double g_androidDPmm;
151chartdldr_pi *g_pi;
152
153//---------------------------------------------------------------------------------------------------------
154//
155// ChartDldr PlugIn Implementation
156//
157//---------------------------------------------------------------------------------------------------------
158
159#include "icons.h"
160
161//---------------------------------------------------------------------------------------------------------
162//
163// PlugIn initialization and de-init
164//
165//---------------------------------------------------------------------------------------------------------
166
167chartdldr_pi::chartdldr_pi(void *ppimgr) : opencpn_plugin_113(ppimgr) {
168 // Create the PlugIn icons
169 initialize_images();
170
171 m_parent_window = NULL;
172 m_pChartSource = NULL;
173 m_pconfig = NULL;
174 m_preselect_new = false;
175 m_preselect_updated = false;
176 m_allow_bulk_update = false;
177 m_pOptionsPage = NULL;
178 m_selected_source = -1;
179 m_dldrpanel = NULL;
180 m_leftclick_tool_id = -1;
181 m_schartdldr_sources = wxEmptyString;
182
183 g_pi = this;
184}
185
186int chartdldr_pi::Init(void) {
187 AddLocaleCatalog(PLUGIN_CATALOG_NAME);
188
189 // Get a pointer to the opencpn display canvas, to use as a parent for the
190 // POI Manager dialog
191 m_parent_window = GetOCPNCanvasWindow();
192
193 // Get a pointer to the opencpn configuration object
194 m_pconfig = GetOCPNConfigObject();
195 m_pOptionsPage = NULL;
196
197 m_pChartSource = NULL;
198
199#ifdef __OCPN__ANDROID__
200 androidGetSDKVersion();
201#endif
202
203 // And load the configuration items
204 LoadConfig();
205
206 getDisplayMetrics();
207
208 wxStringTokenizer st(m_schartdldr_sources, _T("|"), wxTOKEN_DEFAULT);
209 while (st.HasMoreTokens()) {
210 wxString s1 = st.GetNextToken();
211 wxString s2 = st.GetNextToken();
212 wxString s3 = st.GetNextToken();
213 if (!s2.IsEmpty()) // scrub empty sources.
214 m_ChartSources.push_back(std::make_unique<ChartSource>(s1, s2, s3));
215 }
217}
218
219bool chartdldr_pi::DeInit(void) {
220 wxLogMessage(_T("chartdldr_pi: DeInit"));
221
222 m_ChartSources.clear();
223 // wxDELETE(m_pChartSource);
224 /* TODO: Seth */
225 // dialog->Close();
226 // dialog->Destroy();
227 // wxDELETE(dialog);
228 /* We must delete remaining page if the plugin is disabled while in Options
229 * dialog */
230 if (m_pOptionsPage) {
231 if (DeleteOptionsPage(m_pOptionsPage)) m_pOptionsPage = NULL;
232 // TODO: any other memory leak?
233 }
234 return true;
235}
236
237int chartdldr_pi::GetAPIVersionMajor() { return MY_API_VERSION_MAJOR; }
238
239int chartdldr_pi::GetAPIVersionMinor() { return MY_API_VERSION_MINOR; }
240
241int chartdldr_pi::GetPlugInVersionMajor() { return PLUGIN_VERSION_MAJOR; }
242
243int chartdldr_pi::GetPlugInVersionMinor() { return PLUGIN_VERSION_MINOR; }
244
245wxBitmap *chartdldr_pi::GetPlugInBitmap() { return _img_chartdldr_pi; }
246
247wxString chartdldr_pi::GetCommonName() { return _("ChartDownloader"); }
248
249wxString chartdldr_pi::GetShortDescription() {
250 return _("Chart Downloader PlugIn for OpenCPN");
251}
252
253wxString chartdldr_pi::GetLongDescription() {
254 return _(
255 "Chart Downloader PlugIn for OpenCPN\n\
256Manages chart downloads and updates from sources supporting\n\
257NOAA Chart Catalog format");
258}
259
260void chartdldr_pi::OnSetupOptions(void) {
261 m_pOptionsPage =
262 AddOptionsPage(PI_OPTIONS_PARENT_CHARTS, _("Chart Downloader"));
263 if (!m_pOptionsPage) {
264 wxLogMessage(
265 _T("Error: chartdldr_pi::OnSetupOptions AddOptionsPage failed!"));
266 return;
267 }
268 wxBoxSizer *sizer = new wxBoxSizer(wxVERTICAL);
269 m_pOptionsPage->SetSizer(sizer);
270
271 m_dldrpanel =
272 new ChartDldrPanelImpl(this, m_pOptionsPage, wxID_ANY, wxDefaultPosition,
273 wxDefaultSize, wxDEFAULT_DIALOG_STYLE);
274
275 m_pOptionsPage->InvalidateBestSize();
276 sizer->Add(m_dldrpanel, 1, wxALL | wxEXPAND);
277 m_dldrpanel->SetBulkUpdate(m_allow_bulk_update);
278 m_dldrpanel->FitInside();
279}
280
281void chartdldr_pi::OnCloseToolboxPanel(int page_sel, int ok_apply_cancel) {
282 /* TODO: Seth */
283 m_dldrpanel->CancelDownload();
284#ifndef __OCPN__ANDROID__
285 OCPN_cancelDownloadFileBackground(
286 0); // Stop the thread, is something like this needed on Android as well?
287#endif
288 m_selected_source = m_dldrpanel->GetSelectedCatalog();
289 SaveConfig();
290}
291
292bool chartdldr_pi::LoadConfig(void) {
293 wxFileConfig *pConf = (wxFileConfig *)m_pconfig;
294
295 if (pConf) {
296 pConf->SetPath(_T ( "/Settings/ChartDnldr" ));
297 pConf->Read(_T ( "ChartSources" ), &m_schartdldr_sources, wxEmptyString);
298 pConf->Read(_T ( "Source" ), &m_selected_source, -1);
299
300 wxFileName fn(GetWritableDocumentsDir(), wxEmptyString);
301 fn.AppendDir(_T(CHART_DIR));
302
303 pConf->Read(_T ( "BaseChartDir" ), &m_base_chart_dir, fn.GetPath());
304 wxLogMessage(_T ( "chartdldr_pi:m_base_chart_dir: " ) + m_base_chart_dir);
305
306 // Check to see if the directory is writeable, esp. on App updates.
307 wxFileName testFN(m_base_chart_dir);
308 if (!IsDLDirWritable(testFN)) {
309 wxLogMessage(
310 "Cannot write to m_base_chart_dir, override to "
311 "GetWritableDocumentsDir()");
312 m_base_chart_dir = fn.GetPath();
313 wxLogMessage(_T ( "chartdldr_pi: Corrected: " ) + m_base_chart_dir);
314 }
315
316 pConf->Read(_T ( "PreselectNew" ), &m_preselect_new, true);
317 pConf->Read(_T ( "PreselectUpdated" ), &m_preselect_updated, true);
318 pConf->Read(_T ( "AllowBulkUpdate" ), &m_allow_bulk_update, false);
319 return true;
320 } else
321 return false;
322}
323
324bool chartdldr_pi::SaveConfig(void) {
325 wxFileConfig *pConf = (wxFileConfig *)m_pconfig;
326
327 m_schartdldr_sources.Clear();
328
329 for (size_t i = 0; i < m_ChartSources.size(); i++) {
330 std::unique_ptr<ChartSource> &cs = m_ChartSources.at(i);
331 m_schartdldr_sources.Append(
332 wxString::Format(_T("%s|%s|%s|"), cs->GetName().c_str(),
333 cs->GetUrl().c_str(), cs->GetDir().c_str()));
334 }
335
336 if (pConf) {
337 pConf->SetPath(_T ( "/Settings/ChartDnldr" ));
338 pConf->Write(_T ( "ChartSources" ), m_schartdldr_sources);
339 pConf->Write(_T ( "Source" ), m_selected_source);
340 pConf->Write(_T ( "BaseChartDir" ), m_base_chart_dir);
341 pConf->Write(_T ( "PreselectNew" ), m_preselect_new);
342 pConf->Write(_T ( "PreselectUpdated" ), m_preselect_updated);
343 pConf->Write(_T ( "AllowBulkUpdate" ), m_allow_bulk_update);
344
345 return true;
346 } else
347 return false;
348}
349
350void SetBackColor(wxWindow *ctrl, wxColour col) {
351 static int depth = 0; // recursion count
352 if (depth == 0) { // only for the window root, not for every child
353
354 ctrl->SetBackgroundColour(col);
355 }
356
357 wxWindowList kids = ctrl->GetChildren();
358 for (unsigned int i = 0; i < kids.GetCount(); i++) {
359 wxWindowListNode *node = kids.Item(i);
360 wxWindow *win = node->GetData();
361
362 if (dynamic_cast<wxListBox *>(win))
363 dynamic_cast<wxListBox *>(win)->SetBackgroundColour(col);
364
365 else if (dynamic_cast<wxTextCtrl *>(win))
366 dynamic_cast<wxTextCtrl *>(win)->SetBackgroundColour(col);
367
368 // else if( win->IsKindOf( CLASSINFO(wxStaticText) ) )
369 // ( (wxStaticText*) win )->SetForegroundColour( uitext );
370
371 else if (dynamic_cast<wxChoice *>(win))
372 dynamic_cast<wxChoice *>(win)->SetBackgroundColour(col);
373
374 else if (dynamic_cast<wxComboBox *>(win))
375 dynamic_cast<wxComboBox *>(win)->SetBackgroundColour(col);
376
377 else if (dynamic_cast<wxRadioButton *>(win))
378 dynamic_cast<wxRadioButton *>(win)->SetBackgroundColour(col);
379
380 else if (dynamic_cast<wxScrolledWindow *>(win)) {
381 dynamic_cast<wxScrolledWindow *>(win)->SetBackgroundColour(col);
382 }
383
384 else if (dynamic_cast<wxButton *>(win)) {
385 dynamic_cast<wxButton *>(win)->SetBackgroundColour(col);
386 }
387
388 else {
389 ;
390 }
391
392 if (win->GetChildren().GetCount() > 0) {
393 depth++;
394 wxWindow *w = win;
395 SetBackColor(w, col);
396 depth--;
397 }
398 }
399}
400
401void chartdldr_pi::ShowPreferencesDialog(wxWindow *parent) {
402 ChartDldrPrefsDlgImpl *dialog = new ChartDldrPrefsDlgImpl(parent);
403
404 wxFont fo = GetOCPNGUIScaledFont_PlugIn(_("Dialog"));
405 dialog->SetFont(fo);
406
407#ifdef __OCPN__ANDROID__
408 if (m_parent_window) {
409 int xmax = m_parent_window->GetSize().GetWidth();
410 int ymax = m_parent_window->GetParent()
411 ->GetSize()
412 .GetHeight(); // This would be the Options dialog itself
413 dialog->SetSize(xmax, ymax);
414 dialog->Layout();
415
416 dialog->Move(0, 0);
417 }
418
419 wxColour cl = wxColour(214, 218, 222);
420 SetBackColor(dialog, cl);
421#endif
422
423 dialog->SetPath(m_base_chart_dir);
424 dialog->SetPreferences(m_preselect_new, m_preselect_updated,
425 m_allow_bulk_update);
426
427 dialog->ShowModal();
428 dialog->Destroy();
429}
430
431void chartdldr_pi::UpdatePrefs(ChartDldrPrefsDlgImpl *dialog) {
432 m_base_chart_dir = dialog->GetPath();
433 dialog->GetPreferences(m_preselect_new, m_preselect_updated,
434 m_allow_bulk_update);
435 SaveConfig();
436 if (m_dldrpanel) m_dldrpanel->SetBulkUpdate(m_allow_bulk_update);
437}
438
439bool getDisplayMetrics() {
440#ifdef __OCPN__ANDROID__
441
442 g_androidDPmm = 4.0; // nominal default
443
444 // Get a reference to the running native activity
445 QAndroidJniObject activity = QAndroidJniObject::callStaticObjectMethod(
446 "org/qtproject/qt5/android/QtNative", "activity",
447 "()Landroid/app/Activity;");
448
449 if (!activity.isValid()) {
450 return false;
451 }
452
453 // Call the desired method
454 QAndroidJniObject data =
455 activity.callObjectMethod("getDisplayMetrics", "()Ljava/lang/String;");
456
457 wxString return_string;
458 jstring s = data.object<jstring>();
459
460 // Need a Java environment to decode the resulting string
461 JNIEnv *jenv;
462 if (java_vm->GetEnv((void **)&jenv, JNI_VERSION_1_6) != JNI_OK) {
463 // qDebug() << "GetEnv failed.";
464 } else {
465 const char *ret_string = (jenv)->GetStringUTFChars(s, NULL);
466 return_string = wxString(ret_string, wxConvUTF8);
467 }
468
469 // Return string may have commas instead of periods, if using Euro locale
470 // We just fix it here...
471 return_string.Replace(_T(","), _T("."));
472
473 // wxLogMessage(_T("Metrics:") + return_string);
474 // wxSize screen_size = ::wxGetDisplaySize();
475 // wxString msg;
476 // msg.Printf(_T("wxGetDisplaySize(): %d %d"), screen_size.x,
477 // screen_size.y); wxLogMessage(msg);
478
479 double density = 1.0;
480 wxStringTokenizer tk(return_string, _T(";"));
481 if (tk.HasMoreTokens()) {
482 wxString token = tk.GetNextToken(); // xdpi
483 token = tk.GetNextToken(); // density
484
485 long b = ::wxGetDisplaySize().y;
486 token.ToDouble(&density);
487
488 token = tk.GetNextToken(); // ldpi
489
490 token = tk.GetNextToken(); // width
491 token = tk.GetNextToken(); // height - statusBarHeight
492 token = tk.GetNextToken(); // width
493 token = tk.GetNextToken(); // height
494 token = tk.GetNextToken(); // dm.widthPixels
495 token = tk.GetNextToken(); // dm.heightPixels
496
497 token = tk.GetNextToken(); // actionBarHeight
498 long abh;
499 token.ToLong(&abh);
500 // g_ActionBarHeight = wxMax(abh, 50);
501
502 // qDebug() << "g_ActionBarHeight" << abh << g_ActionBarHeight;
503 }
504
505 double ldpi = 160. * density;
506
507 // double maxDim = wxMax(::wxGetDisplaySize().x, ::wxGetDisplaySize().y);
508 // ret = (maxDim / ldpi) * 25.4;
509
510 // msg.Printf(_T("Android Auto Display Size (mm, est.): %g"), ret);
511 // wxLogMessage(msg);
512
513 // Save some items as global statics for convenience
514 g_androidDPmm = ldpi / 25.4;
515 // g_androidDensity = density;
516
517 // qDebug() << "PI Metrics" << g_androidDPmm << density;
518 return true;
519#else
520
521 return true;
522#endif
523}
524
525ChartSource::ChartSource(wxString name, wxString url, wxString localdir) {
526 m_name = name;
527 m_url = url;
528 m_dir = localdir;
529 m_update_data.clear();
530}
531
532ChartSource::~ChartSource() { m_update_data.clear(); }
533
534#define ID_MNU_SELALL 2001
535#define ID_MNU_DELALL 2002
536#define ID_MNU_INVSEL 2003
537#define ID_MNU_SELUPD 2004
538#define ID_MNU_SELNEW 2005
539
540enum { ThreadId = wxID_HIGHEST + 1 };
541
542BEGIN_EVENT_TABLE(ChartDldrPanelImpl, ChartDldrPanel)
543END_EVENT_TABLE()
544
545void ChartDldrPanelImpl::OnPopupClick(wxCommandEvent &evt) {
546 switch (evt.GetId()) {
547 case ID_MNU_SELALL:
548 CheckAllCharts(true);
549 break;
550 case ID_MNU_DELALL:
551 CheckAllCharts(false);
552 break;
553 case ID_MNU_INVSEL:
554 InvertCheckAllCharts();
555 break;
556 case ID_MNU_SELUPD:
557 CheckUpdatedCharts(true);
558 break;
559 case ID_MNU_SELNEW:
560 CheckNewCharts(true);
561 break;
562 }
563}
564
565void ChartDldrPanelImpl::OnContextMenu(wxMouseEvent &event) {
566 wxMenu menu;
567
568 wxPoint mouseScreen = wxGetMousePosition();
569 wxPoint mouseClient = ScreenToClient(mouseScreen);
570
571#ifdef __OCPN__ANDROID__
572 wxFont *pf = OCPNGetFont(_("Menu"), 0);
573
574 // add stuff
575 wxMenuItem *item1 = new wxMenuItem(&menu, ID_MNU_SELALL, _("Select all"));
576 item1->SetFont(*pf);
577 menu.Append(item1);
578
579 wxMenuItem *item2 = new wxMenuItem(&menu, ID_MNU_DELALL, _("Deselect all"));
580 item2->SetFont(*pf);
581 menu.Append(item2);
582
583 wxMenuItem *item3 =
584 new wxMenuItem(&menu, ID_MNU_INVSEL, _("Invert selection"));
585 item3->SetFont(*pf);
586 menu.Append(item3);
587
588 wxMenuItem *item4 = new wxMenuItem(&menu, ID_MNU_SELUPD, _("Select updated"));
589 item4->SetFont(*pf);
590 menu.Append(item4);
591
592 wxMenuItem *item5 = new wxMenuItem(&menu, ID_MNU_SELNEW, _("Select new"));
593 item5->SetFont(*pf);
594 menu.Append(item5);
595
596#else
597
598 menu.Append(ID_MNU_SELALL, _("Select all"), wxT(""));
599 menu.Append(ID_MNU_DELALL, _("Deselect all"), wxT(""));
600 menu.Append(ID_MNU_INVSEL, _("Invert selection"), wxT(""));
601 menu.Append(ID_MNU_SELUPD, _("Select updated"), wxT(""));
602 menu.Append(ID_MNU_SELNEW, _("Select new"), wxT(""));
603
604#endif
605
606 menu.Connect(wxEVT_COMMAND_MENU_SELECTED,
607 (wxObjectEventFunction)&ChartDldrPanelImpl::OnPopupClick, NULL,
608 this);
609 // and then display
610 PopupMenu(&menu, mouseClient.x, mouseClient.y);
611}
612
613void ChartDldrPanelImpl::OnShowLocalDir(wxCommandEvent &event) {
614 if (pPlugIn->m_pChartSource == NULL) return;
615#ifdef __WXGTK__
616 wxExecute(wxString::Format(_T("xdg-open %s"),
617 pPlugIn->m_pChartSource->GetDir().c_str()));
618#endif
619#ifdef __WXMAC__
620 wxExecute(wxString::Format(_T("open %s"),
621 pPlugIn->m_pChartSource->GetDir().c_str()));
622#endif
623#ifdef __WXMSW__
624 wxExecute(wxString::Format(_T("explorer %s"),
625 pPlugIn->m_pChartSource->GetDir().c_str()));
626#endif
627}
628
629void ChartDldrPanelImpl::SetSource(int id) {
630 pPlugIn->SetSourceId(id);
631
632 m_bDeleteSource->Enable(id >= 0);
633 m_bUpdateChartList->Enable(id >= 0);
634 m_bEditSource->Enable(id >= 0);
635
636 // TODO: DAN - Need to optimze to only update the chart list if needed.
637 // Right now it updates multiple times unnecessarily.
638 CleanForm();
639 if (id >= 0 && id < (int)pPlugIn->m_ChartSources.size()) {
640 ::wxBeginBusyCursor(); // wxSetCursor(wxCURSOR_WAIT);
641 // wxYield();
642 std::unique_ptr<ChartSource> &cs = pPlugIn->m_ChartSources.at(id);
643 cs->LoadUpdateData();
644 cs->UpdateLocalFiles();
645 pPlugIn->m_pChartSource = cs.get();
646 FillFromFile(cs->GetUrl(), cs->GetDir(), pPlugIn->m_preselect_new,
647 pPlugIn->m_preselect_updated);
648 wxURI url(cs->GetUrl());
649 m_chartsLabel->SetLabel(wxString::Format(
650 _("Charts: %s"), (cs->GetName() + _(" from ") + url.BuildURI() +
651 _T(" -> ") + cs->GetDir())
652 .c_str()));
653 if (::wxIsBusy()) ::wxEndBusyCursor();
654 } else {
655 pPlugIn->m_pChartSource = NULL;
656 m_chartsLabel->SetLabel(_("Charts"));
657 }
658}
659
660void ChartDldrPanelImpl::SelectSource(wxListEvent &event) {
661 int i = GetSelectedCatalog();
662 if (i >= 0) SetSource(i);
663 event.Skip();
664}
665
666void ChartDldrPanelImpl::SetBulkUpdate(bool bulk_update) {
667 m_bUpdateAllCharts->Enable(bulk_update);
668 m_bUpdateAllCharts->Show(bulk_update);
669 Layout();
670 m_parent->Layout();
671}
672
673void ChartDldrPanelImpl::CleanForm() {
674#if defined(CHART_LIST)
675 clearChartList();
676#else
677 m_scrollWinChartList->ClearBackground();
678#endif /* CHART_LIST */
679 // m_stCatalogInfo->Show( false );
680}
681
682void ChartDldrPanelImpl::FillFromFile(wxString url, wxString dir, bool selnew,
683 bool selupd) {
684 // load if exists
685 wxStringTokenizer tk(url, _T("/"));
686 wxString file;
687 do {
688 file = tk.GetNextToken();
689 } while (tk.HasMoreTokens());
690 wxFileName fn;
691 fn.SetFullName(file);
692 fn.SetPath(dir);
693 wxString path = fn.GetFullPath();
694 if (wxFileExists(path)) {
695 pPlugIn->m_pChartCatalog.LoadFromFile(path);
696 // m_tChartSourceInfo->SetValue(pPlugIn->m_pChartCatalog.GetDescription());
697 // fill in the rest of the form
698
699 m_updatedCharts = 0;
700 m_newCharts = 0;
701
702#if !defined(CHART_LIST)
703 // Clear any existing panels
704 m_panelArray.clear();
705 m_scrollWinChartList->ClearBackground();
706#endif /* CHART_LIST */
707
708 for (size_t i = 0; i < pPlugIn->m_pChartCatalog.charts.size(); i++) {
709 wxString status;
710 wxString latest;
711 bool bcheck = false;
712 wxString file =
713 pPlugIn->m_pChartCatalog.charts.at(i)->GetChartFilename(true);
714 if (!pPlugIn->m_pChartSource->ExistsLocaly(
715 pPlugIn->m_pChartCatalog.charts.at(i)->number, file)) {
716 m_newCharts++;
717 status = _("New");
718 if (selnew) bcheck = true;
719 } else {
720 if (pPlugIn->m_pChartSource->IsNewerThanLocal(
721 pPlugIn->m_pChartCatalog.charts.at(i)->number, file,
722 pPlugIn->m_pChartCatalog.charts.at(i)->GetUpdateDatetime())) {
723 m_updatedCharts++;
724 status = _("Out of date");
725 if (selupd) bcheck = true;
726 } else {
727 status = _("Up to date");
728 }
729 }
730 latest =
731 pPlugIn->m_pChartCatalog.charts.at(i)->GetUpdateDatetime().Format(
732 _T("%Y-%m-%d"));
733
734#if defined(CHART_LIST)
735 wxVector<wxVariant> data;
736 data.push_back(wxVariant(bcheck));
737 data.push_back(wxVariant(status));
738 data.push_back(wxVariant(latest));
739 data.push_back(
740 wxVariant(pPlugIn->m_pChartCatalog.charts.at(i)->GetChartTitle()));
741 getChartList()->AppendItem(data);
742#else
743 auto pC = std::make_unique<ChartPanel>(
744 m_scrollWinChartList, wxID_ANY, wxDefaultPosition, wxSize(-1, -1),
745 pPlugIn->m_pChartCatalog.charts.at(i)->GetChartTitle(), status,
746 latest, this, bcheck);
747 pC->Connect(wxEVT_RIGHT_DOWN,
748 wxMouseEventHandler(ChartDldrPanel::OnContextMenu), NULL,
749 this);
750
751 m_boxSizerCharts->Add(pC.get(), 0, wxEXPAND | wxLEFT | wxRIGHT, 2);
752 m_panelArray.push_back(std::move(pC));
753#endif /* CHART_LIST */
754 }
755
756#if !defined(CHART_LIST) // wxDataViewListCtrl handles all of this AFAIK: Dan
757 m_scrollWinChartList->ClearBackground();
758 m_scrollWinChartList->FitInside();
759 m_scrollWinChartList->GetSizer()->Layout();
760 Layout();
761 m_scrollWinChartList->ClearBackground();
762 SetChartInfo(wxString::Format(_("%lu charts total, %lu updated, %lu new"),
763 pPlugIn->m_pChartCatalog.charts.size(),
764 m_updatedCharts, m_newCharts));
765#else
766 SetChartInfo(wxString::Format(
767 _("%lu charts total, %lu updated, %lu new, %lu selected"),
768 pPlugIn->m_pChartCatalog.charts.size(), m_updatedCharts, m_newCharts,
769 GetCheckedChartCount()));
770#endif /* CHART_LIST */
771 }
772}
773
774bool ChartSource::ExistsLocaly(wxString chart_number, wxString filename) {
775 wxASSERT(this);
776
777 wxStringTokenizer tk(filename, _T("."));
778 wxString file = tk.GetNextToken().MakeLower();
779
780 if (!m_update_data.empty()) {
781 return m_update_data.find(std::string(chart_number.Lower().mb_str())) !=
782 m_update_data.end() ||
783 m_update_data.find(std::string(file.mb_str())) !=
784 m_update_data.end();
785 }
786 for (size_t i = 0; i < m_localfiles.Count(); i++) {
787 if (m_localfiles.Item(i) == file) return true;
788 }
789 return false;
790}
791
792bool ChartSource::IsNewerThanLocal(wxString chart_number, wxString filename,
793 wxDateTime validDate) {
794 wxStringTokenizer tk(filename, _T("."));
795 wxString file = tk.GetNextToken().MakeLower();
796 if (!m_update_data.empty()) {
797 if (m_update_data[std::string(chart_number.Lower().mbc_str())] <
798 validDate.GetTicks() &&
799 m_update_data[std::string(file.mbc_str())] < validDate.GetTicks())
800 return true;
801 else
802 return false;
803 }
804 bool update_candidate = false;
805
806 for (size_t i = 0; i < m_localfiles.Count(); i++) {
807 if (m_localfiles.Item(i) == file) {
808 if (validDate.IsLaterThan(m_localdt.at(i))) {
809 update_candidate = true;
810 } else
811 return false;
812 }
813 }
814 return update_candidate;
815}
816
817int ChartDldrPanelImpl::GetSelectedCatalog() {
818 long item =
819 m_lbChartSources->GetNextItem(-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED);
820 return item;
821}
822
823void ChartDldrPanelImpl::SelectCatalog(int item) {
824 if (item >= 0) {
825 m_bDeleteSource->Enable();
826 m_bEditSource->Enable();
827 m_bUpdateChartList->Enable();
828 } else {
829 m_bDeleteSource->Disable();
830 m_bEditSource->Disable();
831 m_bUpdateChartList->Disable();
832 }
833 m_lbChartSources->SetItemState(item, wxLIST_STATE_SELECTED,
834 wxLIST_STATE_SELECTED);
835}
836
837void ChartDldrPanelImpl::AppendCatalog(std::unique_ptr<ChartSource> &cs) {
838 long id = m_lbChartSources->GetItemCount();
839 m_lbChartSources->InsertItem(id, cs->GetName());
840 m_lbChartSources->SetItem(id, 1, _("(Please update first)"));
841 m_lbChartSources->SetItem(id, 2, cs->GetDir());
842 wxURI url(cs->GetUrl());
843 if (url.IsReference()) {
844 OCPNMessageBox_PlugIn(
845 this, _("Error, the URL to the chart source data seems wrong."),
846 _("Error"));
847 return;
848 }
849 wxFileName fn(url.GetPath());
850 fn.SetPath(cs->GetDir());
851 wxString path = fn.GetFullPath();
852 if (wxFileExists(path)) {
853 if (pPlugIn->m_pChartCatalog.LoadFromFile(path, true)) {
854 m_lbChartSources->SetItem(id, 0, pPlugIn->m_pChartCatalog.title);
855 m_lbChartSources->SetItem(
856 id, 1,
857 pPlugIn->m_pChartCatalog.GetReleaseDate().Format(
858 _T("%Y-%m-%d %H:%M")));
859 m_lbChartSources->SetItem(id, 2, path);
860#ifdef __OCPN__ANDROID__
861 m_lbChartSources->GetHandle()->resizeColumnToContents(0);
862 m_lbChartSources->GetHandle()->resizeColumnToContents(1);
863 m_lbChartSources->GetHandle()->resizeColumnToContents(2);
864#endif
865 }
866 }
867}
868
869void ChartDldrPanelImpl::UpdateAllCharts(wxCommandEvent &event) {
870 int failed_to_update = 0;
871 int attempted_to_update = 0;
872 if ((pPlugIn->m_preselect_new) && (pPlugIn->m_preselect_updated)) {
873 wxMessageDialog mess(
874 this,
875 _("You have chosen to update all chart catalogs.\nThen download all "
876 "new and updated charts.\nThis may take a long time."),
877 _("Chart Downloader"), wxOK | wxCANCEL);
878 if (mess.ShowModal() == wxID_CANCEL) return;
879 } else if (pPlugIn->m_preselect_new) {
880 wxMessageDialog mess(
881 this,
882 _("You have chosen to update all chart catalogs.\nThen download only "
883 "new (but not updated) charts.\nThis may take a long time."),
884 _("Chart Downloader"), wxOK | wxCANCEL);
885 if (mess.ShowModal() == wxID_CANCEL) return;
886 } else if (pPlugIn->m_preselect_updated) {
887 wxMessageDialog mess(
888 this,
889 _("You have chosen to update all chart catalogs.\nThen download only "
890 "updated (but not new) charts.\nThis may take a long time."),
891 _("Chart Downloader"), wxOK | wxCANCEL);
892 if (mess.ShowModal() == wxID_CANCEL) return;
893 }
894 updatingAll = true;
895 cancelled = false;
896 // Flip to the list of charts so user can observe the download progress
897 int oldPage = m_DLoadNB->SetSelection(1);
898 for (long chartIndex = 0; chartIndex < m_lbChartSources->GetItemCount();
899 chartIndex++) {
900 m_lbChartSources->SetItemState(chartIndex, wxLIST_STATE_SELECTED,
901 wxLIST_STATE_SELECTED);
902 if (cancelled) break;
903 UpdateChartList(event);
905 attempted_to_update += m_downloading;
906 failed_to_update += m_failed_downloads;
907 }
908 wxLogMessage(wxString::Format(
909 _T("chartdldr_pi::UpdateAllCharts() downloaded %d out of %d charts."),
910 attempted_to_update - failed_to_update, attempted_to_update));
911 if (failed_to_update > 0)
912 OCPNMessageBox_PlugIn(
913 this,
914 wxString::Format(_("%d out of %d charts failed to download.\nCheck the "
915 "list, verify there is a working Internet "
916 "connection and repeat the operation if needed."),
917 failed_to_update, attempted_to_update),
918 _("Chart Downloader"), wxOK | wxICON_ERROR);
919 if (attempted_to_update > failed_to_update) ForceChartDBUpdate();
920 updatingAll = false;
921 cancelled = false;
922 // Flip back to the original page
923 m_DLoadNB->SetSelection(oldPage);
924}
925
926void ChartDldrPanelImpl::UpdateChartList(wxCommandEvent &event) {
927 // TODO: check if everything exists and we can write to the output dir etc.
928 if (!m_lbChartSources->GetSelectedItemCount()) return;
929 std::unique_ptr<ChartSource> &cs =
930 pPlugIn->m_ChartSources.at(GetSelectedCatalog());
931 wxURI url(cs->GetUrl());
932 if (url.IsReference()) {
933 OCPNMessageBox_PlugIn(
934 this, _("Error, the URL to the chart source data seems wrong."),
935 _("Error"));
936 return;
937 }
938
939 wxStringTokenizer tk(url.GetPath(), _T("/"));
940 wxString file;
941 do {
942 file = tk.GetNextToken();
943 } while (tk.HasMoreTokens());
944 wxFileName fn;
945 fn.SetFullName(file);
946 fn.SetPath(cs->GetDir());
947 if (!wxDirExists(cs->GetDir())) {
948 if (!wxFileName::Mkdir(cs->GetDir(), 0755, wxPATH_MKDIR_FULL)) {
949 OCPNMessageBox_PlugIn(
950 this,
951 wxString::Format(_("Directory %s can't be created."),
952 cs->GetDir().c_str()),
953 _("Chart Downloader"));
954 return;
955 }
956 }
957
958 bool bok = false;
959
960#ifdef __OCPN__ANDROID__
961 wxString file_URI = _T("file://") + fn.GetFullPath();
962
963 // wxFile testFile(tfn.GetFullPath().c_str(), wxFile::write);
964 // if(!testFile.IsOpened()){
965 // wxMessageBox(this, wxString::Format(_("File %s can't be written.
966 // \nChoose a writable folder for Chart Downloader file storage."),
967 // tfn.GetFullPath().c_str()), _("Chart Downloader")); return;
968 // }
969 // testFile.Close();
970 // ::wxRemoveFile(tfn.GetFullPath());
971
972 _OCPN_DLStatus ret = OCPN_downloadFile(
973 cs->GetUrl(), file_URI, _("Downloading file"),
974 _("Reading Headers: ") + url.BuildURI(), wxNullBitmap, this,
979 10);
980 bok = true;
981
982#else
983 wxFileName tfn = wxFileName::CreateTempFileName(fn.GetFullPath());
984 wxString file_URI = tfn.GetFullPath();
985
986 _OCPN_DLStatus ret = OCPN_downloadFile(
987 cs->GetUrl(), file_URI, _("Downloading file"),
988 _("Reading Headers: ") + url.BuildURI(), wxNullBitmap, this,
993 10);
994
995 bok = wxCopyFile(tfn.GetFullPath(), fn.GetFullPath());
996 wxRemoveFile(tfn.GetFullPath());
997
998#endif
999
1000 // wxLogMessage(_T("chartdldr_pi: OCPN_downloadFile done:"));
1001
1002 switch (ret) {
1003 case OCPN_DL_NO_ERROR: {
1004 if (bok) {
1005 long id = GetSelectedCatalog();
1006 SetSource(id);
1007
1008 m_lbChartSources->SetItem(id, 0, pPlugIn->m_pChartCatalog.title);
1009 m_lbChartSources->SetItem(
1010 id, 1,
1011 pPlugIn->m_pChartCatalog.GetReleaseDate().Format(
1012 _T("%Y-%m-%d %H:%M")));
1013 m_lbChartSources->SetItem(id, 2, cs->GetDir());
1014
1015 } else
1016 OCPNMessageBox_PlugIn(
1017 this,
1018 wxString::Format(_("Failed to Find New Catalog: %s "),
1019 url.BuildURI().c_str()),
1020 _("Chart Downloader"), wxOK | wxICON_ERROR);
1021 break;
1022 }
1023 case OCPN_DL_FAILED: {
1024 OCPNMessageBox_PlugIn(
1025 this,
1026 wxString::Format(_("Failed to Download Catalog: %s \nVerify there is "
1027 "a working Internet connection."),
1028 url.BuildURI().c_str()),
1029 _("Chart Downloader"), wxOK | wxICON_ERROR);
1030 break;
1031 }
1032
1033 case OCPN_DL_USER_TIMEOUT:
1034 case OCPN_DL_ABORTED: {
1035 cancelled = true;
1036 break;
1037 }
1038
1039 case OCPN_DL_UNKNOWN:
1040 case OCPN_DL_STARTED: {
1041 break;
1042 }
1043
1044 default:
1045 wxASSERT(false); // This should never happen because we handle all
1046 // possible cases of ret
1047 }
1048
1049 if ((ret == OCPN_DL_NO_ERROR) && bok) m_DLoadNB->SetSelection(1);
1050}
1051
1052void ChartSource::GetLocalFiles() {
1053 if (!UpdateDataExists() || m_update_data.empty()) {
1054 wxArrayString *allFiles = new wxArrayString;
1055 if (wxDirExists(GetDir())) wxDir::GetAllFiles(GetDir(), allFiles);
1056 m_localdt.clear();
1057 m_localfiles.Clear();
1058 wxDateTime ct, mt, at;
1059 wxString name;
1060 for (size_t i = 0; i < allFiles->Count(); i++) {
1061 wxFileName fn(allFiles->Item(i));
1062 name = fn.GetFullName().Lower();
1063 // Only add unique files names to the local list.
1064 // This is safe because all chart names within a catalog
1065 // are necessarily unique.
1066 if (!ExistsLocaly(wxEmptyString, name)) {
1067 fn.GetTimes(&at, &mt, &ct);
1068 m_localdt.push_back(mt);
1069 m_localfiles.Add(fn.GetName().Lower());
1070
1071 wxStringTokenizer tk(name, _T("."));
1072 wxString file = tk.GetNextToken().MakeLower();
1073 m_update_data[std::string(file.mbc_str())] = mt.GetTicks();
1074 }
1075 }
1076 allFiles->Clear();
1077 wxDELETE(allFiles);
1078 SaveUpdateData();
1079 } else {
1080 LoadUpdateData();
1081 }
1082}
1083
1084bool ChartSource::UpdateDataExists() {
1085 return wxFileExists(GetDir() + wxFileName::GetPathSeparator() +
1086 _T(UPDATE_DATA_FILENAME));
1087}
1088
1089void ChartSource::LoadUpdateData() {
1090 m_update_data.clear();
1091 wxString fn =
1092 GetDir() + wxFileName::GetPathSeparator() + _T(UPDATE_DATA_FILENAME);
1093
1094 if (!wxFileExists(fn)) return;
1095
1096 std::ifstream infile(fn.mb_str());
1097
1098 std::string key;
1099 long value;
1100
1101 while (infile >> key >> value) m_update_data[key] = value;
1102
1103 infile.close();
1104}
1105
1106void ChartSource::SaveUpdateData() {
1107 wxString fn;
1108 fn = GetDir() + wxFileName::GetPathSeparator() + _T(UPDATE_DATA_FILENAME);
1109
1110#ifdef __OCPN__ANDROID__
1111 fn = AndroidGetCacheDir() + wxFileName::GetPathSeparator() +
1112 _T(UPDATE_DATA_FILENAME);
1113#endif
1114
1115 std::ofstream outfile(fn.mb_str());
1116 if (!outfile.is_open()) return;
1117
1118 std::map<std::string, time_t>::iterator iter;
1119 for (iter = m_update_data.begin(); iter != m_update_data.end(); ++iter) {
1120 if (iter->first.find(" ") == std::string::npos)
1121 if (!iter->first.empty())
1122 outfile << iter->first << " " << iter->second << "\n";
1123 }
1124
1125 outfile.close();
1126
1127#ifdef __OCPN__ANDROID__
1128 AndroidSecureCopyFile(
1129 fn, GetDir() + wxFileName::GetPathSeparator() + _T(UPDATE_DATA_FILENAME));
1130#endif
1131}
1132
1133void ChartSource::ChartUpdated(wxString chart_number, time_t timestamp) {
1134 m_update_data[std::string(chart_number.Lower().mb_str())] = timestamp;
1135 SaveUpdateData();
1136}
1137
1138bool ChartDldrPanelImpl::DownloadChart(wxString url, wxString file,
1139 wxString title) {
1140 return false;
1141}
1142
1143void ChartDldrPanelImpl::DisableForDownload(bool enabled) {
1144 m_bAddSource->Enable(enabled);
1145 m_bDeleteSource->Enable(enabled);
1146 m_bEditSource->Enable(enabled);
1147 m_bUpdateAllCharts->Enable(enabled);
1148 m_bUpdateChartList->Enable(enabled);
1149 m_lbChartSources->Enable(enabled);
1150#if defined(CHART_LIST)
1151 m_bSelectNew->Enable(enabled);
1152 m_bSelectUpdated->Enable(enabled);
1153 m_bSelectAll->Enable(enabled);
1154#endif /* CHART_LIST */
1155}
1156
1157void ChartDldrPanelImpl::OnDownloadCharts(wxCommandEvent &event) {
1158 if (DownloadIsCancel) {
1159 cancelled = true;
1160 return;
1161 }
1163}
1164#if defined(CHART_LIST)
1165void ChartDldrPanelImpl::OnSelectChartItem(wxCommandEvent &event) {
1166 if (!m_bInfoHold)
1167 SetChartInfo(wxString::Format(
1168 _("%lu charts total, %lu updated, %lu new, %lu selected"),
1169 pPlugIn->m_pChartCatalog.charts.size(), m_updatedCharts, m_newCharts,
1170 GetCheckedChartCount()));
1171 else
1172 event.Skip();
1173}
1174#endif /* CHART_LIST */
1175#if defined(CHART_LIST)
1176void ChartDldrPanelImpl::OnSelectNewCharts(wxCommandEvent &event) {
1177 CheckNewCharts(true);
1178}
1179#endif /* CHART_LIST */
1180
1181#if defined(CHART_LIST)
1182void ChartDldrPanelImpl::OnSelectUpdatedCharts(wxCommandEvent &event) {
1183 CheckUpdatedCharts(true);
1184}
1185#endif /* CHART_LIST */
1186
1187#if defined(CHART_LIST)
1188void ChartDldrPanelImpl::OnSelectAllCharts(wxCommandEvent &event) {
1189 if (m_bSelectAll->GetLabel() == _("Select All")) {
1190 CheckAllCharts(true);
1191 m_bSelectAll->SetLabel(_("Select None"));
1192 m_bSelectAll->SetToolTip(_("De-select all charts in the list."));
1193 } else {
1194 CheckAllCharts(false);
1195 m_bSelectAll->SetLabel(_("Select All"));
1196 m_bSelectAll->SetToolTip(_("Select all charts in the list."));
1197 }
1198}
1199#endif /* CHART_LIST */
1200
1201int ChartDldrPanelImpl::GetChartCount() {
1202#if defined(CHART_LIST)
1203 return getChartList()->GetItemCount();
1204#else
1205 return m_panelArray.size();
1206#endif /* CHART_LIST*/
1207}
1208
1209int ChartDldrPanelImpl::GetCheckedChartCount() {
1210#if defined(CHART_LIST)
1211 int cnt = 0;
1212 int chartCnt = GetChartCount();
1213 for (int i = 0; i < chartCnt; i++)
1214 if (isChartChecked(i)) cnt++;
1215#else
1216 int cnt = 0;
1217 for (int i = 0; i < GetChartCount(); i++) {
1218 if (m_panelArray.at(i)->GetCB()->IsChecked()) cnt++;
1219 }
1220#endif /* CHART_LIST*/
1221 return cnt;
1222}
1223
1224bool ChartDldrPanelImpl::isChartChecked(int i) {
1225 wxASSERT_MSG(i >= 0,
1226 wxT("This function should be called with non-negative index."));
1227 if (i <= GetChartCount())
1228#if defined(CHART_LIST)
1229 return getChartList()->GetToggleValue(i, 0);
1230#else
1231 return m_panelArray.at(i)->GetCB()->IsChecked();
1232#endif /* CHART_LIST*/
1233 else
1234 return false;
1235}
1236
1237void ChartDldrPanelImpl::CheckAllCharts(bool value) {
1238#if defined(CHART_LIST)
1239 m_bInfoHold = true;
1240#endif /* CHART_LIST */
1241
1242 for (int i = 0; i < GetChartCount(); i++) {
1243#if defined(CHART_LIST)
1244 getChartList()->SetToggleValue(value, i, 0);
1245#else
1246 m_panelArray.at(i)->GetCB()->SetValue(value);
1247#endif /* CHART_LIST*/
1248 }
1249#if defined(CHART_LIST)
1250 SetChartInfo(wxString::Format(
1251 _("%lu charts total, %lu updated, %lu new, %lu selected"),
1252 pPlugIn->m_pChartCatalog.charts.size(), m_updatedCharts, m_newCharts,
1253 GetCheckedChartCount()));
1254 m_bInfoHold = false;
1255#endif /* CHART_LIST */
1256}
1257
1258void ChartDldrPanelImpl::CheckNewCharts(bool value) {
1259 for (int i = 0; i < GetChartCount(); i++) {
1260#if defined(CHART_LIST)
1261 if (isNew(i)) getChartList()->SetToggleValue(true, i, 0);
1262#else
1263 if (m_panelArray.at(i)->isNew())
1264 m_panelArray.at(i)->GetCB()->SetValue(value);
1265#endif /* CHART_LIST*/
1266 }
1267#if defined(CHART_LIST)
1268 SetChartInfo(wxString::Format(
1269 _("%lu charts total, %lu updated, %lu new, %lu selected"),
1270 pPlugIn->m_pChartCatalog.charts.size(), m_updatedCharts, m_newCharts,
1271 GetCheckedChartCount()));
1272#endif /* CHART_LIST */
1273}
1274
1275void ChartDldrPanelImpl::CheckUpdatedCharts(bool value) {
1276 for (int i = 0; i < GetChartCount(); i++) {
1277#if defined(CHART_LIST)
1278 if (isUpdated(i)) getChartList()->SetToggleValue(value, i, 0);
1279#else
1280 if (m_panelArray.at(i)->isUpdated())
1281 m_panelArray.at(i)->GetCB()->SetValue(value);
1282#endif /* CHART_LIST */
1283 }
1284#if defined(CHART_LIST)
1285 SetChartInfo(wxString::Format(
1286 _("%lu charts total, %lu updated, %lu new, %lu selected"),
1287 pPlugIn->m_pChartCatalog.charts.size(), m_updatedCharts, m_newCharts,
1288 GetCheckedChartCount()));
1289#endif /* CHART_LIST */
1290}
1291
1292void ChartDldrPanelImpl::InvertCheckAllCharts() {
1293#if defined(CHART_LIST)
1294 m_bInfoHold = true;
1295#endif /* CHART_LIST */
1296 for (int i = 0; i < GetChartCount(); i++)
1297#if defined(CHART_LIST)
1298 getChartList()->SetToggleValue(!isChartChecked(i), i, 0);
1299#else
1300 m_panelArray.at(i)->GetCB()->SetValue(!isChartChecked(i));
1301#endif /* CHART_LIST */
1302#if defined(CHART_LIST)
1303 m_bInfoHold = false;
1304 SetChartInfo(wxString::Format(
1305 _("%lu charts total, %lu updated, %lu new, %lu selected"),
1306 pPlugIn->m_pChartCatalog.charts.size(), m_updatedCharts, m_newCharts,
1307 GetCheckedChartCount()));
1308#endif /* CHART_LIST */
1309}
1310
1312 if (!m_bconnected) {
1313 Connect(
1314 wxEVT_DOWNLOAD_EVENT,
1315 (wxObjectEventFunction)(wxEventFunction)&ChartDldrPanelImpl::onDLEvent);
1316 m_bconnected = true;
1317 }
1318
1319 if (!GetCheckedChartCount() && !updatingAll) {
1320 OCPNMessageBox_PlugIn(this, _("No charts selected for download."));
1321 return;
1322 }
1323 std::unique_ptr<ChartSource> &cs =
1324 pPlugIn->m_ChartSources.at(GetSelectedCatalog());
1325
1326 cancelled = false;
1327 to_download = GetCheckedChartCount();
1328 m_downloading = 0;
1329 m_failed_downloads = 0;
1330 DisableForDownload(false);
1331 // wxString old_label = m_bDnldCharts->GetLabel(); // Broken on Android??
1332 m_bDnldCharts->SetLabel(_("Abort download"));
1333 DownloadIsCancel = true;
1334
1335 wxFileName downloaded_p;
1336 int idx = -1;
1337
1338 for (int i = 0; i < GetChartCount() && to_download; i++) {
1339 int index = i;
1340 if (cancelled) break;
1341 // Prepare download queues
1342 if (!isChartChecked(i)) continue;
1343 m_bTransferComplete = false;
1344 m_bTransferSuccess = true;
1345 m_totalsize = _("Unknown");
1346 m_transferredsize = _T("0");
1347 m_downloading++;
1348 if (pPlugIn->m_pChartCatalog.charts.at(index)->NeedsManualDownload()) {
1349 if (wxID_YES ==
1350 OCPNMessageBox_PlugIn(
1351 this,
1352 wxString::Format(
1353 _("The selected chart '%s' can't be downloaded automatically, do you want me to open a browser window and download them manually?\n\n \
1354After downloading the charts, please extract them to %s"),
1355 pPlugIn->m_pChartCatalog.charts.at(index)->title.c_str(),
1356 pPlugIn->m_pChartSource->GetDir().c_str()),
1357 _("Chart Downloader"), wxYES_NO | wxCENTRE | wxICON_QUESTION)) {
1358 wxLaunchDefaultBrowser(
1359 pPlugIn->m_pChartCatalog.charts.at(index)->GetManualDownloadUrl());
1360 }
1361 continue;
1362 }
1363
1364 // download queue
1365 wxURI url(pPlugIn->m_pChartCatalog.charts.at(index)->GetDownloadLocation());
1366 if (url.IsReference()) {
1367 OCPNMessageBox_PlugIn(
1368 this,
1369 wxString::Format(
1370 _("Error, the URL to the chart (%s) data seems wrong."),
1371 url.BuildURI().c_str()),
1372 _("Error"));
1373 this->Enable();
1375 return;
1376 }
1377 // construct local file path
1378 wxString file =
1379 pPlugIn->m_pChartCatalog.charts.at(index)->GetChartFilename();
1380 wxFileName fn;
1381 fn.SetFullName(file);
1382 fn.SetPath(cs->GetDir());
1383 wxString path = fn.GetFullPath();
1384 if (wxFileExists(path)) wxRemoveFile(path);
1385 wxString title = pPlugIn->m_pChartCatalog.charts.at(index)->GetChartTitle();
1386
1387 // Ready to start download
1388#ifdef __OCPN__ANDROID__
1389 wxString file_path = _T("file://") + fn.GetFullPath();
1390#else
1391 wxString file_path = fn.GetFullPath();
1392#endif
1393
1394 long handle;
1395 OCPN_downloadFileBackground(url.BuildURI(), file_path, this, &handle);
1396
1397 if (idx >= 0) {
1398 if (pPlugIn->ProcessFile(
1399 downloaded_p.GetFullPath(), downloaded_p.GetPath(), true,
1400 pPlugIn->m_pChartCatalog.charts.at(idx)->GetUpdateDatetime())) {
1401 cs->ChartUpdated(pPlugIn->m_pChartCatalog.charts.at(idx)->number,
1402 pPlugIn->m_pChartCatalog.charts.at(idx)
1403 ->GetUpdateDatetime()
1404 .GetTicks());
1405 } else {
1406 m_failed_downloads++;
1407 }
1408 idx = -1;
1409 }
1410
1411 while (!m_bTransferComplete && m_bTransferSuccess && !cancelled) {
1412 if (m_failed_downloads)
1413 SetChartInfo(wxString::Format(
1414 _("Downloading chart %u of %u, %u downloads failed (%s / %s)"),
1415 m_downloading, to_download, m_failed_downloads,
1416 m_transferredsize.c_str(), m_totalsize.c_str()));
1417 else
1418 SetChartInfo(wxString::Format(
1419 _("Downloading chart %u of %u (%s / %s)"), m_downloading,
1420 to_download, m_transferredsize.c_str(), m_totalsize.c_str()));
1421
1422 Update();
1423 Refresh();
1424
1425 wxTheApp->ProcessPendingEvents();
1426 wxYield();
1427 wxMilliSleep(20);
1428 }
1429
1430 if (cancelled) {
1431 idx = -1;
1432 OCPN_cancelDownloadFileBackground(handle);
1433 }
1434
1435 if (m_bTransferSuccess && !cancelled) {
1436 idx = index;
1437 downloaded_p = path;
1438 } else {
1439 idx = -1;
1440 if (wxFileExists(path)) wxRemoveFile(path);
1441 m_failed_downloads++;
1442 }
1443 }
1444 if (idx >= 0) {
1445 if (pPlugIn->ProcessFile(
1446 downloaded_p.GetFullPath(), downloaded_p.GetPath(), true,
1447 pPlugIn->m_pChartCatalog.charts.at(idx)->GetUpdateDatetime())) {
1448 cs->ChartUpdated(pPlugIn->m_pChartCatalog.charts.at(idx)->number,
1449 pPlugIn->m_pChartCatalog.charts.at(idx)
1450 ->GetUpdateDatetime()
1451 .GetTicks());
1452 } else {
1453 m_failed_downloads++;
1454 }
1455 }
1456 DisableForDownload(true);
1457 m_bDnldCharts->SetLabel(_("Download selected charts"));
1458 DownloadIsCancel = false;
1459 SetSource(GetSelectedCatalog());
1460 if (m_failed_downloads > 0 && !updatingAll && !cancelled)
1461 OCPNMessageBox_PlugIn(
1462 this,
1463 wxString::Format(_("%d out of %d charts failed to download.\nCheck the "
1464 "list, verify there is a working Internet "
1465 "connection and repeat the operation if needed."),
1466 m_failed_downloads, m_downloading),
1467 _("Chart Downloader"), wxOK | wxICON_ERROR);
1468
1469 if (cancelled)
1470 OCPNMessageBox_PlugIn(this, _("Chart download cancelled."),
1471 _("Chart Downloader"), wxOK | wxICON_INFORMATION);
1472
1473 if ((m_downloading - m_failed_downloads > 0) && !updatingAll)
1474 ForceChartDBUpdate();
1475}
1476
1477ChartDldrPanelImpl::~ChartDldrPanelImpl() {
1478 Disconnect(
1479 wxEVT_DOWNLOAD_EVENT,
1480 (wxObjectEventFunction)(wxEventFunction)&ChartDldrPanelImpl::onDLEvent);
1481 m_bconnected = false;
1482
1483#ifndef __OCPN__ANDROID__
1484 OCPN_cancelDownloadFileBackground(
1485 0); // Stop the thread, is something like this needed on Android as well?
1486#endif
1487#if defined(CHART_LIST)
1488 clearChartList();
1489#endif /* CHART_LIST */
1490}
1491
1492ChartDldrPanelImpl::ChartDldrPanelImpl(chartdldr_pi *plugin, wxWindow *parent,
1493 wxWindowID id, const wxPoint &pos,
1494 const wxSize &size, long style)
1495 : ChartDldrPanel(parent, id, pos, size, style) {
1496 m_bDeleteSource->Disable();
1497 m_bUpdateChartList->Disable();
1498 m_bEditSource->Disable();
1499 m_lbChartSources->InsertColumn(0, _("Catalog"), wxLIST_FORMAT_LEFT,
1500 CATALOGS_NAME_WIDTH);
1501 m_lbChartSources->InsertColumn(1, _("Released"), wxLIST_FORMAT_LEFT,
1502 CATALOGS_DATE_WIDTH);
1503 m_lbChartSources->InsertColumn(2, _("Local path"), wxLIST_FORMAT_LEFT,
1504 CATALOGS_PATH_WIDTH);
1505 m_lbChartSources->Enable();
1506 m_bInfoHold = false;
1507 downloadInProgress = false;
1508 cancelled = true;
1509 to_download = -1;
1510 m_downloading = -1;
1511 updatingAll = false;
1512 pPlugIn = plugin;
1513 m_populated = false;
1514 DownloadIsCancel = false;
1515 m_failed_downloads = 0;
1516 SetChartInfo(wxEmptyString);
1517 m_bTransferComplete = true;
1518 m_bTransferSuccess = true;
1519
1520 Connect(
1521 wxEVT_DOWNLOAD_EVENT,
1522 (wxObjectEventFunction)(wxEventFunction)&ChartDldrPanelImpl::onDLEvent);
1523 m_bconnected = true;
1524
1525 for (size_t i = 0; i < pPlugIn->m_ChartSources.size(); i++) {
1526 AppendCatalog(pPlugIn->m_ChartSources.at(i));
1527 }
1528 m_populated = true;
1529}
1530
1531void ChartDldrPanelImpl::OnPaint(wxPaintEvent &event) {
1532 if (!m_populated) {
1533 m_populated = true;
1534 for (size_t i = 0; i < pPlugIn->m_ChartSources.size(); i++) {
1535 AppendCatalog(pPlugIn->m_ChartSources.at(i));
1536 }
1537 }
1538#ifdef __WXMAC__
1539 // Mojave does not paint the controls correctly without this.
1540 m_lbChartSources->Refresh(true);
1541#endif
1542 event.Skip();
1543}
1544
1545void ChartDldrPanelImpl::DeleteSource(wxCommandEvent &event) {
1546 if (!m_lbChartSources->GetSelectedItemCount()) return;
1547 if (wxID_YES != OCPNMessageBox_PlugIn(
1548 this,
1549 _("Do you really want to remove the chart source?\nThe "
1550 "local chart files will not be removed,\nbut you will "
1551 "not be able to update the charts anymore."),
1552 _("Chart Downloader"), wxYES_NO | wxCENTRE))
1553 return;
1554 int ToBeRemoved = GetSelectedCatalog();
1555 m_lbChartSources->SetItemState(ToBeRemoved, 0,
1556 wxLIST_STATE_SELECTED | wxLIST_STATE_FOCUSED);
1557 pPlugIn->m_ChartSources.erase(pPlugIn->m_ChartSources.begin() + ToBeRemoved);
1558 m_lbChartSources->DeleteItem(ToBeRemoved);
1559 CleanForm();
1560 pPlugIn->SetSourceId(-1);
1561 SelectCatalog(-1);
1562 pPlugIn->SaveConfig();
1563 event.Skip();
1564}
1565
1566void ChartDldrPanelImpl::AddSource(wxCommandEvent &event) {
1568 dialog->SetBasePath(pPlugIn->GetBaseChartDir());
1569
1570 wxSize sz = GetParent()
1571 ->GetGrandParent()
1572 ->GetSize(); // This is the options panel true size
1573 dialog->SetSize(sz.GetWidth(), sz.GetHeight());
1574 dialog->Center();
1575
1576#ifdef __OCPN__ANDROID__
1577 androidDisableRotation();
1578#endif
1579
1580 if (dialog->ShowModal() == wxID_OK) {
1581 std::unique_ptr<ChartSource> cs =
1582 std::make_unique<ChartSource>(dialog->m_tSourceName->GetValue(),
1583 dialog->m_tChartSourceUrl->GetValue(),
1584 dialog->m_tcChartDirectory->GetValue());
1585 dialog->Destroy();
1586 AppendCatalog(cs);
1587 bool covered = false;
1588 for (size_t i = 0; i < GetChartDBDirArrayString().GetCount(); i++) {
1589 if (cs->GetDir().StartsWith((GetChartDBDirArrayString().Item(i)))) {
1590 covered = true;
1591 break;
1592 }
1593 }
1594 if (!covered) {
1595 wxString dir = cs->GetDir();
1596 AddChartDirectory(dir);
1597 }
1598
1599 long itemSelectedNow = GetSelectedCatalog();
1600 m_lbChartSources->SetItemState(itemSelectedNow, 0, wxLIST_STATE_SELECTED);
1601
1602 SelectCatalog(m_lbChartSources->GetItemCount() - 1);
1603 pPlugIn->m_ChartSources.push_back(std::move(cs));
1604 pPlugIn->SaveConfig();
1605 }
1606#ifdef __OCPN__ANDROID__
1607 androidEnableRotation();
1608#endif
1609
1610 event.Skip();
1611}
1612
1613void ChartDldrPanelImpl::DoEditSource() {
1614 if (!m_lbChartSources->GetSelectedItemCount()) return;
1615 int cat = GetSelectedCatalog();
1617 dialog->SetBasePath(pPlugIn->GetBaseChartDir());
1618 dialog->SetSourceEdit(pPlugIn->m_ChartSources.at(cat));
1619 dialog->SetTitle(_("Edit Chart Source"));
1620
1621 dialog->ShowModal();
1622 int retcode = dialog->GetReturnCode();
1623 {
1624 if (retcode == wxID_OK) {
1625 pPlugIn->m_ChartSources.at(cat)->SetName(
1626 dialog->m_tSourceName->GetValue());
1627 pPlugIn->m_ChartSources.at(cat)->SetUrl(
1628 dialog->m_tChartSourceUrl->GetValue());
1629 pPlugIn->m_ChartSources.at(cat)->SetDir(
1630 dialog->m_tcChartDirectory->GetValue());
1631
1632 m_lbChartSources->SetItem(cat, 0,
1633 pPlugIn->m_ChartSources.at(cat)->GetName());
1634 m_lbChartSources->SetItem(cat, 1, _("(Please update first)"));
1635 m_lbChartSources->SetItem(cat, 2,
1636 pPlugIn->m_ChartSources.at(cat)->GetDir());
1637 wxURI url(pPlugIn->m_ChartSources.at(cat)->GetUrl());
1638 wxFileName fn(url.GetPath());
1639 fn.SetPath(pPlugIn->m_ChartSources.at(cat)->GetDir());
1640 wxString path = fn.GetFullPath();
1641 if (wxFileExists(path)) {
1642 if (pPlugIn->m_pChartCatalog.LoadFromFile(path, true)) {
1643 m_lbChartSources->SetItem(cat, 0, pPlugIn->m_pChartCatalog.title);
1644 m_lbChartSources->SetItem(
1645 cat, 1,
1646 pPlugIn->m_pChartCatalog.GetReleaseDate().Format(
1647 _T("%Y-%m-%d %H:%M")));
1648 m_lbChartSources->SetItem(cat, 2, path);
1649 }
1650 }
1651 bool covered = false;
1652 for (size_t i = 0; i < GetChartDBDirArrayString().GetCount(); i++) {
1653 if (pPlugIn->m_ChartSources.at(cat)->GetDir().StartsWith(
1654 (GetChartDBDirArrayString().Item(i)))) {
1655 covered = true;
1656 break;
1657 }
1658 }
1659 if (!covered)
1660 OCPNMessageBox_PlugIn(
1661 this,
1662 wxString::Format(
1663 _("Path %s seems not to be covered by your configured Chart "
1664 "Directories.\nTo see the charts you have to adjust the "
1665 "configuration on the 'Chart Files' tab."),
1666 pPlugIn->m_ChartSources.at(cat)->GetDir().c_str()),
1667 _("Chart Downloader"));
1668
1669 pPlugIn->SaveConfig();
1670 SetSource(cat);
1671 }
1672 }
1673}
1674
1675void ChartDldrPanelImpl::EditSource(wxCommandEvent &event) {
1676 DoEditSource();
1677 event.Skip();
1678}
1679
1680void ChartDldrPanelImpl::OnLeftDClick(wxMouseEvent &event) {
1681 DoEditSource();
1682 event.Skip();
1683}
1684
1685bool chartdldr_pi::ProcessFile(const wxString &aFile,
1686 const wxString &aTargetDir, bool aStripPath,
1687 wxDateTime aMTime) {
1688 if (aFile.Lower().EndsWith(_T("zip"))) // Zip compressed
1689 {
1690 bool ret = ExtractZipFiles(aFile, aTargetDir, aStripPath, aMTime, false);
1691 if (ret)
1692 wxRemoveFile(aFile);
1693 else
1694 wxLogError(_T("chartdldr_pi: Unable to extract: ") + aFile);
1695 return ret;
1696 }
1697#ifdef DLDR_USE_LIBARCHIVE
1698 else if (aFile.Lower().EndsWith(_T("rar"))) {
1699#ifdef CHARTDLDR_RAR_UNARR
1700 bool ret = ExtractUnarrFiles(aFile, aTargetDir, aStripPath, aMTime, false);
1701#else
1702 bool ret =
1703 ExtractLibArchiveFiles(aFile, aTargetDir, aStripPath, aMTime, false);
1704#endif
1705 if (ret)
1706 wxRemoveFile(aFile);
1707 else
1708 wxLogError(_T("chartdldr_pi: Unable to extract: ") + aFile);
1709 return ret;
1710 } else if (aFile.Lower().EndsWith(_T("tar")) ||
1711 aFile.Lower().EndsWith(_T("gz")) ||
1712 aFile.Lower().EndsWith(_T("bz2")) ||
1713 aFile.Lower().EndsWith(_T("lzma")) ||
1714 aFile.Lower().EndsWith(_T("7z")) ||
1715 aFile.Lower().EndsWith(_T("xz"))) {
1716 bool ret =
1717 ExtractLibArchiveFiles(aFile, aTargetDir, aStripPath, aMTime, false);
1718 if (ret)
1719 wxRemoveFile(aFile);
1720 else
1721 wxLogError(_T("chartdldr_pi: Unable to extract: ") + aFile);
1722 return ret;
1723 }
1724#else
1725 else if (aFile.Lower().EndsWith(_T("rar")) ||
1726 aFile.Lower().EndsWith(_T("tar"))
1727#ifdef HAVE_BZIP2
1728 || aFile.Lower().EndsWith(_T("bz2"))
1729#endif
1730#ifdef HAVE_ZLIB
1731 || aFile.Lower().EndsWith(_T("gz"))
1732#endif
1733#ifdef HAVE_7Z
1734 || aFile.Lower().EndsWith(
1735 _T("7z")) // TODO: Could it actually extract more formats the
1736 // LZMA SDK supports?
1737#endif
1738 ) {
1739 bool ret = ExtractUnarrFiles(aFile, aTargetDir, aStripPath, aMTime, false);
1740 if (ret)
1741 wxRemoveFile(aFile);
1742 else
1743 wxLogError(_T("chartdldr_pi: Unable to extract: ") + aFile);
1744 return ret;
1745 }
1746#endif
1747
1748#ifdef __OCPN__ANDROID__
1749 else if (aFile.Lower().EndsWith(_T("tar")) ||
1750 aFile.Lower().EndsWith(_T("gz")) ||
1751 aFile.Lower().EndsWith(_T("bz2")) ||
1752 aFile.Lower().EndsWith(_T("lzma")) ||
1753 aFile.Lower().EndsWith(_T("7z")) ||
1754 aFile.Lower().EndsWith(_T("xz"))) {
1755 int nStrip = 0;
1756 if (aStripPath) nStrip = 1;
1757
1758 if (m_dldrpanel) m_dldrpanel->SetChartInfo(_("Installing charts."));
1759
1760 androidShowBusyIcon();
1761 bool ret = AndroidUnzip(aFile, aTargetDir, nStrip, true);
1762 androidHideBusyIcon();
1763
1764 return ret;
1765 }
1766#endif
1767
1768 else // Uncompressed
1769 {
1770 wxFileName fn(aFile);
1771 if (fn.GetPath() != aTargetDir) // We have to move the file somewhere
1772 {
1773 if (!wxDirExists(aTargetDir)) {
1774 if (wxFileName::Mkdir(aTargetDir, 0755, wxPATH_MKDIR_FULL)) {
1775 if (!wxRenameFile(aFile, aTargetDir)) return false;
1776 } else
1777 return false;
1778 }
1779 }
1780 wxString name = fn.GetFullName();
1781 fn.Clear();
1782 fn.Assign(aTargetDir, name);
1783 fn.SetTimes(&aMTime, &aMTime, &aMTime);
1784 }
1785 return true;
1786}
1787
1788#ifdef DLDR_USE_LIBARCHIVE
1789#ifndef __OCPN__ANDROID__
1790static int copy_data(struct archive *ar, struct archive *aw) {
1791 int r;
1792 const void *buff;
1793 size_t size;
1794 __LA_INT64_T offset;
1795
1796 for (;;) {
1797 r = archive_read_data_block(ar, &buff, &size, &offset);
1798 if (r == ARCHIVE_EOF) return (ARCHIVE_OK);
1799 if (r < ARCHIVE_OK) return (r);
1800 r = archive_write_data_block(aw, buff, size, offset);
1801 if (r < ARCHIVE_OK) {
1802 // fprintf(stderr, "%s\n", archive_error_string(aw));
1803 wxLogError(wxString::Format("Chartdldr_pi: LibArchive error: %s",
1804 archive_error_string(aw)));
1805 return (r);
1806 }
1807 }
1808}
1809#endif
1810
1811bool chartdldr_pi::ExtractLibArchiveFiles(const wxString &aArchiveFile,
1812 const wxString &aTargetDir,
1813 bool aStripPath, wxDateTime aMTime,
1814 bool aRemoveArchive) {
1815#ifndef __OCPN__ANDROID__
1816 struct archive *a;
1817 struct archive *ext;
1818 struct archive_entry *entry;
1819 int flags;
1820 int r;
1821
1822 /* Select which attributes we want to restore. */
1823 flags = ARCHIVE_EXTRACT_TIME;
1824 /*
1825 flags |= ARCHIVE_EXTRACT_PERM;
1826 flags |= ARCHIVE_EXTRACT_ACL;
1827 flags |= ARCHIVE_EXTRACT_FFLAGS;
1828 */
1829
1830 a = archive_read_new();
1831 archive_read_support_format_all(a);
1832 archive_read_support_filter_all(a);
1833 archive_read_support_compression_all(a);
1834 ext = archive_write_disk_new();
1835 archive_write_disk_set_options(ext, flags);
1836 archive_write_disk_set_standard_lookup(ext);
1837 if ((r = archive_read_open_filename(a, aArchiveFile.c_str(), 10240))) {
1838 return false;
1839 }
1840 for (;;) {
1841 r = archive_read_next_header(a, &entry);
1842 if (r == ARCHIVE_EOF) break;
1843 if (r < ARCHIVE_OK)
1844 // fprintf(stderr, "%s\n", archive_error_string(a));
1845 wxLogError(wxString::Format("Chartdldr_pi: LibArchive error: %s",
1846 archive_error_string(a)));
1847 if (r < ARCHIVE_WARN) return false;
1848 if (aStripPath) {
1849 const char *currentFile = archive_entry_pathname(entry);
1850 std::string fullOutputPath = currentFile;
1851 size_t sep = fullOutputPath.find_last_of("\\/");
1852 if (sep != std::string::npos)
1853 fullOutputPath =
1854 fullOutputPath.substr(sep + 1, fullOutputPath.size() - sep - 1);
1855 archive_entry_set_pathname(entry, fullOutputPath.c_str());
1856 }
1857 if (aTargetDir != wxEmptyString) {
1858 const char *currentFile = archive_entry_pathname(entry);
1859 const std::string fullOutputPath =
1860 aTargetDir.ToStdString() +
1861 wxString(wxFileName::GetPathSeparator()).ToStdString() + currentFile;
1862 archive_entry_set_pathname(entry, fullOutputPath.c_str());
1863 }
1864 r = archive_write_header(ext, entry);
1865 if (r < ARCHIVE_OK)
1866 // fprintf(stderr, "%s\n", archive_error_string(ext));
1867 wxLogError(wxString::Format("Chartdldr_pi: LibArchive error: %s",
1868 archive_error_string(ext)));
1869 else if (archive_entry_size(entry) > 0) {
1870 r = copy_data(a, ext);
1871 if (r < ARCHIVE_OK)
1872 // fprintf(stderr, "%s\n", archive_error_string(ext));
1873 wxLogError(wxString::Format("Chartdldr_pi: LibArchive error: %s",
1874 archive_error_string(ext)));
1875 if (r < ARCHIVE_WARN) return false;
1876 }
1877 r = archive_write_finish_entry(ext);
1878
1879 if (r < ARCHIVE_OK)
1880 // fprintf(stderr, "%s\n", archive_error_string(ext));
1881 wxLogError(wxString::Format("Chartdldr_pi: LibArchive error: %s",
1882 archive_error_string(ext)));
1883 if (r < ARCHIVE_WARN) return false;
1884 }
1885 archive_read_close(a);
1886 // archive_read_free(a);
1887 archive_write_close(ext);
1888 // archive_write_free(ext);
1889
1890 if (aRemoveArchive) wxRemoveFile(aArchiveFile);
1891 return true;
1892
1893#else
1894
1895 return rv;
1896
1897#endif // Android
1898}
1899#endif
1900
1901#if defined(CHARTDLDR_RAR_UNARR) || !defined(DLDR_USE_LIBARCHIVE)
1902ar_archive *ar_open_any_archive(ar_stream *stream, const char *fileext) {
1903 ar_archive *ar = ar_open_rar_archive(stream);
1904 if (!ar)
1905 ar =
1906 ar_open_zip_archive(stream, fileext && (strcmp(fileext, ".xps") == 0 ||
1907 strcmp(fileext, ".epub") == 0));
1908 if (!ar) ar = ar_open_7z_archive(stream);
1909 if (!ar) ar = ar_open_tar_archive(stream);
1910 return ar;
1911}
1912
1913bool chartdldr_pi::ExtractUnarrFiles(const wxString &aRarFile,
1914 const wxString &aTargetDir,
1915 bool aStripPath, wxDateTime aMTime,
1916 bool aRemoveRar) {
1917 ar_stream *stream = NULL;
1918 ar_archive *ar = NULL;
1919 int entry_count = 1;
1920 int entry_skips = 0;
1921 int error_step = 1;
1922 bool ret = true;
1923
1924 stream = ar_open_file(aRarFile.c_str());
1925 if (!stream) {
1926 wxLogError(_T("Can not open file '") + aRarFile + _T("'."));
1927 ar_close_archive(ar);
1928 ar_close(stream);
1929 return false;
1930 }
1931 ar = ar_open_any_archive(stream, strrchr(aRarFile.c_str(), '.'));
1932 if (!ar) {
1933 wxLogError(_T("Can not open archive '") + aRarFile + _T("'."));
1934 ar_close_archive(ar);
1935 ar_close(stream);
1936 return false;
1937 }
1938 while (ar_parse_entry(ar)) {
1939 size_t size = ar_entry_get_size(ar);
1940 wxString name = ar_entry_get_name(ar);
1941 if (aStripPath) {
1942 wxFileName fn(name);
1943 /* We can completly replace the entry path */
1944 // fn.SetPath(aTargetDir);
1945 // name = fn.GetFullPath();
1946 /* Or only remove the first dir (eg. ENC_ROOT) */
1947 if (fn.GetDirCount() > 0) {
1948 fn.RemoveDir(0);
1949 name = aTargetDir + wxFileName::GetPathSeparator() + fn.GetFullPath();
1950 } else {
1951 name = aTargetDir + wxFileName::GetPathSeparator() + name;
1952 }
1953 }
1954 wxFileName fn(name);
1955 if (!fn.DirExists()) {
1956 if (!wxFileName::Mkdir(fn.GetPath())) {
1957 wxLogError(_T("Can not create directory '") + fn.GetPath() + _T("'."));
1958 ret = false;
1959 break;
1960 }
1961 }
1962 wxFileOutputStream file(name);
1963 if (!file) {
1964 wxLogError(_T("Can not create file '") + name + _T("'."));
1965 ret = false;
1966 break;
1967 }
1968 while (size > 0) {
1969 unsigned char buffer[1024];
1970 size_t count = size < sizeof(buffer) ? size : sizeof(buffer);
1971 if (!ar_entry_uncompress(ar, buffer, count)) break;
1972 file.Write(buffer, count);
1973 size -= count;
1974 }
1975 file.Close();
1976 fn.SetTimes(&aMTime, &aMTime, &aMTime);
1977 if (size > 0) {
1978 wxLogError("Warning: Failed to uncompress... skipping");
1979 entry_skips++;
1980 ret = false;
1981 }
1982 }
1983 if (!ar_at_eof(ar)) {
1984 wxLogError("Error: Failed to parse entry %d!", entry_count);
1985 ret = false;
1986 }
1987 ar_close_archive(ar);
1988 ar_close(stream);
1989
1990 if (aRemoveRar) wxRemoveFile(aRarFile);
1991
1992#ifdef _UNIX
1993 // reset LC_NUMERIC locale, some locales use a comma for decimal point
1994 // and it corrupts navobj.xml file
1995 setlocale(LC_NUMERIC, "C");
1996#endif
1997
1998 return ret;
1999}
2000#endif
2001
2002bool chartdldr_pi::ExtractZipFiles(const wxString &aZipFile,
2003 const wxString &aTargetDir, bool aStripPath,
2004 wxDateTime aMTime, bool aRemoveZip) {
2005 bool ret = true;
2006
2007#ifdef __OCPN__ANDROID__
2008 int nStrip = 0;
2009 if (aStripPath) nStrip = 1;
2010
2011 ret = AndroidUnzip(aZipFile, aTargetDir, nStrip, true);
2012#else
2013 std::unique_ptr<wxZipEntry> entry(new wxZipEntry());
2014
2015 do {
2016 wxLogMessage(_T("chartdldr_pi: Going to extract '") + aZipFile + _T("'."));
2017 wxFileInputStream in(aZipFile);
2018
2019 if (!in) {
2020 wxLogMessage(_T("Can not open file '") + aZipFile + _T("'."));
2021 ret = false;
2022 break;
2023 }
2024 wxZipInputStream zip(in);
2025 ret = false;
2026
2027 while (entry.reset(zip.GetNextEntry()), entry.get() != NULL) {
2028 // access meta-data
2029 wxString name = entry->GetName();
2030 if (aStripPath) {
2031 wxFileName fn(name);
2032 /* We can completly replace the entry path */
2033 // fn.SetPath(aTargetDir);
2034 // name = fn.GetFullPath();
2035 /* Or only remove the first dir (eg. ENC_ROOT) */
2036 if (fn.GetDirCount() > 0) fn.RemoveDir(0);
2037 name = aTargetDir + wxFileName::GetPathSeparator() + fn.GetFullPath();
2038 } else {
2039 name = aTargetDir + wxFileName::GetPathSeparator() + name;
2040 }
2041
2042 // read 'zip' to access the entry's data
2043 if (entry->IsDir()) {
2044 int perm = entry->GetMode();
2045 if (!wxFileName::Mkdir(name, perm, wxPATH_MKDIR_FULL)) {
2046 wxLogMessage(_T("Can not create directory '") + name + _T("'."));
2047 ret = false;
2048 break;
2049 }
2050 } else {
2051 if (!zip.OpenEntry(*entry.get())) {
2052 wxLogMessage(_T("Can not open zip entry '") + entry->GetName() +
2053 _T("'."));
2054 ret = false;
2055 break;
2056 }
2057 if (!zip.CanRead()) {
2058 wxLogMessage(_T("Can not read zip entry '") + entry->GetName() +
2059 _T("'."));
2060 ret = false;
2061 break;
2062 }
2063
2064 wxFileName fn(name);
2065 if (!fn.DirExists()) {
2066 if (!wxFileName::Mkdir(fn.GetPath())) {
2067 wxLogMessage(_T("Can not create directory '") + fn.GetPath() +
2068 _T("'."));
2069 ret = false;
2070 break;
2071 }
2072 }
2073
2074 wxFileOutputStream file(name);
2075
2076 if (!file) {
2077 wxLogMessage(_T("Can not create file '") + name + _T("'."));
2078 ret = false;
2079 break;
2080 }
2081 zip.Read(file);
2082 fn.SetTimes(&aMTime, &aMTime, &aMTime);
2083 ret = true;
2084 }
2085 }
2086
2087 } while (false);
2088
2089 if (aRemoveZip) wxRemoveFile(aZipFile);
2090#endif // Android
2091
2092 return ret;
2093}
2094
2095ChartDldrGuiAddSourceDlg::ChartDldrGuiAddSourceDlg(wxWindow *parent)
2096 : AddSourceDlg(parent) {
2097 wxFileName fn;
2098 fn.SetPath(*GetpSharedDataLocation());
2099 fn.AppendDir(_T("plugins"));
2100 fn.AppendDir(_T("chartdldr_pi"));
2101 fn.AppendDir(_T("data"));
2102
2103 int w = 16; // default for desktop
2104 int h = 16;
2105
2106#ifdef __OCPN__ANDROID__
2107 w = 6 * g_androidDPmm; // mm nominal size
2108 h = w;
2109
2110 p_buttonIconList = new wxImageList(w, h);
2111
2112 fn.SetFullName(_T("button_right.png"));
2113 wxImage im1(fn.GetFullPath(), wxBITMAP_TYPE_PNG);
2114 im1.Rescale(w, h, wxIMAGE_QUALITY_HIGH);
2115 p_buttonIconList->Add(im1);
2116
2117 fn.SetFullName(_T("button_right.png"));
2118 wxImage im2(fn.GetFullPath(), wxBITMAP_TYPE_PNG);
2119 im2.Rescale(w, h, wxIMAGE_QUALITY_HIGH);
2120 p_buttonIconList->Add(im2);
2121
2122 fn.SetFullName(_T("button_down.png"));
2123 wxImage im3(fn.GetFullPath(), wxBITMAP_TYPE_PNG);
2124 im3.Rescale(w, h, wxIMAGE_QUALITY_HIGH);
2125 p_buttonIconList->Add(im3);
2126
2127 fn.SetFullName(_T("button_down.png"));
2128 wxImage im4(fn.GetFullPath(), wxBITMAP_TYPE_PNG);
2129 im4.Rescale(w, h, wxIMAGE_QUALITY_HIGH);
2130 p_buttonIconList->Add(im4);
2131
2132 m_treeCtrlPredefSrcs->AssignButtonsImageList(p_buttonIconList);
2133#else
2134 p_iconList = new wxImageList(w, h);
2135
2136 fn.SetFullName(_T("folder.png"));
2137 wxImage ima(fn.GetFullPath(), wxBITMAP_TYPE_PNG);
2138 ima.Rescale(w, h, wxIMAGE_QUALITY_HIGH);
2139 p_iconList->Add(ima);
2140
2141 fn.SetFullName(_T("file.png"));
2142 wxImage imb(fn.GetFullPath(), wxBITMAP_TYPE_PNG);
2143 imb.Rescale(w, h, wxIMAGE_QUALITY_HIGH);
2144 p_iconList->Add(imb);
2145
2146 m_treeCtrlPredefSrcs->AssignImageList(p_iconList);
2147#endif /* __OCPN__ANDROID__ */
2148
2149 m_treeCtrlPredefSrcs->SetIndent(w);
2150
2151 m_base_path = wxEmptyString;
2152 m_last_path = wxEmptyString;
2153 LoadSources();
2154 m_nbChoice->SetSelection(0);
2155 // m_treeCtrlPredefSrcs->ExpandAll();
2156
2157 Fit();
2158
2159 applyStyle();
2160}
2161
2162bool ChartDldrGuiAddSourceDlg::LoadSources() {
2163 wxTreeItemId tree = m_treeCtrlPredefSrcs->AddRoot(_T("root"));
2164
2165 wxFileName fn;
2166 fn.SetPath(*GetpPrivateApplicationDataLocation());
2167 fn.SetFullName(_T("chartdldr_pi-chart_sources.xml"));
2168 if (!fn.FileExists()) {
2169 fn.SetPath(*GetpSharedDataLocation());
2170 fn.AppendDir(_T("plugins"));
2171 fn.AppendDir(_T("chartdldr_pi"));
2172 fn.AppendDir(_T("data"));
2173 fn.SetFullName(_T("chart_sources.xml"));
2174 if (!fn.FileExists()) {
2175 wxLogMessage(wxString::Format(
2176 _T("Error: chartdldr_pi::LoadSources() %s not found!"),
2177 fn.GetFullPath().c_str()));
2178 return false;
2179 }
2180 }
2181 wxString path = fn.GetFullPath();
2182
2184 bool ret = doc->load_file(path.mb_str());
2185 if (ret) {
2186 pugi::xml_node root = doc->first_child();
2187
2188 for (pugi::xml_node element = root.first_child(); element;
2189 element = element.next_sibling()) {
2190 if (!strcmp(element.name(), "sections")) {
2191 LoadSections(tree, element);
2192 }
2193 }
2194 }
2195 wxDELETE(doc);
2196 return true;
2197}
2198
2199bool ChartDldrGuiAddSourceDlg::LoadSections(const wxTreeItemId &root,
2200 pugi::xml_node &node) {
2201 for (pugi::xml_node element = node.first_child(); element;
2202 element = element.next_sibling()) {
2203 if (!strcmp(element.name(), "section")) {
2204 LoadSection(root, element);
2205 }
2206 }
2207 return true;
2208}
2209
2210bool ChartDldrGuiAddSourceDlg::LoadSection(const wxTreeItemId &root,
2211 pugi::xml_node &node) {
2212 wxTreeItemId item;
2213 for (pugi::xml_node element = node.first_child(); element;
2214 element = element.next_sibling()) {
2215 if (!strcmp(element.name(), "name")) {
2216 item = m_treeCtrlPredefSrcs->AppendItem(
2217 root, wxString::FromUTF8(element.first_child().value()), 0, 0);
2218
2219 wxFont *pFont = OCPNGetFont(_("Dialog"), 0);
2220 if (pFont) m_treeCtrlPredefSrcs->SetItemFont(item, *pFont);
2221 }
2222 if (!strcmp(element.name(), "sections")) LoadSections(item, element);
2223 if (!strcmp(element.name(), "catalogs")) LoadCatalogs(item, element);
2224 }
2225
2226 return true;
2227}
2228
2229bool ChartDldrGuiAddSourceDlg::LoadCatalogs(const wxTreeItemId &root,
2230 pugi::xml_node &node) {
2231 for (pugi::xml_node element = node.first_child(); element;
2232 element = element.next_sibling()) {
2233 if (!strcmp(element.name(), "catalog")) LoadCatalog(root, element);
2234 }
2235
2236 return true;
2237}
2238
2239bool ChartDldrGuiAddSourceDlg::LoadCatalog(const wxTreeItemId &root,
2240 pugi::xml_node &node) {
2241 wxString name, type, location, dir;
2242 for (pugi::xml_node element = node.first_child(); element;
2243 element = element.next_sibling()) {
2244 if (!strcmp(element.name(), "name"))
2245 name = wxString::FromUTF8(element.first_child().value());
2246 else if (!strcmp(element.name(), "type"))
2247 type = wxString::FromUTF8(element.first_child().value());
2248 else if (!strcmp(element.name(), "location"))
2249 location = wxString::FromUTF8(element.first_child().value());
2250 else if (!strcmp(element.name(), "dir"))
2251 dir = wxString::FromUTF8(element.first_child().value());
2252 }
2253 ChartSource *cs = new ChartSource(name, location, dir);
2254 wxTreeItemId id = m_treeCtrlPredefSrcs->AppendItem(root, name, 1, 1, cs);
2255
2256 wxFont *pFont = OCPNGetFont(_("Dialog"), 0);
2257 if (pFont) m_treeCtrlPredefSrcs->SetItemFont(id, *pFont);
2258
2259 return true;
2260}
2261
2262ChartDldrGuiAddSourceDlg::~ChartDldrGuiAddSourceDlg() {}
2263
2264wxString ChartDldrGuiAddSourceDlg::FixPath(wxString path) {
2265 wxString sep(wxFileName::GetPathSeparator());
2266 wxString s = path;
2267 s.Replace(_T("/"), sep, true);
2268 s.Replace(_T(USERDATA), m_base_path);
2269 s.Replace(sep + sep, sep);
2270 return s;
2271}
2272
2273void ChartDldrGuiAddSourceDlg::OnChangeType(wxCommandEvent &event) {
2274 m_treeCtrlPredefSrcs->Enable(m_nbChoice->GetSelection() == 0);
2275 m_tSourceName->Enable(m_nbChoice->GetSelection() == 1);
2276 m_tChartSourceUrl->Enable(m_nbChoice->GetSelection() == 1);
2277}
2278
2279void ChartDldrGuiAddSourceDlg::OnSourceSelected(wxTreeEvent &event) {
2280 wxTreeItemId item = m_treeCtrlPredefSrcs->GetSelection();
2281 ChartSource *cs = (ChartSource *)(m_treeCtrlPredefSrcs->GetItemData(item));
2282 if (cs) {
2283 m_dirExpanded = FixPath(cs->GetDir());
2284
2285 m_tSourceName->SetValue(cs->GetName());
2286 m_tChartSourceUrl->SetValue(cs->GetUrl());
2287 if (m_tcChartDirectory->GetValue() == m_last_path) {
2288 m_tcChartDirectory->SetValue(FixPath(cs->GetDir()));
2289 m_panelChartDirectory->SetText(FixPath(cs->GetDir()));
2290
2291 m_buttonChartDirectory->Enable();
2292 m_last_path = m_tcChartDirectory->GetValue();
2293 }
2294 }
2295 event.Skip();
2296}
2297
2298void ChartDldrGuiAddSourceDlg::SetSourceEdit(std::unique_ptr<ChartSource> &cs) {
2299 m_nbChoice->SetSelection(1);
2300 m_tChartSourceUrl->Enable();
2301 m_treeCtrlPredefSrcs->Disable();
2302 m_tSourceName->SetValue(cs->GetName());
2303 m_tChartSourceUrl->SetValue(cs->GetUrl());
2304 m_tcChartDirectory->SetValue(FixPath(cs->GetDir()));
2305 m_panelChartDirectory->SetText(FixPath(cs->GetDir()));
2306
2307 m_buttonChartDirectory->Enable();
2308}
2309
2310ChartDldrPrefsDlgImpl::ChartDldrPrefsDlgImpl(wxWindow *parent)
2311 : ChartDldrPrefsDlg(parent) {}
2312
2313ChartDldrPrefsDlgImpl::~ChartDldrPrefsDlgImpl() {}
2314
2315void ChartDldrPrefsDlgImpl::SetPath(const wxString path) {
2316 // if( !wxDirExists(path) )
2317 // if( !wxFileName::Mkdir(path, 0755, wxPATH_MKDIR_FULL) )
2318 //{
2319 // OCPNMessageBox_PlugIn(this, wxString::Format(_("Directory %s can't be
2320 // created."), m_dpDefaultDir->GetTextCtrlValue().c_str()), _("Chart
2321 // Downloader")); return;
2322 //}
2323 m_tcDefaultDir->SetValue(path);
2324}
2325
2326void ChartDldrPrefsDlgImpl::GetPreferences(bool &preselect_new,
2327 bool &preselect_updated,
2328 bool &bulk_update) {
2329 preselect_new = m_cbSelectNew->GetValue();
2330 preselect_updated = m_cbSelectUpdated->GetValue();
2331 bulk_update = m_cbBulkUpdate->GetValue();
2332}
2333void ChartDldrPrefsDlgImpl::SetPreferences(bool preselect_new,
2334 bool preselect_updated,
2335 bool bulk_update) {
2336 m_cbSelectNew->SetValue(preselect_new);
2337 m_cbSelectUpdated->SetValue(preselect_updated);
2338 m_cbBulkUpdate->SetValue(bulk_update);
2339}
2340
2341void ChartDldrGuiAddSourceDlg::OnOkClick(wxCommandEvent &event) {
2342 wxString msg = wxEmptyString;
2343
2344 if (m_nbChoice->GetSelection() == 0) {
2345 wxTreeItemId item = m_treeCtrlPredefSrcs->GetSelection();
2346 if (m_treeCtrlPredefSrcs->GetSelection().IsOk()) {
2347 ChartSource *cs =
2348 (ChartSource *)(m_treeCtrlPredefSrcs->GetItemData(item));
2349 if (!cs)
2350 msg +=
2351 _("You must select one of the predefined chart sources or create "
2352 "one of your own.\n");
2353 } else
2354 msg +=
2355 _("You must select one of the predefined chart sources or create one "
2356 "of your own.\n");
2357 }
2358 if (m_nbChoice->GetSelection() == 1 &&
2359 m_tSourceName->GetValue() == wxEmptyString)
2360 msg += _("The chart source must have a name.\n");
2361 wxURI url(m_tChartSourceUrl->GetValue());
2362 if (m_nbChoice->GetSelection() == 1 &&
2363 (m_tChartSourceUrl->GetValue() == wxEmptyString ||
2364 !ValidateUrl(m_tChartSourceUrl->GetValue())))
2365 msg += _("The chart source must have a valid URL.\n");
2366 if (m_tcChartDirectory->GetValue() == wxEmptyString)
2367 msg += _("You must select a local folder to store the charts.\n");
2368 else if (!wxDirExists(m_tcChartDirectory->GetValue()))
2369 if (!wxFileName::Mkdir(m_tcChartDirectory->GetValue(), 0755,
2370 wxPATH_MKDIR_FULL))
2371 msg += wxString::Format(_("Directory %s can't be created."),
2372 m_tcChartDirectory->GetValue().c_str()) +
2373 _T("\n");
2374
2375 if (msg != wxEmptyString)
2376 OCPNMessageBox_PlugIn(this, msg, _("Chart source definition problem"),
2377 wxOK | wxCENTRE | wxICON_ERROR);
2378 else {
2379 event.Skip();
2380 SetReturnCode(wxID_OK);
2381 EndModal(wxID_OK);
2382 }
2383}
2384
2385void ChartDldrGuiAddSourceDlg::OnCancelClick(wxCommandEvent &event) {
2386 SetReturnCode(wxID_CANCEL);
2387 EndModal(wxID_CANCEL);
2388}
2389
2390void ChartDldrPrefsDlgImpl::OnOkClick(wxCommandEvent &event) {
2391 if (!wxDirExists(m_tcDefaultDir->GetValue())) {
2392 if (!wxFileName::Mkdir(m_tcDefaultDir->GetValue(), 0755,
2393 wxPATH_MKDIR_FULL)) {
2394 OCPNMessageBox_PlugIn(
2395 this,
2396 wxString::Format(_("Directory %s can't be created."),
2397 m_tcDefaultDir->GetValue().c_str()),
2398 _("Chart Downloader"));
2399 return;
2400 }
2401 }
2402
2403 if (g_pi) {
2404 g_pi->UpdatePrefs(this);
2405 }
2406
2407 event.Skip();
2408 EndModal(wxID_OK);
2409
2410 // Hide();
2411 // Close();
2412}
2413
2414void ChartDldrPrefsDlg::OnCancelClick(wxCommandEvent &event) {
2415 event.Skip();
2416 EndModal(wxID_CANCEL);
2417 // Close();
2418}
2419
2420void ChartDldrPrefsDlg::OnOkClick(wxCommandEvent &event) {
2421 event.Skip();
2422 // Close();
2423}
2424
2425bool ChartDldrGuiAddSourceDlg::ValidateUrl(const wxString Url,
2426 bool catalog_xml) {
2427 wxRegEx re;
2428 if (catalog_xml)
2429 re.Compile(
2430 _T("^https?\\://[a-zA-Z0-9\\./_-]*\\.[xX][mM][lL]$")); // TODO: wxRegEx
2431 // sucks a bit,
2432 // this RE is
2433 // way too naive
2434 else
2435 re.Compile(
2436 _T("^https?\\://[a-zA-Z0-9\\./_-]*$")); // TODO: wxRegEx sucks a bit,
2437 // this RE is way too naive
2438 return re.Matches(Url);
2439}
2440
2441static wxString FormatBytes(double bytes) {
2442 return wxString::Format(_T("%.1fMB"), bytes / 1024 / 1024);
2443}
2444
2445void ChartDldrPanelImpl::onDLEvent(OCPN_downloadEvent &ev) {
2446 // wxString msg;
2447 // msg.Printf(_T("onDLEvent %d %d"),ev.getDLEventCondition(),
2448 // ev.getDLEventStatus()); wxLogMessage(msg);
2449
2450 switch (ev.getDLEventCondition()) {
2451 case OCPN_DL_EVENT_TYPE_END:
2452 m_bTransferComplete = true;
2453 m_bTransferSuccess =
2454 (ev.getDLEventStatus() == OCPN_DL_NO_ERROR) ? true : false;
2455 break;
2456
2457 case OCPN_DL_EVENT_TYPE_PROGRESS:
2458 m_totalsize = FormatBytes(ev.getTotal());
2459 m_transferredsize = FormatBytes(ev.getTransferred());
2460
2461 break;
2462 default:
2463 break;
2464 }
2465 wxYieldIfNeeded();
2466}
Class AddSourceDlg.
Implementing ChartDldrPanel.
Class ChartDldrPanel.
Class ChartDldrPrefsDlg.
wxBitmap * GetPlugInBitmap()
FIXME static wxBitmap* LoadSVG(const wxString filename, unsigned int width, ...
@ OCPN_DLDS_URL
The dialog shows the URL involved in the transfer.
@ OCPN_DLDS_AUTO_CLOSE
The dialog auto closes when transfer is complete.
@ OCPN_DLDS_CAN_ABORT
The transfer can be aborted by the user.
@ OCPN_DLDS_ESTIMATED_TIME
The dialog shows the estimated total time.
@ OCPN_DLDS_SIZE
The dialog shows the size of the resource to download/upload.
@ OCPN_DLDS_REMAINING_TIME
The dialog shows the remaining time.
@ OCPN_DLDS_SPEED
The dialog shows the transfer speed.
@ OCPN_DLDS_ELAPSED_TIME
The dialog shows the elapsed time.
@ OCPN_DLDS_CAN_PAUSE
The transfer can be paused.
#define INSTALLS_TOOLBOX_PAGE
Plugin will add pages to the toolbox/settings dialog.
#define WANTS_PREFERENCES
Plugin will add page(s) to global preferences dialog.
#define WANTS_CONFIG
Plugin requires persistent configuration storage.
wxFont * OCPNGetFont(wxString TextElement, int default_size)
Gets a font for UI elements.
wxFont GetOCPNGUIScaledFont_PlugIn(wxString item)
Gets a uniquely scaled font copy for responsive UI elements.