35#include "chartdldr_pi.h"
36#include "wxWTranslateCatalog.h"
37#include <wx/stdpaths.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>
45#include <wx/filesys.h>
46#include <wx/zipstrm.h>
47#include <wx/wfstream.h>
52#ifdef DLDR_USE_LIBARCHIVE
54#include <archive_entry.h>
55#ifdef CHARTDLDR_RAR_UNARR
63#define _LIBCPP_HAS_NO_OFF_T_FUNCTIONS
69#include "androidSupport.h"
70#include "android_jvm.h"
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
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
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
104#include <QtAndroidExtras/QAndroidJniObject>
109bool getDisplayMetrics();
111#define CHART_DIR "Charts"
116static bool IsPathInsideDir(
const wxString &targetDir,
117 const wxString &entryName, wxString &outFullPath) {
119 wxString combinedPath = targetDir;
120 if (!combinedPath.EndsWith(wxFileName::GetPathSeparator())) {
121 combinedPath += wxFileName::GetPathSeparator();
123 combinedPath += entryName;
126 wxFileName fn(combinedPath);
127 fn.Normalize(wxPATH_NORM_DOTS | wxPATH_NORM_ABSOLUTE | wxPATH_NORM_LONG);
128 outFullPath = fn.GetFullPath();
131 wxFileName targetFn(targetDir);
132 targetFn.Normalize(wxPATH_NORM_DOTS | wxPATH_NORM_ABSOLUTE |
134 wxString normalizedTarget = targetFn.GetFullPath();
137 if (!normalizedTarget.EndsWith(wxFileName::GetPathSeparator())) {
138 normalizedTarget += wxFileName::GetPathSeparator();
144 if (outFullPath.StartsWith(normalizedTarget)) {
149 if (outFullPath == targetFn.GetFullPath()) {
156static wxString FormatBytes(
double bytes) {
157 if (bytes <= 0)
return "?";
158 return wxString::Format(_T(
"%.1fMB"), bytes / 1024 / 1024);
161static wxString FormatBytes(
long bytes) {
162 return FormatBytes(
static_cast<double>(bytes));
165int g_Android_SDK_Version;
167bool IsDLDirWritable(wxFileName fn) {
169 return fn.IsDirWritable();
171 if (g_Android_SDK_Version >= 30) {
173 return (fn.GetFullPath().Contains(
"org.opencpn.opencpn"));
175 return fn.IsDirWritable();
186extern "C" DECL_EXP
void destroy_pi(
opencpn_plugin *p) {
delete p; }
209 m_parent_window = NULL;
210 m_pChartSource = NULL;
212 m_preselect_new =
false;
213 m_preselect_updated =
false;
214 m_allow_bulk_update =
false;
215 m_pOptionsPage = NULL;
216 m_selected_source = -1;
218 m_schartdldr_sources = wxEmptyString;
232 m_pOptionsPage = NULL;
234 m_pChartSource = NULL;
237 androidGetSDKVersion();
245 wxStringTokenizer st(m_schartdldr_sources, _T(
"|"), wxTOKEN_DEFAULT);
246 while (st.HasMoreTokens()) {
247 wxString s1 = st.GetNextToken();
248 wxString s2 = st.GetNextToken();
249 wxString s3 = st.GetNextToken();
251 m_ChartSources.push_back(std::make_unique<ChartSource>(s1, s2, s3));
257 wxLogMessage(_T(
"chartdldr_pi: DeInit"));
259 m_ChartSources.clear();
267 if (m_pOptionsPage) {
287 return _(
"Chart Downloader PlugIn for OpenCPN");
292 "Chart Downloader PlugIn for OpenCPN\n\
293Manages chart downloads and updates from sources supporting\n\
294NOAA Chart Catalog format");
300 if (!m_pOptionsPage) {
302 _T(
"Error: chartdldr_pi::OnSetupOptions AddOptionsPage failed!"));
305 wxBoxSizer *sizer =
new wxBoxSizer(wxVERTICAL);
306 m_pOptionsPage->SetSizer(sizer);
310 wxDefaultSize, wxDEFAULT_DIALOG_STYLE);
312 m_pOptionsPage->InvalidateBestSize();
313 sizer->Add(m_dldrpanel, 1, wxALL | wxEXPAND);
314 m_dldrpanel->SetBulkUpdate(m_allow_bulk_update);
315 m_dldrpanel->FitInside();
320 m_dldrpanel->CancelDownload();
325 m_selected_source = m_dldrpanel->GetSelectedCatalog();
329bool chartdldr_pi::LoadConfig(
void) {
330 wxFileConfig *pConf = (wxFileConfig *)m_pconfig;
333 pConf->SetPath(_T (
"/Settings/ChartDnldr" ));
334 pConf->Read(_T (
"ChartSources" ), &m_schartdldr_sources, wxEmptyString);
335 pConf->Read(_T (
"Source" ), &m_selected_source, -1);
338 fn.AppendDir(_T(CHART_DIR));
340 pConf->Read(_T (
"BaseChartDir" ), &m_base_chart_dir, fn.GetPath());
341 wxLogMessage(_T (
"chartdldr_pi:m_base_chart_dir: " ) + m_base_chart_dir);
344 wxFileName testFN(m_base_chart_dir);
345 if (!IsDLDirWritable(testFN)) {
347 "Cannot write to m_base_chart_dir, override to "
348 "GetWritableDocumentsDir()");
349 m_base_chart_dir = fn.GetPath();
350 wxLogMessage(_T (
"chartdldr_pi: Corrected: " ) + m_base_chart_dir);
353 pConf->Read(_T (
"PreselectNew" ), &m_preselect_new,
true);
354 pConf->Read(_T (
"PreselectUpdated" ), &m_preselect_updated,
true);
355 pConf->Read(_T (
"AllowBulkUpdate" ), &m_allow_bulk_update,
false);
361bool chartdldr_pi::SaveConfig(
void) {
362 wxFileConfig *pConf = (wxFileConfig *)m_pconfig;
364 m_schartdldr_sources.Clear();
366 for (
size_t i = 0; i < m_ChartSources.size(); i++) {
367 std::unique_ptr<ChartSource> &cs = m_ChartSources.at(i);
368 m_schartdldr_sources.Append(
369 wxString::Format(_T(
"%s|%s|%s|"), cs->GetName().c_str(),
370 cs->GetUrl().c_str(), cs->GetDir().c_str()));
374 pConf->SetPath(_T (
"/Settings/ChartDnldr" ));
375 pConf->Write(_T (
"ChartSources" ), m_schartdldr_sources);
376 pConf->Write(_T (
"Source" ), m_selected_source);
377 pConf->Write(_T (
"BaseChartDir" ), m_base_chart_dir);
378 pConf->Write(_T (
"PreselectNew" ), m_preselect_new);
379 pConf->Write(_T (
"PreselectUpdated" ), m_preselect_updated);
380 pConf->Write(_T (
"AllowBulkUpdate" ), m_allow_bulk_update);
387void SetBackColor(wxWindow *ctrl, wxColour col) {
388 static int depth = 0;
391 ctrl->SetBackgroundColour(col);
394 wxWindowList kids = ctrl->GetChildren();
395 for (
unsigned int i = 0; i < kids.GetCount(); i++) {
396 wxWindowListNode *node = kids.Item(i);
397 wxWindow *win = node->GetData();
399 if (
dynamic_cast<wxListBox *
>(win))
400 dynamic_cast<wxListBox *
>(win)->SetBackgroundColour(col);
402 else if (
dynamic_cast<wxTextCtrl *
>(win))
403 dynamic_cast<wxTextCtrl *
>(win)->SetBackgroundColour(col);
408 else if (
dynamic_cast<wxChoice *
>(win))
409 dynamic_cast<wxChoice *
>(win)->SetBackgroundColour(col);
411 else if (
dynamic_cast<wxComboBox *
>(win))
412 dynamic_cast<wxComboBox *
>(win)->SetBackgroundColour(col);
414 else if (
dynamic_cast<wxRadioButton *
>(win))
415 dynamic_cast<wxRadioButton *
>(win)->SetBackgroundColour(col);
417 else if (
dynamic_cast<wxScrolledWindow *
>(win)) {
418 dynamic_cast<wxScrolledWindow *
>(win)->SetBackgroundColour(col);
421 else if (
dynamic_cast<wxButton *
>(win)) {
422 dynamic_cast<wxButton *
>(win)->SetBackgroundColour(col);
429 if (win->GetChildren().GetCount() > 0) {
432 SetBackColor(w, col);
445 if (m_parent_window) {
446 int xmax = m_parent_window->GetSize().GetWidth();
447 int ymax = m_parent_window->GetParent()
450 dialog->SetSize(xmax, ymax);
456 wxColour cl = wxColour(214, 218, 222);
457 SetBackColor(dialog, cl);
460 dialog->SetPath(m_base_chart_dir);
461 dialog->SetPreferences(m_preselect_new, m_preselect_updated,
462 m_allow_bulk_update);
469 m_base_chart_dir = dialog->GetPath();
470 dialog->GetPreferences(m_preselect_new, m_preselect_updated,
471 m_allow_bulk_update);
473 if (m_dldrpanel) m_dldrpanel->SetBulkUpdate(m_allow_bulk_update);
476bool getDisplayMetrics() {
482 QAndroidJniObject activity = QAndroidJniObject::callStaticObjectMethod(
483 "org/qtproject/qt5/android/QtNative",
"activity",
484 "()Landroid/app/Activity;");
486 if (!activity.isValid()) {
491 QAndroidJniObject data =
492 activity.callObjectMethod(
"getDisplayMetrics",
"()Ljava/lang/String;");
494 wxString return_string;
495 jstring s = data.object<jstring>();
499 if (java_vm->GetEnv((
void **)&jenv, JNI_VERSION_1_6) != JNI_OK) {
502 const char *ret_string = (jenv)->GetStringUTFChars(s, NULL);
503 return_string = wxString(ret_string, wxConvUTF8);
508 return_string.Replace(_T(
","), _T(
"."));
516 double density = 1.0;
517 wxStringTokenizer tk(return_string, _T(
";"));
518 if (tk.HasMoreTokens()) {
519 wxString token = tk.GetNextToken();
520 token = tk.GetNextToken();
522 long b = ::wxGetDisplaySize().y;
523 token.ToDouble(&density);
525 token = tk.GetNextToken();
527 token = tk.GetNextToken();
528 token = tk.GetNextToken();
529 token = tk.GetNextToken();
530 token = tk.GetNextToken();
531 token = tk.GetNextToken();
532 token = tk.GetNextToken();
534 token = tk.GetNextToken();
542 double ldpi = 160. * density;
562ChartSource::ChartSource(wxString name, wxString url, wxString localdir) {
566 m_update_data.clear();
569ChartSource::~ChartSource() { m_update_data.clear(); }
571#define ID_MNU_SELALL 2001
572#define ID_MNU_DELALL 2002
573#define ID_MNU_INVSEL 2003
574#define ID_MNU_SELUPD 2004
575#define ID_MNU_SELNEW 2005
577enum { ThreadId = wxID_HIGHEST + 1 };
583 switch (evt.GetId()) {
585 CheckAllCharts(
true);
588 CheckAllCharts(
false);
591 InvertCheckAllCharts();
594 CheckUpdatedCharts(
true);
597 CheckNewCharts(
true);
602void ChartDldrPanelImpl::OnContextMenu(wxMouseEvent &event) {
605 wxPoint mouseScreen = wxGetMousePosition();
606 wxPoint mouseClient = ScreenToClient(mouseScreen);
612 wxMenuItem *item1 =
new wxMenuItem(&menu, ID_MNU_SELALL, _(
"Select all"));
616 wxMenuItem *item2 =
new wxMenuItem(&menu, ID_MNU_DELALL, _(
"Deselect all"));
621 new wxMenuItem(&menu, ID_MNU_INVSEL, _(
"Invert selection"));
625 wxMenuItem *item4 =
new wxMenuItem(&menu, ID_MNU_SELUPD, _(
"Select updated"));
629 wxMenuItem *item5 =
new wxMenuItem(&menu, ID_MNU_SELNEW, _(
"Select new"));
635 menu.Append(ID_MNU_SELALL, _(
"Select all"), wxT(
""));
636 menu.Append(ID_MNU_DELALL, _(
"Deselect all"), wxT(
""));
637 menu.Append(ID_MNU_INVSEL, _(
"Invert selection"), wxT(
""));
638 menu.Append(ID_MNU_SELUPD, _(
"Select updated"), wxT(
""));
639 menu.Append(ID_MNU_SELNEW, _(
"Select new"), wxT(
""));
643 menu.Connect(wxEVT_COMMAND_MENU_SELECTED,
644 (wxObjectEventFunction)&ChartDldrPanelImpl::OnPopupClick, NULL,
647 PopupMenu(&menu, mouseClient.x, mouseClient.y);
650void ChartDldrPanelImpl::OnShowLocalDir(wxCommandEvent &event) {
651 if (pPlugIn->m_pChartSource == NULL)
return;
653 wxExecute(wxString::Format(_T(
"xdg-open %s"),
654 pPlugIn->m_pChartSource->GetDir().c_str()));
657 wxExecute(wxString::Format(_T(
"open %s"),
658 pPlugIn->m_pChartSource->GetDir().c_str()));
661 wxExecute(wxString::Format(_T(
"explorer %s"),
662 pPlugIn->m_pChartSource->GetDir().c_str()));
666void ChartDldrPanelImpl::SetSource(
int id) {
667 pPlugIn->SetSourceId(
id);
669 m_bDeleteSource->Enable(
id >= 0);
670 m_bUpdateChartList->Enable(
id >= 0);
671 m_bEditSource->Enable(
id >= 0);
676 if (
id >= 0 &&
id < (
int)pPlugIn->m_ChartSources.size()) {
677 ::wxBeginBusyCursor();
679 std::unique_ptr<ChartSource> &cs = pPlugIn->m_ChartSources.at(
id);
680 cs->LoadUpdateData();
681 cs->UpdateLocalFiles();
682 pPlugIn->m_pChartSource = cs.get();
683 FillFromFile(cs->GetUrl(), cs->GetDir(), pPlugIn->m_preselect_new,
684 pPlugIn->m_preselect_updated);
685 wxURI url(cs->GetUrl());
686 m_chartsLabel->SetLabel(wxString::Format(
687 _(
"Charts: %s"), (cs->GetName() + _(
" from ") + url.BuildURI() +
688 _T(
" -> ") + cs->GetDir())
690 if (::wxIsBusy()) ::wxEndBusyCursor();
692 pPlugIn->m_pChartSource = NULL;
693 m_chartsLabel->SetLabel(_(
"Charts"));
697void ChartDldrPanelImpl::SelectSource(wxListEvent &event) {
698 int i = GetSelectedCatalog();
699 if (i >= 0) SetSource(i);
703void ChartDldrPanelImpl::SetBulkUpdate(
bool bulk_update) {
704 m_bUpdateAllCharts->Enable(bulk_update);
705 m_bUpdateAllCharts->Show(bulk_update);
710void ChartDldrPanelImpl::CleanForm() {
711#if defined(CHART_LIST)
714 m_scrollWinChartList->ClearBackground();
719void ChartDldrPanelImpl::FillFromFile(wxString url, wxString dir,
bool selnew,
722 wxStringTokenizer tk(url, _T(
"/"));
725 file = tk.GetNextToken();
726 }
while (tk.HasMoreTokens());
728 fn.SetFullName(file);
730 wxString path = fn.GetFullPath();
731 if (wxFileExists(path)) {
732 pPlugIn->m_pChartCatalog.LoadFromFile(path);
739#if !defined(CHART_LIST)
741 m_panelArray.clear();
742 m_scrollWinChartList->ClearBackground();
745 for (
size_t i = 0; i < pPlugIn->m_pChartCatalog.charts.size(); i++) {
750 pPlugIn->m_pChartCatalog.charts.at(i)->GetChartFilename(
true);
751 if (!pPlugIn->m_pChartSource->ExistsLocaly(
752 pPlugIn->m_pChartCatalog.charts.at(i)->number, file_)) {
755 if (selnew) bcheck =
true;
757 if (pPlugIn->m_pChartSource->IsNewerThanLocal(
758 pPlugIn->m_pChartCatalog.charts.at(i)->number, file_,
759 pPlugIn->m_pChartCatalog.charts.at(i)->GetUpdateDatetime())) {
761 status = _(
"Out of date");
762 if (selupd) bcheck =
true;
764 status = _(
"Up to date");
768 pPlugIn->m_pChartCatalog.charts.at(i)->GetUpdateDatetime().Format(
771#if defined(CHART_LIST)
772 wxVector<wxVariant> data;
773 data.push_back(wxVariant(bcheck));
774 data.push_back(wxVariant(status));
775 data.push_back(wxVariant(latest));
777 wxVariant(pPlugIn->m_pChartCatalog.charts.at(i)->GetChartTitle()));
778 getChartList()->AppendItem(data);
780 auto pC = std::make_unique<ChartPanel>(
781 m_scrollWinChartList, wxID_ANY, wxDefaultPosition, wxSize(-1, -1),
782 pPlugIn->m_pChartCatalog.charts.at(i)->GetChartTitle(), status,
783 latest,
this, bcheck);
784 pC->Connect(wxEVT_RIGHT_DOWN,
785 wxMouseEventHandler(ChartDldrPanel::OnContextMenu), NULL,
788 m_boxSizerCharts->Add(pC.get(), 0, wxEXPAND | wxLEFT | wxRIGHT, 2);
789 m_panelArray.push_back(std::move(pC));
793#if !defined(CHART_LIST)
794 m_scrollWinChartList->ClearBackground();
795 m_scrollWinChartList->FitInside();
796 m_scrollWinChartList->GetSizer()->Layout();
798 m_scrollWinChartList->ClearBackground();
799 SetChartInfo(wxString::Format(_(
"%lu charts total, %lu updated, %lu new"),
800 pPlugIn->m_pChartCatalog.charts.size(),
801 m_updatedCharts, m_newCharts));
803 SetChartInfo(wxString::Format(
804 _(
"%lu charts total, %lu updated, %lu new, %lu selected"),
805 pPlugIn->m_pChartCatalog.charts.size(), m_updatedCharts, m_newCharts,
806 GetCheckedChartCount()));
811bool ChartSource::ExistsLocaly(wxString chart_number, wxString filename) {
814 wxStringTokenizer tk(filename, _T(
"."));
815 wxString file = tk.GetNextToken().MakeLower();
817 if (!m_update_data.empty()) {
818 return m_update_data.find(std::string(chart_number.Lower().mb_str())) !=
819 m_update_data.end() ||
820 m_update_data.find(std::string(file.mb_str())) !=
823 for (
size_t i = 0; i < m_localfiles.Count(); i++) {
824 if (m_localfiles.Item(i) == file)
return true;
829bool ChartSource::IsNewerThanLocal(wxString chart_number, wxString filename,
830 wxDateTime validDate) {
831 wxStringTokenizer tk(filename, _T(
"."));
832 wxString file = tk.GetNextToken().MakeLower();
833 if (!m_update_data.empty()) {
834 if (m_update_data[std::string(chart_number.Lower().mbc_str())] <
835 validDate.GetTicks() &&
836 m_update_data[std::string(file.mbc_str())] < validDate.GetTicks())
841 bool update_candidate =
false;
843 for (
size_t i = 0; i < m_localfiles.Count(); i++) {
844 if (m_localfiles.Item(i) == file) {
845 if (validDate.IsLaterThan(m_localdt.at(i))) {
846 update_candidate =
true;
851 return update_candidate;
854int ChartDldrPanelImpl::GetSelectedCatalog() {
856 m_lbChartSources->GetNextItem(-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED);
860void ChartDldrPanelImpl::SelectCatalog(
int item) {
862 m_bDeleteSource->Enable();
863 m_bEditSource->Enable();
864 m_bUpdateChartList->Enable();
866 m_bDeleteSource->Disable();
867 m_bEditSource->Disable();
868 m_bUpdateChartList->Disable();
870 m_lbChartSources->SetItemState(item, wxLIST_STATE_SELECTED,
871 wxLIST_STATE_SELECTED);
874void ChartDldrPanelImpl::AppendCatalog(std::unique_ptr<ChartSource> &cs) {
875 long id = m_lbChartSources->GetItemCount();
876 m_lbChartSources->InsertItem(
id, cs->GetName());
877 m_lbChartSources->SetItem(
id, 1, _(
"(Please update first)"));
878 m_lbChartSources->SetItem(
id, 2, cs->GetDir());
879 wxURI url(cs->GetUrl());
880 if (url.IsReference()) {
882 this, _(
"Error, the URL to the chart source data seems wrong."),
886 wxFileName fn(url.GetPath());
887 fn.SetPath(cs->GetDir());
888 wxString path = fn.GetFullPath();
889 if (wxFileExists(path)) {
890 if (pPlugIn->m_pChartCatalog.LoadFromFile(path,
true)) {
891 m_lbChartSources->SetItem(
id, 0, pPlugIn->m_pChartCatalog.title);
892 m_lbChartSources->SetItem(
894 pPlugIn->m_pChartCatalog.GetReleaseDate().Format(
895 _T(
"%Y-%m-%d %H:%M")));
896 m_lbChartSources->SetItem(
id, 2, path);
898 m_lbChartSources->GetHandle()->resizeColumnToContents(0);
899 m_lbChartSources->GetHandle()->resizeColumnToContents(1);
900 m_lbChartSources->GetHandle()->resizeColumnToContents(2);
906void ChartDldrPanelImpl::UpdateAllCharts(wxCommandEvent &event) {
907 int failed_to_update = 0;
908 int attempted_to_update = 0;
909 if ((pPlugIn->m_preselect_new) && (pPlugIn->m_preselect_updated)) {
910 wxMessageDialog mess(
912 _(
"You have chosen to update all chart catalogs.\nThen download all "
913 "new and updated charts.\nThis may take a long time."),
914 _(
"Chart Downloader"), wxOK | wxCANCEL);
915 if (mess.ShowModal() == wxID_CANCEL)
return;
916 }
else if (pPlugIn->m_preselect_new) {
917 wxMessageDialog mess(
919 _(
"You have chosen to update all chart catalogs.\nThen download only "
920 "new (but not updated) charts.\nThis may take a long time."),
921 _(
"Chart Downloader"), wxOK | wxCANCEL);
922 if (mess.ShowModal() == wxID_CANCEL)
return;
923 }
else if (pPlugIn->m_preselect_updated) {
924 wxMessageDialog mess(
926 _(
"You have chosen to update all chart catalogs.\nThen download only "
927 "updated (but not new) charts.\nThis may take a long time."),
928 _(
"Chart Downloader"), wxOK | wxCANCEL);
929 if (mess.ShowModal() == wxID_CANCEL)
return;
934 int oldPage = m_DLoadNB->SetSelection(1);
935 for (
long chartIndex = 0; chartIndex < m_lbChartSources->GetItemCount();
937 m_lbChartSources->SetItemState(chartIndex, wxLIST_STATE_SELECTED,
938 wxLIST_STATE_SELECTED);
939 if (cancelled)
break;
940 UpdateChartList(event);
942 attempted_to_update += m_downloading;
943 failed_to_update += m_failed_downloads;
945 wxLogMessage(wxString::Format(
946 _T(
"chartdldr_pi::UpdateAllCharts() downloaded %d out of %d charts."),
947 attempted_to_update - failed_to_update, attempted_to_update));
948 if (failed_to_update > 0)
951 wxString::Format(_(
"%d out of %d charts failed to download.\nCheck the "
952 "list, verify there is a working Internet "
953 "connection and repeat the operation if needed."),
954 failed_to_update, attempted_to_update),
955 _(
"Chart Downloader"), wxOK | wxICON_ERROR);
960 m_DLoadNB->SetSelection(oldPage);
963void ChartDldrPanelImpl::UpdateChartList(wxCommandEvent &event) {
965 if (!m_lbChartSources->GetSelectedItemCount())
return;
966 std::unique_ptr<ChartSource> &cs =
967 pPlugIn->m_ChartSources.at(GetSelectedCatalog());
968 wxURI url(cs->GetUrl());
969 if (url.IsReference()) {
971 this, _(
"Error, the URL to the chart source data seems wrong."),
976 wxStringTokenizer tk(url.GetPath(), _T(
"/"));
979 file = tk.GetNextToken();
980 }
while (tk.HasMoreTokens());
982 fn.SetFullName(file);
983 fn.SetPath(cs->GetDir());
984 if (!wxDirExists(cs->GetDir())) {
985 if (!wxFileName::Mkdir(cs->GetDir(), 0755, wxPATH_MKDIR_FULL)) {
988 wxString::Format(_(
"Directory %s can't be created."),
989 cs->GetDir().c_str()),
990 _(
"Chart Downloader"));
998 wxString file_URI = _T(
"file://") + fn.GetFullPath();
1010 cs->GetUrl(), file_URI, _(
"Downloading file"),
1011 _(
"Reading Headers: ") + url.BuildURI(), wxNullBitmap,
this,
1020 wxFileName tfn = wxFileName::CreateTempFileName(fn.GetFullPath());
1021 wxString file_URI = tfn.GetFullPath();
1024 cs->GetUrl(), file_URI, _(
"Downloading file"),
1025 _(
"Reading Headers: ") + url.BuildURI(), wxNullBitmap,
this,
1032 bok = wxCopyFile(tfn.GetFullPath(), fn.GetFullPath());
1033 wxRemoveFile(tfn.GetFullPath());
1042 long id = GetSelectedCatalog();
1045 m_lbChartSources->SetItem(
id, 0, pPlugIn->m_pChartCatalog.title);
1046 m_lbChartSources->SetItem(
1048 pPlugIn->m_pChartCatalog.GetReleaseDate().Format(
1049 _T(
"%Y-%m-%d %H:%M")));
1050 m_lbChartSources->SetItem(
id, 2, cs->GetDir());
1055 wxString::Format(_(
"Failed to Find New Catalog: %s "),
1056 url.BuildURI().c_str()),
1057 _(
"Chart Downloader"), wxOK | wxICON_ERROR);
1063 wxString::Format(_(
"Failed to Download Catalog: %s \nVerify there is "
1064 "a working Internet connection."),
1065 url.BuildURI().c_str()),
1066 _(
"Chart Downloader"), wxOK | wxICON_ERROR);
1089void ChartSource::GetLocalFiles() {
1090 if (!UpdateDataExists() || m_update_data.empty()) {
1091 wxArrayString *allFiles =
new wxArrayString;
1092 if (wxDirExists(GetDir())) wxDir::GetAllFiles(GetDir(), allFiles);
1094 m_localfiles.Clear();
1095 wxDateTime ct, mt, at;
1097 for (
size_t i = 0; i < allFiles->Count(); i++) {
1098 wxFileName fn(allFiles->Item(i));
1099 name = fn.GetFullName().Lower();
1103 if (!ExistsLocaly(wxEmptyString, name)) {
1104 fn.GetTimes(&at, &mt, &ct);
1105 m_localdt.push_back(mt);
1106 m_localfiles.Add(fn.GetName().Lower());
1108 wxStringTokenizer tk(name, _T(
"."));
1109 wxString file = tk.GetNextToken().MakeLower();
1110 m_update_data[std::string(file.mbc_str())] = mt.GetTicks();
1121bool ChartSource::UpdateDataExists() {
1122 return wxFileExists(GetDir() + wxFileName::GetPathSeparator() +
1123 _T(UPDATE_DATA_FILENAME));
1126void ChartSource::LoadUpdateData() {
1127 m_update_data.clear();
1129 GetDir() + wxFileName::GetPathSeparator() + _T(UPDATE_DATA_FILENAME);
1131 if (!wxFileExists(fn))
return;
1133 std::ifstream infile(fn.mb_str());
1138 while (infile >> key >> value) m_update_data[key] = value;
1143void ChartSource::SaveUpdateData() {
1145 fn = GetDir() + wxFileName::GetPathSeparator() + _T(UPDATE_DATA_FILENAME);
1148 fn = AndroidGetCacheDir() + wxFileName::GetPathSeparator() +
1149 _T(UPDATE_DATA_FILENAME);
1152 std::ofstream outfile(fn.mb_str());
1153 if (!outfile.is_open())
return;
1155 std::map<std::string, time_t>::iterator iter;
1156 for (iter = m_update_data.begin(); iter != m_update_data.end(); ++iter) {
1157 if (iter->first.find(
" ") == std::string::npos)
1158 if (!iter->first.empty())
1159 outfile << iter->first <<
" " << iter->second <<
"\n";
1165 AndroidSecureCopyFile(
1166 fn, GetDir() + wxFileName::GetPathSeparator() + _T(UPDATE_DATA_FILENAME));
1170void ChartSource::ChartUpdated(wxString chart_number, time_t timestamp) {
1171 m_update_data[std::string(chart_number.Lower().mb_str())] = timestamp;
1175bool ChartDldrPanelImpl::DownloadChart(wxString url, wxString file,
1180void ChartDldrPanelImpl::DisableForDownload(
bool enabled) {
1181 m_bAddSource->Enable(enabled);
1182 m_bDeleteSource->Enable(enabled);
1183 m_bEditSource->Enable(enabled);
1184 m_bUpdateAllCharts->Enable(enabled);
1185 m_bUpdateChartList->Enable(enabled);
1186 m_lbChartSources->Enable(enabled);
1187#if defined(CHART_LIST)
1188 m_bSelectNew->Enable(enabled);
1189 m_bSelectUpdated->Enable(enabled);
1190 m_bSelectAll->Enable(enabled);
1194void ChartDldrPanelImpl::OnDownloadCharts(wxCommandEvent &event) {
1195 if (DownloadIsCancel) {
1201#if defined(CHART_LIST)
1202void ChartDldrPanelImpl::OnSelectChartItem(wxCommandEvent &event) {
1204 SetChartInfo(wxString::Format(
1205 _(
"%lu charts total, %lu updated, %lu new, %lu selected"),
1206 pPlugIn->m_pChartCatalog.charts.size(), m_updatedCharts, m_newCharts,
1207 GetCheckedChartCount()));
1212#if defined(CHART_LIST)
1213void ChartDldrPanelImpl::OnSelectNewCharts(wxCommandEvent &event) {
1214 CheckNewCharts(
true);
1218#if defined(CHART_LIST)
1219void ChartDldrPanelImpl::OnSelectUpdatedCharts(wxCommandEvent &event) {
1220 CheckUpdatedCharts(
true);
1224#if defined(CHART_LIST)
1225void ChartDldrPanelImpl::OnSelectAllCharts(wxCommandEvent &event) {
1226 if (m_bSelectAll->GetLabel() == _(
"Select All")) {
1227 CheckAllCharts(
true);
1228 m_bSelectAll->SetLabel(_(
"Select None"));
1229 m_bSelectAll->SetToolTip(_(
"De-select all charts in the list."));
1231 CheckAllCharts(
false);
1232 m_bSelectAll->SetLabel(_(
"Select All"));
1233 m_bSelectAll->SetToolTip(_(
"Select all charts in the list."));
1238int ChartDldrPanelImpl::GetChartCount() {
1239#if defined(CHART_LIST)
1240 return getChartList()->GetItemCount();
1242 return m_panelArray.size();
1246int ChartDldrPanelImpl::GetCheckedChartCount() {
1247#if defined(CHART_LIST)
1249 int chartCnt = GetChartCount();
1250 for (
int i = 0; i < chartCnt; i++)
1251 if (isChartChecked(i)) cnt++;
1254 for (
int i = 0; i < GetChartCount(); i++) {
1255 if (m_panelArray.at(i)->GetCB()->IsChecked()) cnt++;
1261bool ChartDldrPanelImpl::isChartChecked(
int i) {
1262 wxASSERT_MSG(i >= 0,
1263 wxT(
"This function should be called with non-negative index."));
1264 if (i <= GetChartCount())
1265#if defined(CHART_LIST)
1266 return getChartList()->GetToggleValue(i, 0);
1268 return m_panelArray.at(i)->GetCB()->IsChecked();
1274void ChartDldrPanelImpl::CheckAllCharts(
bool value) {
1275#if defined(CHART_LIST)
1279 for (
int i = 0; i < GetChartCount(); i++) {
1280#if defined(CHART_LIST)
1281 getChartList()->SetToggleValue(value, i, 0);
1283 m_panelArray.at(i)->GetCB()->SetValue(value);
1286#if defined(CHART_LIST)
1287 SetChartInfo(wxString::Format(
1288 _(
"%lu charts total, %lu updated, %lu new, %lu selected"),
1289 pPlugIn->m_pChartCatalog.charts.size(), m_updatedCharts, m_newCharts,
1290 GetCheckedChartCount()));
1291 m_bInfoHold =
false;
1295void ChartDldrPanelImpl::CheckNewCharts(
bool value) {
1296 for (
int i = 0; i < GetChartCount(); i++) {
1297#if defined(CHART_LIST)
1298 if (isNew(i)) getChartList()->SetToggleValue(
true, i, 0);
1300 if (m_panelArray.at(i)->isNew())
1301 m_panelArray.at(i)->GetCB()->SetValue(value);
1304#if defined(CHART_LIST)
1305 SetChartInfo(wxString::Format(
1306 _(
"%lu charts total, %lu updated, %lu new, %lu selected"),
1307 pPlugIn->m_pChartCatalog.charts.size(), m_updatedCharts, m_newCharts,
1308 GetCheckedChartCount()));
1312void ChartDldrPanelImpl::CheckUpdatedCharts(
bool value) {
1313 for (
int i = 0; i < GetChartCount(); i++) {
1314#if defined(CHART_LIST)
1315 if (isUpdated(i)) getChartList()->SetToggleValue(value, i, 0);
1317 if (m_panelArray.at(i)->isUpdated())
1318 m_panelArray.at(i)->GetCB()->SetValue(value);
1321#if defined(CHART_LIST)
1322 SetChartInfo(wxString::Format(
1323 _(
"%lu charts total, %lu updated, %lu new, %lu selected"),
1324 pPlugIn->m_pChartCatalog.charts.size(), m_updatedCharts, m_newCharts,
1325 GetCheckedChartCount()));
1329void ChartDldrPanelImpl::InvertCheckAllCharts() {
1330#if defined(CHART_LIST)
1333 for (
int i = 0; i < GetChartCount(); i++)
1334#
if defined(CHART_LIST)
1335 getChartList()->SetToggleValue(!isChartChecked(i), i, 0);
1337 m_panelArray.at(i)->GetCB()->SetValue(!isChartChecked(i));
1339#if defined(CHART_LIST)
1340 m_bInfoHold =
false;
1341 SetChartInfo(wxString::Format(
1342 _(
"%lu charts total, %lu updated, %lu new, %lu selected"),
1343 pPlugIn->m_pChartCatalog.charts.size(), m_updatedCharts, m_newCharts,
1344 GetCheckedChartCount()));
1349 if (!m_bconnected) {
1351 wxEVT_DOWNLOAD_EVENT,
1352 (wxObjectEventFunction)(wxEventFunction)&ChartDldrPanelImpl::onDLEvent);
1353 m_bconnected =
true;
1356 if (!GetCheckedChartCount() && !updatingAll) {
1360 std::unique_ptr<ChartSource> &cs =
1361 pPlugIn->m_ChartSources.at(GetSelectedCatalog());
1364 to_download = GetCheckedChartCount();
1366 m_failed_downloads = 0;
1367 DisableForDownload(
false);
1369 m_bDnldCharts->SetLabel(_(
"Abort download"));
1370 DownloadIsCancel =
true;
1372 wxFileName downloaded_p;
1375 for (
int i = 0; i < GetChartCount() && to_download; i++) {
1377 if (cancelled)
break;
1379 if (!isChartChecked(i))
continue;
1380 m_bTransferComplete =
false;
1381 m_bTransferSuccess =
true;
1383 m_transferredsize = 0;
1385 if (pPlugIn->m_pChartCatalog.charts.at(index)->NeedsManualDownload()) {
1390 _(
"The selected chart '%s' can't be downloaded automatically, do you want me to open a browser window and download them manually?\n\n \
1391After downloading the charts, please extract them to %s"),
1392 pPlugIn->m_pChartCatalog.charts.at(index)->title.c_str(),
1393 pPlugIn->m_pChartSource->GetDir().c_str()),
1394 _(
"Chart Downloader"), wxYES_NO | wxCENTRE | wxICON_QUESTION)) {
1395 wxLaunchDefaultBrowser(
1396 pPlugIn->m_pChartCatalog.charts.at(index)->GetManualDownloadUrl());
1402 wxURI url(pPlugIn->m_pChartCatalog.charts.at(index)->GetDownloadLocation());
1403 if (url.IsReference()) {
1407 _(
"Error, the URL to the chart (%s) data seems wrong."),
1408 url.BuildURI().c_str()),
1416 pPlugIn->m_pChartCatalog.charts.at(index)->GetChartFilename();
1418 fn.SetFullName(file);
1419 fn.SetPath(cs->GetDir());
1420 wxString path = fn.GetFullPath();
1421 if (wxFileExists(path)) wxRemoveFile(path);
1422 wxString title = pPlugIn->m_pChartCatalog.charts.at(index)->GetChartTitle();
1426 wxString file_path = _T(
"file://") + fn.GetFullPath();
1428 wxString file_path = fn.GetFullPath();
1435 if (pPlugIn->ProcessFile(
1436 downloaded_p.GetFullPath(), downloaded_p.GetPath(),
true,
1437 pPlugIn->m_pChartCatalog.charts.at(idx)->GetUpdateDatetime())) {
1438 cs->ChartUpdated(pPlugIn->m_pChartCatalog.charts.at(idx)->number,
1439 pPlugIn->m_pChartCatalog.charts.at(idx)
1440 ->GetUpdateDatetime()
1443 m_failed_downloads++;
1448 while (!m_bTransferComplete && m_bTransferSuccess && !cancelled) {
1449 if (m_failed_downloads)
1450 SetChartInfo(wxString::Format(
1451 _(
"Downloading chart %u of %u, %u downloads failed (%s / %s)"),
1452 m_downloading, to_download, m_failed_downloads,
1453 FormatBytes(m_transferredsize), FormatBytes(m_totalsize)));
1455 SetChartInfo(wxString::Format(_(
"Downloading chart %u of %u (%s / %s)"),
1456 m_downloading, to_download,
1457 FormatBytes(m_transferredsize),
1458 FormatBytes(m_totalsize)));
1463 wxTheApp->ProcessPendingEvents();
1473 if (m_bTransferSuccess && !cancelled) {
1475 downloaded_p = path;
1478 if (wxFileExists(path)) wxRemoveFile(path);
1479 m_failed_downloads++;
1483 if (pPlugIn->ProcessFile(
1484 downloaded_p.GetFullPath(), downloaded_p.GetPath(),
true,
1485 pPlugIn->m_pChartCatalog.charts.at(idx)->GetUpdateDatetime())) {
1486 cs->ChartUpdated(pPlugIn->m_pChartCatalog.charts.at(idx)->number,
1487 pPlugIn->m_pChartCatalog.charts.at(idx)
1488 ->GetUpdateDatetime()
1491 m_failed_downloads++;
1494 DisableForDownload(
true);
1495 m_bDnldCharts->SetLabel(_(
"Download selected charts"));
1496 DownloadIsCancel =
false;
1497 SetSource(GetSelectedCatalog());
1498 if (m_failed_downloads > 0 && !updatingAll && !cancelled)
1501 wxString::Format(_(
"%d out of %d charts failed to download.\nCheck the "
1502 "list, verify there is a working Internet "
1503 "connection and repeat the operation if needed."),
1504 m_failed_downloads, m_downloading),
1505 _(
"Chart Downloader"), wxOK | wxICON_ERROR);
1509 _(
"Chart Downloader"), wxOK | wxICON_INFORMATION);
1511 if ((m_downloading - m_failed_downloads > 0) && !updatingAll)
1515ChartDldrPanelImpl::~ChartDldrPanelImpl() {
1517 wxEVT_DOWNLOAD_EVENT,
1518 (wxObjectEventFunction)(wxEventFunction)&ChartDldrPanelImpl::onDLEvent);
1519 m_bconnected =
false;
1525#if defined(CHART_LIST)
1530ChartDldrPanelImpl::ChartDldrPanelImpl(
chartdldr_pi *plugin, wxWindow *parent,
1531 wxWindowID
id,
const wxPoint &pos,
1532 const wxSize &size,
long style)
1534 m_bDeleteSource->Disable();
1535 m_bUpdateChartList->Disable();
1536 m_bEditSource->Disable();
1537 m_lbChartSources->InsertColumn(0, _(
"Catalog"), wxLIST_FORMAT_LEFT,
1538 CATALOGS_NAME_WIDTH);
1539 m_lbChartSources->InsertColumn(1, _(
"Released"), wxLIST_FORMAT_LEFT,
1540 CATALOGS_DATE_WIDTH);
1541 m_lbChartSources->InsertColumn(2, _(
"Local path"), wxLIST_FORMAT_LEFT,
1542 CATALOGS_PATH_WIDTH);
1543 m_lbChartSources->Enable();
1544 m_bInfoHold =
false;
1548 updatingAll =
false;
1550 m_populated =
false;
1551 DownloadIsCancel =
false;
1552 m_failed_downloads = 0;
1553 SetChartInfo(wxEmptyString);
1554 m_bTransferComplete =
true;
1555 m_bTransferSuccess =
true;
1558 wxEVT_DOWNLOAD_EVENT,
1559 (wxObjectEventFunction)(wxEventFunction)&ChartDldrPanelImpl::onDLEvent);
1560 m_bconnected =
true;
1562 for (
size_t i = 0; i < pPlugIn->m_ChartSources.size(); i++) {
1563 AppendCatalog(pPlugIn->m_ChartSources.at(i));
1568void ChartDldrPanelImpl::OnPaint(wxPaintEvent &event) {
1571 for (
size_t i = 0; i < pPlugIn->m_ChartSources.size(); i++) {
1572 AppendCatalog(pPlugIn->m_ChartSources.at(i));
1577 m_lbChartSources->Refresh(
true);
1582void ChartDldrPanelImpl::DeleteSource(wxCommandEvent &event) {
1583 if (!m_lbChartSources->GetSelectedItemCount())
return;
1586 _(
"Do you really want to remove the chart source?\nThe "
1587 "local chart files will not be removed,\nbut you will "
1588 "not be able to update the charts anymore."),
1589 _(
"Chart Downloader"), wxYES_NO | wxCENTRE))
1591 int ToBeRemoved = GetSelectedCatalog();
1592 m_lbChartSources->SetItemState(ToBeRemoved, 0,
1593 wxLIST_STATE_SELECTED | wxLIST_STATE_FOCUSED);
1594 pPlugIn->m_ChartSources.erase(pPlugIn->m_ChartSources.begin() + ToBeRemoved);
1595 m_lbChartSources->DeleteItem(ToBeRemoved);
1597 pPlugIn->SetSourceId(-1);
1599 pPlugIn->SaveConfig();
1603void ChartDldrPanelImpl::AddSource(wxCommandEvent &event) {
1605 dialog->SetBasePath(pPlugIn->GetBaseChartDir());
1607 wxSize sz = GetParent()
1610 dialog->SetSize(sz.GetWidth(), sz.GetHeight());
1614 androidDisableRotation();
1617 if (dialog->ShowModal() == wxID_OK) {
1618 std::unique_ptr<ChartSource> cs =
1619 std::make_unique<ChartSource>(dialog->m_tSourceName->GetValue(),
1620 dialog->m_tChartSourceUrl->GetValue(),
1621 dialog->m_tcChartDirectory->GetValue());
1624 bool covered =
false;
1632 wxString dir = cs->GetDir();
1636 long itemSelectedNow = GetSelectedCatalog();
1637 m_lbChartSources->SetItemState(itemSelectedNow, 0, wxLIST_STATE_SELECTED);
1639 SelectCatalog(m_lbChartSources->GetItemCount() - 1);
1640 pPlugIn->m_ChartSources.push_back(std::move(cs));
1641 pPlugIn->SaveConfig();
1644 androidEnableRotation();
1650void ChartDldrPanelImpl::DoEditSource() {
1651 if (!m_lbChartSources->GetSelectedItemCount())
return;
1652 int cat = GetSelectedCatalog();
1654 dialog->SetBasePath(pPlugIn->GetBaseChartDir());
1655 dialog->SetSourceEdit(pPlugIn->m_ChartSources.at(cat));
1656 dialog->SetTitle(_(
"Edit Chart Source"));
1658 dialog->ShowModal();
1659 int retcode = dialog->GetReturnCode();
1661 if (retcode == wxID_OK) {
1662 pPlugIn->m_ChartSources.at(cat)->SetName(
1663 dialog->m_tSourceName->GetValue());
1664 pPlugIn->m_ChartSources.at(cat)->SetUrl(
1665 dialog->m_tChartSourceUrl->GetValue());
1666 pPlugIn->m_ChartSources.at(cat)->SetDir(
1667 dialog->m_tcChartDirectory->GetValue());
1669 m_lbChartSources->SetItem(cat, 0,
1670 pPlugIn->m_ChartSources.at(cat)->GetName());
1671 m_lbChartSources->SetItem(cat, 1, _(
"(Please update first)"));
1672 m_lbChartSources->SetItem(cat, 2,
1673 pPlugIn->m_ChartSources.at(cat)->GetDir());
1674 wxURI url(pPlugIn->m_ChartSources.at(cat)->GetUrl());
1675 wxFileName fn(url.GetPath());
1676 fn.SetPath(pPlugIn->m_ChartSources.at(cat)->GetDir());
1677 wxString path = fn.GetFullPath();
1678 if (wxFileExists(path)) {
1679 if (pPlugIn->m_pChartCatalog.LoadFromFile(path,
true)) {
1680 m_lbChartSources->SetItem(cat, 0, pPlugIn->m_pChartCatalog.title);
1681 m_lbChartSources->SetItem(
1683 pPlugIn->m_pChartCatalog.GetReleaseDate().Format(
1684 _T(
"%Y-%m-%d %H:%M")));
1685 m_lbChartSources->SetItem(cat, 2, path);
1688 bool covered =
false;
1690 if (pPlugIn->m_ChartSources.at(cat)->GetDir().StartsWith(
1700 _(
"Path %s seems not to be covered by your configured Chart "
1701 "Directories.\nTo see the charts you have to adjust the "
1702 "configuration on the 'Chart Files' tab."),
1703 pPlugIn->m_ChartSources.at(cat)->GetDir().c_str()),
1704 _(
"Chart Downloader"));
1706 pPlugIn->SaveConfig();
1712void ChartDldrPanelImpl::EditSource(wxCommandEvent &event) {
1717void ChartDldrPanelImpl::OnLeftDClick(wxMouseEvent &event) {
1722bool chartdldr_pi::ProcessFile(
const wxString &aFile,
1723 const wxString &aTargetDir,
bool aStripPath,
1724 wxDateTime aMTime) {
1725 if (aFile.Lower().EndsWith(_T(
"zip")))
1727 bool ret = ExtractZipFiles(aFile, aTargetDir, aStripPath, aMTime,
false);
1729 wxRemoveFile(aFile);
1731 wxLogError(_T(
"chartdldr_pi: Unable to extract: ") + aFile);
1734#ifdef DLDR_USE_LIBARCHIVE
1735 else if (aFile.Lower().EndsWith(_T(
"rar"))) {
1736#ifdef CHARTDLDR_RAR_UNARR
1737 bool ret = ExtractUnarrFiles(aFile, aTargetDir, aStripPath, aMTime,
false);
1740 ExtractLibArchiveFiles(aFile, aTargetDir, aStripPath, aMTime,
false);
1743 wxRemoveFile(aFile);
1745 wxLogError(_T(
"chartdldr_pi: Unable to extract: ") + aFile);
1747 }
else if (aFile.Lower().EndsWith(_T(
"tar")) ||
1748 aFile.Lower().EndsWith(_T(
"gz")) ||
1749 aFile.Lower().EndsWith(_T(
"bz2")) ||
1750 aFile.Lower().EndsWith(_T(
"lzma")) ||
1751 aFile.Lower().EndsWith(_T(
"7z")) ||
1752 aFile.Lower().EndsWith(_T(
"xz"))) {
1754 ExtractLibArchiveFiles(aFile, aTargetDir, aStripPath, aMTime,
false);
1756 wxRemoveFile(aFile);
1758 wxLogError(_T(
"chartdldr_pi: Unable to extract: ") + aFile);
1762 else if (aFile.Lower().EndsWith(_T(
"rar")) ||
1763 aFile.Lower().EndsWith(_T(
"tar"))
1765 || aFile.Lower().EndsWith(_T(
"bz2"))
1768 || aFile.Lower().EndsWith(_T(
"gz"))
1771 || aFile.Lower().EndsWith(
1776 bool ret = ExtractUnarrFiles(aFile, aTargetDir, aStripPath, aMTime,
false);
1778 wxRemoveFile(aFile);
1780 wxLogError(_T(
"chartdldr_pi: Unable to extract: ") + aFile);
1786 else if (aFile.Lower().EndsWith(_T(
"tar")) ||
1787 aFile.Lower().EndsWith(_T(
"gz")) ||
1788 aFile.Lower().EndsWith(_T(
"bz2")) ||
1789 aFile.Lower().EndsWith(_T(
"lzma")) ||
1790 aFile.Lower().EndsWith(_T(
"7z")) ||
1791 aFile.Lower().EndsWith(_T(
"xz"))) {
1793 if (aStripPath) nStrip = 1;
1795 if (m_dldrpanel) m_dldrpanel->SetChartInfo(_(
"Installing charts."));
1797 androidShowBusyIcon();
1798 bool ret = AndroidUnzip(aFile, aTargetDir, nStrip,
true);
1799 androidHideBusyIcon();
1807 wxFileName fn(aFile);
1808 if (fn.GetPath() != aTargetDir)
1810 if (!wxDirExists(aTargetDir)) {
1811 if (wxFileName::Mkdir(aTargetDir, 0755, wxPATH_MKDIR_FULL)) {
1812 if (!wxRenameFile(aFile, aTargetDir))
return false;
1817 wxString name = fn.GetFullName();
1819 fn.Assign(aTargetDir, name);
1820 fn.SetTimes(&aMTime, &aMTime, &aMTime);
1825#ifdef DLDR_USE_LIBARCHIVE
1827static int copy_data(
struct archive *ar,
struct archive *aw) {
1831 __LA_INT64_T offset;
1834 r = archive_read_data_block(ar, &buff, &size, &offset);
1835 if (r == ARCHIVE_EOF)
return (ARCHIVE_OK);
1836 if (r < ARCHIVE_OK)
return (r);
1837 r = archive_write_data_block(aw, buff, size, offset);
1838 if (r < ARCHIVE_OK) {
1840 wxLogError(wxString::Format(
"Chartdldr_pi: LibArchive error: %s",
1841 archive_error_string(aw)));
1848bool chartdldr_pi::ExtractLibArchiveFiles(
const wxString &aArchiveFile,
1849 const wxString &aTargetDir,
1850 bool aStripPath, wxDateTime aMTime,
1851 bool aRemoveArchive) {
1854 struct archive *ext;
1855 struct archive_entry *entry;
1860 flags = ARCHIVE_EXTRACT_TIME;
1867 a = archive_read_new();
1868 archive_read_support_format_all(a);
1869 archive_read_support_filter_all(a);
1870 archive_read_support_compression_all(a);
1871 ext = archive_write_disk_new();
1872 archive_write_disk_set_options(ext, flags);
1873 archive_write_disk_set_standard_lookup(ext);
1874 if ((r = archive_read_open_filename(a, aArchiveFile.c_str(), 10240))) {
1878 r = archive_read_next_header(a, &entry);
1879 if (r == ARCHIVE_EOF)
break;
1882 wxLogError(wxString::Format(
"Chartdldr_pi: LibArchive error: %s",
1883 archive_error_string(a)));
1884 if (r < ARCHIVE_WARN)
return false;
1886 const char *currentFile = archive_entry_pathname(entry);
1887 std::string fullOutputPath = currentFile;
1888 size_t sep = fullOutputPath.find_last_of(
"\\/");
1889 if (sep != std::string::npos)
1891 fullOutputPath.substr(sep + 1, fullOutputPath.size() - sep - 1);
1892 archive_entry_set_pathname(entry, fullOutputPath.c_str());
1894 if (aTargetDir != wxEmptyString) {
1895 const char *currentFile = archive_entry_pathname(entry);
1896 wxString entryName = wxString::FromUTF8(currentFile);
1897 wxString fullOutputPath;
1900 if (!IsPathInsideDir(aTargetDir, entryName, fullOutputPath)) {
1902 _T(
"Skipping archive entry with path traversal attempt: ") +
1906 archive_entry_set_pathname(entry, fullOutputPath.ToUTF8().data());
1908 r = archive_write_header(ext, entry);
1911 wxLogError(wxString::Format(
"Chartdldr_pi: LibArchive error: %s",
1912 archive_error_string(ext)));
1913 else if (archive_entry_size(entry) > 0) {
1914 r = copy_data(a, ext);
1917 wxLogError(wxString::Format(
"Chartdldr_pi: LibArchive error: %s",
1918 archive_error_string(ext)));
1919 if (r < ARCHIVE_WARN)
return false;
1921 r = archive_write_finish_entry(ext);
1925 wxLogError(wxString::Format(
"Chartdldr_pi: LibArchive error: %s",
1926 archive_error_string(ext)));
1927 if (r < ARCHIVE_WARN)
return false;
1929 archive_read_close(a);
1931 archive_write_close(ext);
1934 if (aRemoveArchive) wxRemoveFile(aArchiveFile);
1945#if defined(CHARTDLDR_RAR_UNARR) || !defined(DLDR_USE_LIBARCHIVE)
1946ar_archive *ar_open_any_archive(ar_stream *stream,
const char *fileext) {
1947 ar_archive *ar = ar_open_rar_archive(stream);
1950 ar_open_zip_archive(stream, fileext && (strcmp(fileext,
".xps") == 0 ||
1951 strcmp(fileext,
".epub") == 0));
1952 if (!ar) ar = ar_open_7z_archive(stream);
1953 if (!ar) ar = ar_open_tar_archive(stream);
1957bool chartdldr_pi::ExtractUnarrFiles(
const wxString &aRarFile,
1958 const wxString &aTargetDir,
1959 bool aStripPath, wxDateTime aMTime,
1961 ar_stream *stream = NULL;
1962 ar_archive *ar = NULL;
1963 int entry_count = 1;
1964 int entry_skips = 0;
1968 stream = ar_open_file(aRarFile.c_str());
1970 wxLogError(_T(
"Can not open file '") + aRarFile + _T(
"'."));
1971 ar_close_archive(ar);
1975 ar = ar_open_any_archive(stream, strrchr(aRarFile.c_str(),
'.'));
1977 wxLogError(_T(
"Can not open archive '") + aRarFile + _T(
"'."));
1978 ar_close_archive(ar);
1982 while (ar_parse_entry(ar)) {
1983 size_t size = ar_entry_get_size(ar);
1984 wxString name = ar_entry_get_name(ar);
1985 wxString originalName = name;
1987 wxFileName fn(name);
1992 if (fn.GetDirCount() > 0) {
1994 name = fn.GetFullPath();
2000 if (!IsPathInsideDir(aTargetDir, name, fullPath)) {
2001 wxLogWarning(_T(
"Skipping archive entry with path traversal attempt: ") +
2007 wxFileName fn(name);
2008 if (!fn.DirExists()) {
2009 if (!wxFileName::Mkdir(fn.GetPath())) {
2010 wxLogError(_T(
"Can not create directory '") + fn.GetPath() + _T(
"'."));
2015 wxFileOutputStream file(name);
2017 wxLogError(_T(
"Can not create file '") + name + _T(
"'."));
2022 unsigned char buffer[1024];
2023 size_t count = size <
sizeof(buffer) ? size : sizeof(buffer);
2024 if (!ar_entry_uncompress(ar, buffer, count))
break;
2025 file.Write(buffer, count);
2029 fn.SetTimes(&aMTime, &aMTime, &aMTime);
2031 wxLogError(
"Warning: Failed to uncompress... skipping");
2036 if (!ar_at_eof(ar)) {
2037 wxLogError(
"Error: Failed to parse entry %d!", entry_count);
2040 ar_close_archive(ar);
2043 if (aRemoveRar) wxRemoveFile(aRarFile);
2048 setlocale(LC_NUMERIC,
"C");
2055bool chartdldr_pi::ExtractZipFiles(
const wxString &aZipFile,
2056 const wxString &aTargetDir,
bool aStripPath,
2057 wxDateTime aMTime,
bool aRemoveZip) {
2062 if (aStripPath) nStrip = 1;
2064 ret = AndroidUnzip(aZipFile, aTargetDir, nStrip,
true);
2066 std::unique_ptr<wxZipEntry> entry(
new wxZipEntry());
2069 wxLogMessage(_T(
"chartdldr_pi: Going to extract '") + aZipFile + _T(
"'."));
2070 wxFileInputStream in(aZipFile);
2073 wxLogMessage(_T(
"Can not open file '") + aZipFile + _T(
"'."));
2077 wxZipInputStream zip(in);
2080 while (entry.reset(zip.GetNextEntry()), entry.get() != NULL) {
2082 wxString name = entry->GetName();
2085 wxFileName fn(name);
2090 if (fn.GetDirCount() > 0) fn.RemoveDir(0);
2091 name = fn.GetFullPath();
2095 if (!IsPathInsideDir(aTargetDir, name, fullPath)) {
2096 wxLogWarning(_T(
"Skipping zip entry with path traversal attempt: ") +
2103 if (entry->IsDir()) {
2104 int perm = entry->GetMode();
2105 if (!wxFileName::Mkdir(name, perm, wxPATH_MKDIR_FULL)) {
2106 wxLogMessage(_T(
"Can not create directory '") + name + _T(
"'."));
2111 if (!zip.OpenEntry(*entry.get())) {
2112 wxLogMessage(_T(
"Can not open zip entry '") + entry->GetName() +
2117 if (!zip.CanRead()) {
2118 wxLogMessage(_T(
"Can not read zip entry '") + entry->GetName() +
2124 wxFileName fn(name);
2125 if (!fn.DirExists()) {
2126 if (!wxFileName::Mkdir(fn.GetPath())) {
2127 wxLogMessage(_T(
"Can not create directory '") + fn.GetPath() +
2134 wxFileOutputStream file(name);
2137 wxLogMessage(_T(
"Can not create file '") + name + _T(
"'."));
2142 fn.SetTimes(&aMTime, &aMTime, &aMTime);
2149 if (aRemoveZip) wxRemoveFile(aZipFile);
2155ChartDldrGuiAddSourceDlg::ChartDldrGuiAddSourceDlg(wxWindow *parent)
2159 fn.AppendDir(_T(
"plugins"));
2160 fn.AppendDir(_T(
"chartdldr_pi"));
2161 fn.AppendDir(_T(
"data"));
2170 p_buttonIconList =
new wxImageList(w, h);
2172 fn.SetFullName(_T(
"button_right.png"));
2173 wxImage im1(fn.GetFullPath(), wxBITMAP_TYPE_PNG);
2174 im1.Rescale(w, h, wxIMAGE_QUALITY_HIGH);
2175 p_buttonIconList->Add(im1);
2177 fn.SetFullName(_T(
"button_right.png"));
2178 wxImage im2(fn.GetFullPath(), wxBITMAP_TYPE_PNG);
2179 im2.Rescale(w, h, wxIMAGE_QUALITY_HIGH);
2180 p_buttonIconList->Add(im2);
2182 fn.SetFullName(_T(
"button_down.png"));
2183 wxImage im3(fn.GetFullPath(), wxBITMAP_TYPE_PNG);
2184 im3.Rescale(w, h, wxIMAGE_QUALITY_HIGH);
2185 p_buttonIconList->Add(im3);
2187 fn.SetFullName(_T(
"button_down.png"));
2188 wxImage im4(fn.GetFullPath(), wxBITMAP_TYPE_PNG);
2189 im4.Rescale(w, h, wxIMAGE_QUALITY_HIGH);
2190 p_buttonIconList->Add(im4);
2192 m_treeCtrlPredefSrcs->AssignButtonsImageList(p_buttonIconList);
2194 p_iconList =
new wxImageList(w, h);
2196 fn.SetFullName(_T(
"folder.png"));
2197 wxImage ima(fn.GetFullPath(), wxBITMAP_TYPE_PNG);
2198 ima.Rescale(w, h, wxIMAGE_QUALITY_HIGH);
2199 p_iconList->Add(ima);
2201 fn.SetFullName(_T(
"file.png"));
2202 wxImage imb(fn.GetFullPath(), wxBITMAP_TYPE_PNG);
2203 imb.Rescale(w, h, wxIMAGE_QUALITY_HIGH);
2204 p_iconList->Add(imb);
2206 m_treeCtrlPredefSrcs->AssignImageList(p_iconList);
2209 m_treeCtrlPredefSrcs->SetIndent(w);
2211 m_base_path = wxEmptyString;
2212 m_last_path = wxEmptyString;
2214 m_nbChoice->SetSelection(0);
2222bool ChartDldrGuiAddSourceDlg::LoadSources() {
2223 wxTreeItemId tree = m_treeCtrlPredefSrcs->AddRoot(_T(
"root"));
2227 fn.SetFullName(_T(
"chartdldr_pi-chart_sources.xml"));
2228 if (!fn.FileExists()) {
2230 fn.AppendDir(_T(
"plugins"));
2231 fn.AppendDir(_T(
"chartdldr_pi"));
2232 fn.AppendDir(_T(
"data"));
2233 fn.SetFullName(_T(
"chart_sources.xml"));
2234 if (!fn.FileExists()) {
2235 wxLogMessage(wxString::Format(
2236 _T(
"Error: chartdldr_pi::LoadSources() %s not found!"),
2237 fn.GetFullPath().c_str()));
2241 wxString path = fn.GetFullPath();
2244 bool ret = doc->load_file(path.mb_str());
2249 element = element.next_sibling()) {
2250 if (!strcmp(element.name(),
"sections")) {
2251 LoadSections(tree, element);
2259bool ChartDldrGuiAddSourceDlg::LoadSections(
const wxTreeItemId &root,
2262 element = element.next_sibling()) {
2263 if (!strcmp(element.name(),
"section")) {
2264 LoadSection(root, element);
2270bool ChartDldrGuiAddSourceDlg::LoadSection(
const wxTreeItemId &root,
2274 element = element.next_sibling()) {
2275 if (!strcmp(element.name(),
"name")) {
2276 item = m_treeCtrlPredefSrcs->AppendItem(
2277 root, wxString::FromUTF8(element.first_child().value()), 0, 0);
2280 if (pFont) m_treeCtrlPredefSrcs->SetItemFont(item, *pFont);
2282 if (!strcmp(element.name(),
"sections")) LoadSections(item, element);
2283 if (!strcmp(element.name(),
"catalogs")) LoadCatalogs(item, element);
2289bool ChartDldrGuiAddSourceDlg::LoadCatalogs(
const wxTreeItemId &root,
2292 element = element.next_sibling()) {
2293 if (!strcmp(element.name(),
"catalog")) LoadCatalog(root, element);
2299bool ChartDldrGuiAddSourceDlg::LoadCatalog(
const wxTreeItemId &root,
2301 wxString name, type, location, dir;
2303 element = element.next_sibling()) {
2304 if (!strcmp(element.name(),
"name"))
2305 name = wxString::FromUTF8(element.first_child().value());
2306 else if (!strcmp(element.name(),
"type"))
2307 type = wxString::FromUTF8(element.first_child().value());
2308 else if (!strcmp(element.name(),
"location"))
2309 location = wxString::FromUTF8(element.first_child().value());
2310 else if (!strcmp(element.name(),
"dir"))
2311 dir = wxString::FromUTF8(element.first_child().value());
2314 wxTreeItemId
id = m_treeCtrlPredefSrcs->AppendItem(root, name, 1, 1, cs);
2317 if (pFont) m_treeCtrlPredefSrcs->SetItemFont(
id, *pFont);
2322ChartDldrGuiAddSourceDlg::~ChartDldrGuiAddSourceDlg() {}
2324wxString ChartDldrGuiAddSourceDlg::FixPath(wxString path) {
2325 wxString sep(wxFileName::GetPathSeparator());
2327 s.Replace(_T(
"/"), sep,
true);
2328 s.Replace(_T(USERDATA), m_base_path);
2329 s.Replace(sep + sep, sep);
2333void ChartDldrGuiAddSourceDlg::OnChangeType(wxCommandEvent &event) {
2334 m_treeCtrlPredefSrcs->Enable(m_nbChoice->GetSelection() == 0);
2335 m_tSourceName->Enable(m_nbChoice->GetSelection() == 1);
2336 m_tChartSourceUrl->Enable(m_nbChoice->GetSelection() == 1);
2339void ChartDldrGuiAddSourceDlg::OnSourceSelected(wxTreeEvent &event) {
2340 wxTreeItemId item = m_treeCtrlPredefSrcs->GetSelection();
2343 m_dirExpanded = FixPath(cs->GetDir());
2345 m_tSourceName->SetValue(cs->GetName());
2346 m_tChartSourceUrl->SetValue(cs->GetUrl());
2347 if (m_tcChartDirectory->GetValue() == m_last_path) {
2348 m_tcChartDirectory->SetValue(FixPath(cs->GetDir()));
2349 m_panelChartDirectory->SetText(FixPath(cs->GetDir()));
2351 m_buttonChartDirectory->Enable();
2352 m_last_path = m_tcChartDirectory->GetValue();
2358void ChartDldrGuiAddSourceDlg::SetSourceEdit(std::unique_ptr<ChartSource> &cs) {
2359 m_nbChoice->SetSelection(1);
2360 m_tChartSourceUrl->Enable();
2361 m_treeCtrlPredefSrcs->Disable();
2362 m_tSourceName->SetValue(cs->GetName());
2363 m_tChartSourceUrl->SetValue(cs->GetUrl());
2364 m_tcChartDirectory->SetValue(FixPath(cs->GetDir()));
2365 m_panelChartDirectory->SetText(FixPath(cs->GetDir()));
2367 m_buttonChartDirectory->Enable();
2370ChartDldrPrefsDlgImpl::ChartDldrPrefsDlgImpl(wxWindow *parent)
2373ChartDldrPrefsDlgImpl::~ChartDldrPrefsDlgImpl() {}
2375void ChartDldrPrefsDlgImpl::SetPath(
const wxString path) {
2383 m_tcDefaultDir->SetValue(path);
2386void ChartDldrPrefsDlgImpl::GetPreferences(
bool &preselect_new,
2387 bool &preselect_updated,
2388 bool &bulk_update) {
2389 preselect_new = m_cbSelectNew->GetValue();
2390 preselect_updated = m_cbSelectUpdated->GetValue();
2391 bulk_update = m_cbBulkUpdate->GetValue();
2393void ChartDldrPrefsDlgImpl::SetPreferences(
bool preselect_new,
2394 bool preselect_updated,
2396 m_cbSelectNew->SetValue(preselect_new);
2397 m_cbSelectUpdated->SetValue(preselect_updated);
2398 m_cbBulkUpdate->SetValue(bulk_update);
2401void ChartDldrGuiAddSourceDlg::OnOkClick(wxCommandEvent &event) {
2402 wxString msg = wxEmptyString;
2404 if (m_nbChoice->GetSelection() == 0) {
2405 wxTreeItemId item = m_treeCtrlPredefSrcs->GetSelection();
2406 if (m_treeCtrlPredefSrcs->GetSelection().IsOk()) {
2408 (
ChartSource *)(m_treeCtrlPredefSrcs->GetItemData(item));
2411 _(
"You must select one of the predefined chart sources or create "
2412 "one of your own.\n");
2415 _(
"You must select one of the predefined chart sources or create one "
2418 if (m_nbChoice->GetSelection() == 1 &&
2419 m_tSourceName->GetValue() == wxEmptyString)
2420 msg += _(
"The chart source must have a name.\n");
2421 wxURI url(m_tChartSourceUrl->GetValue());
2422 if (m_nbChoice->GetSelection() == 1 &&
2423 (m_tChartSourceUrl->GetValue() == wxEmptyString ||
2424 !ValidateUrl(m_tChartSourceUrl->GetValue())))
2425 msg += _(
"The chart source must have a valid URL.\n");
2426 if (m_tcChartDirectory->GetValue() == wxEmptyString)
2427 msg += _(
"You must select a local folder to store the charts.\n");
2428 else if (!wxDirExists(m_tcChartDirectory->GetValue()))
2429 if (!wxFileName::Mkdir(m_tcChartDirectory->GetValue(), 0755,
2431 msg += wxString::Format(_(
"Directory %s can't be created."),
2432 m_tcChartDirectory->GetValue().c_str()) +
2435 if (msg != wxEmptyString)
2437 wxOK | wxCENTRE | wxICON_ERROR);
2440 SetReturnCode(wxID_OK);
2445void ChartDldrGuiAddSourceDlg::OnCancelClick(wxCommandEvent &event) {
2446 SetReturnCode(wxID_CANCEL);
2447 EndModal(wxID_CANCEL);
2450void ChartDldrPrefsDlgImpl::OnOkClick(wxCommandEvent &event) {
2451 if (!wxDirExists(m_tcDefaultDir->GetValue())) {
2452 if (!wxFileName::Mkdir(m_tcDefaultDir->GetValue(), 0755,
2453 wxPATH_MKDIR_FULL)) {
2456 wxString::Format(_(
"Directory %s can't be created."),
2457 m_tcDefaultDir->GetValue().c_str()),
2458 _(
"Chart Downloader"));
2464 g_pi->UpdatePrefs(
this);
2474void ChartDldrPrefsDlg::OnCancelClick(wxCommandEvent &event) {
2476 EndModal(wxID_CANCEL);
2480void ChartDldrPrefsDlg::OnOkClick(wxCommandEvent &event) {
2485bool ChartDldrGuiAddSourceDlg::ValidateUrl(
const wxString Url,
2490 _T(
"^https?\\://[a-zA-Z0-9\\./_-]*\\.[xX][mM][lL]$"));
2496 _T(
"^https?\\://[a-zA-Z0-9\\./_-]*$"));
2498 return re.Matches(Url);
2506 switch (ev.getDLEventCondition()) {
2508 m_bTransferComplete =
true;
2509 m_bTransferSuccess =
2514 if (ev.getTransferred() > m_transferredsize) {
2515 m_totalsize = ev.getTotal();
2516 m_transferredsize = ev.getTransferred();
Implementing ChartDldrPanel.
int GetPlugInVersionMinor()
Returns the minor version number of the plugin itself.
wxString GetLongDescription()
Get detailed plugin information.
wxBitmap * GetPlugInBitmap()
Get the plugin's icon bitmap.
void ShowPreferencesDialog(wxWindow *parent)
Shows the plugin preferences dialog.
int Init(void)
Initialize the plugin and declare its capabilities.
int GetAPIVersionMinor()
Returns the minor version number of the plugin API that this plugin supports.
bool DeInit(void)
Clean up plugin resources.
void OnCloseToolboxPanel(int page_sel, int ok_apply_cancel)
Handles preference page closure.
int GetAPIVersionMajor()
Returns the major version number of the plugin API that this plugin supports.
wxString GetShortDescription()
Get a brief description of the plugin.
wxString GetCommonName()
Get the plugin's common (short) name.
int GetPlugInVersionMajor()
Returns the major version number of the plugin itself.
void OnSetupOptions(void)
Allows plugin to add pages to global Options dialog.
Base class for OpenCPN plugins.
double g_androidDPmm
Only used used by ANDROID
@ OCPN_DL_EVENT_TYPE_PROGRESS
Download progress update.
@ OCPN_DL_EVENT_TYPE_END
Download has completed.
_OCPN_DLStatus
Status codes for HTTP file download operations.
@ OCPN_DL_NO_ERROR
Download completed successfully.
@ OCPN_DL_USER_TIMEOUT
Download timed out waiting for user action.
@ OCPN_DL_STARTED
Download has begun but not yet complete.
@ OCPN_DL_FAILED
Download failed (general error)
@ OCPN_DL_UNKNOWN
Unknown or uninitialized status.
@ OCPN_DL_ABORTED
Download was cancelled by user.
@ 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.
@ PI_OPTIONS_PARENT_CHARTS
Charts section.
wxWindow * GetOCPNCanvasWindow()
Gets OpenCPN's main canvas window.
wxFont * OCPNGetFont(wxString TextElement, int default_size)
Gets a font for UI elements.
wxFileConfig * GetOCPNConfigObject()
Gets OpenCPN's configuration object.
wxFont GetOCPNGUIScaledFont_PlugIn(wxString item)
Gets a uniquely scaled font copy for responsive UI elements.
wxScrolledWindow * AddOptionsPage(OptionsParentPI parent, wxString title)
Adds a new preferences page to OpenCPN options dialog.
void AddChartDirectory(wxString &path)
Adds a chart directory to OpenCPN's chart database.
wxString * GetpSharedDataLocation()
Gets shared application data location.
wxArrayString GetChartDBDirArrayString()
Gets chart database directory list.
bool DeleteOptionsPage(wxScrolledWindow *page)
Remove a previously added options page.
bool AddLocaleCatalog(wxString catalog)
Adds a locale catalog for translations.
void ForceChartDBUpdate()
Forces an update of the chart database.
wxString * GetpPrivateApplicationDataLocation()
Gets private application data directory.
void OCPN_cancelDownloadFileBackground(long handle)
Cancels a background download.
int OCPNMessageBox_PlugIn(wxWindow *parent, const wxString &message, const wxString &caption, int style, int x, int y)
Shows a message box dialog.
_OCPN_DLStatus OCPN_downloadFileBackground(const wxString &url, const wxString &outputFile, wxEvtHandler *handler, long *handle)
Asynchronously downloads a file in the background.
_OCPN_DLStatus OCPN_downloadFile(const wxString &url, const wxString &outputFile, const wxString &title, const wxString &message, const wxBitmap &bitmap, wxWindow *parent, long style, int timeout_secs)
Synchronously download a file with progress dialog.
wxString GetWritableDocumentsDir()
Returns the platform-specific default documents directory.