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"
113static wxString FormatBytes(
double bytes) {
114 if (bytes <= 0)
return "?";
115 return wxString::Format(_T(
"%.1fMB"), bytes / 1024 / 1024);
118static wxString FormatBytes(
long bytes) {
119 return FormatBytes(
static_cast<double>(bytes));
122int g_Android_SDK_Version;
124bool IsDLDirWritable(wxFileName fn) {
126 return fn.IsDirWritable();
128 if (g_Android_SDK_Version >= 30) {
130 return (fn.GetFullPath().Contains(
"org.opencpn.opencpn"));
132 return fn.IsDirWritable();
143extern "C" DECL_EXP
void destroy_pi(
opencpn_plugin *p) {
delete p; }
166 m_parent_window = NULL;
167 m_pChartSource = NULL;
169 m_preselect_new =
false;
170 m_preselect_updated =
false;
171 m_allow_bulk_update =
false;
172 m_pOptionsPage = NULL;
173 m_selected_source = -1;
175 m_schartdldr_sources = wxEmptyString;
189 m_pOptionsPage = NULL;
191 m_pChartSource = NULL;
194 androidGetSDKVersion();
202 wxStringTokenizer st(m_schartdldr_sources, _T(
"|"), wxTOKEN_DEFAULT);
203 while (st.HasMoreTokens()) {
204 wxString s1 = st.GetNextToken();
205 wxString s2 = st.GetNextToken();
206 wxString s3 = st.GetNextToken();
208 m_ChartSources.push_back(std::make_unique<ChartSource>(s1, s2, s3));
214 wxLogMessage(_T(
"chartdldr_pi: DeInit"));
216 m_ChartSources.clear();
224 if (m_pOptionsPage) {
244 return _(
"Chart Downloader PlugIn for OpenCPN");
249 "Chart Downloader PlugIn for OpenCPN\n\
250Manages chart downloads and updates from sources supporting\n\
251NOAA Chart Catalog format");
257 if (!m_pOptionsPage) {
259 _T(
"Error: chartdldr_pi::OnSetupOptions AddOptionsPage failed!"));
262 wxBoxSizer *sizer =
new wxBoxSizer(wxVERTICAL);
263 m_pOptionsPage->SetSizer(sizer);
267 wxDefaultSize, wxDEFAULT_DIALOG_STYLE);
269 m_pOptionsPage->InvalidateBestSize();
270 sizer->Add(m_dldrpanel, 1, wxALL | wxEXPAND);
271 m_dldrpanel->SetBulkUpdate(m_allow_bulk_update);
272 m_dldrpanel->FitInside();
277 m_dldrpanel->CancelDownload();
279 OCPN_cancelDownloadFileBackground(
282 m_selected_source = m_dldrpanel->GetSelectedCatalog();
286bool chartdldr_pi::LoadConfig(
void) {
287 wxFileConfig *pConf = (wxFileConfig *)m_pconfig;
290 pConf->SetPath(_T (
"/Settings/ChartDnldr" ));
291 pConf->Read(_T (
"ChartSources" ), &m_schartdldr_sources, wxEmptyString);
292 pConf->Read(_T (
"Source" ), &m_selected_source, -1);
294 wxFileName fn(GetWritableDocumentsDir(), wxEmptyString);
295 fn.AppendDir(_T(CHART_DIR));
297 pConf->Read(_T (
"BaseChartDir" ), &m_base_chart_dir, fn.GetPath());
298 wxLogMessage(_T (
"chartdldr_pi:m_base_chart_dir: " ) + m_base_chart_dir);
301 wxFileName testFN(m_base_chart_dir);
302 if (!IsDLDirWritable(testFN)) {
304 "Cannot write to m_base_chart_dir, override to "
305 "GetWritableDocumentsDir()");
306 m_base_chart_dir = fn.GetPath();
307 wxLogMessage(_T (
"chartdldr_pi: Corrected: " ) + m_base_chart_dir);
310 pConf->Read(_T (
"PreselectNew" ), &m_preselect_new,
true);
311 pConf->Read(_T (
"PreselectUpdated" ), &m_preselect_updated,
true);
312 pConf->Read(_T (
"AllowBulkUpdate" ), &m_allow_bulk_update,
false);
318bool chartdldr_pi::SaveConfig(
void) {
319 wxFileConfig *pConf = (wxFileConfig *)m_pconfig;
321 m_schartdldr_sources.Clear();
323 for (
size_t i = 0; i < m_ChartSources.size(); i++) {
324 std::unique_ptr<ChartSource> &cs = m_ChartSources.at(i);
325 m_schartdldr_sources.Append(
326 wxString::Format(_T(
"%s|%s|%s|"), cs->GetName().c_str(),
327 cs->GetUrl().c_str(), cs->GetDir().c_str()));
331 pConf->SetPath(_T (
"/Settings/ChartDnldr" ));
332 pConf->Write(_T (
"ChartSources" ), m_schartdldr_sources);
333 pConf->Write(_T (
"Source" ), m_selected_source);
334 pConf->Write(_T (
"BaseChartDir" ), m_base_chart_dir);
335 pConf->Write(_T (
"PreselectNew" ), m_preselect_new);
336 pConf->Write(_T (
"PreselectUpdated" ), m_preselect_updated);
337 pConf->Write(_T (
"AllowBulkUpdate" ), m_allow_bulk_update);
344void SetBackColor(wxWindow *ctrl, wxColour col) {
345 static int depth = 0;
348 ctrl->SetBackgroundColour(col);
351 wxWindowList kids = ctrl->GetChildren();
352 for (
unsigned int i = 0; i < kids.GetCount(); i++) {
353 wxWindowListNode *node = kids.Item(i);
354 wxWindow *win = node->GetData();
356 if (
dynamic_cast<wxListBox *
>(win))
357 dynamic_cast<wxListBox *
>(win)->SetBackgroundColour(col);
359 else if (
dynamic_cast<wxTextCtrl *
>(win))
360 dynamic_cast<wxTextCtrl *
>(win)->SetBackgroundColour(col);
365 else if (
dynamic_cast<wxChoice *
>(win))
366 dynamic_cast<wxChoice *
>(win)->SetBackgroundColour(col);
368 else if (
dynamic_cast<wxComboBox *
>(win))
369 dynamic_cast<wxComboBox *
>(win)->SetBackgroundColour(col);
371 else if (
dynamic_cast<wxRadioButton *
>(win))
372 dynamic_cast<wxRadioButton *
>(win)->SetBackgroundColour(col);
374 else if (
dynamic_cast<wxScrolledWindow *
>(win)) {
375 dynamic_cast<wxScrolledWindow *
>(win)->SetBackgroundColour(col);
378 else if (
dynamic_cast<wxButton *
>(win)) {
379 dynamic_cast<wxButton *
>(win)->SetBackgroundColour(col);
386 if (win->GetChildren().GetCount() > 0) {
389 SetBackColor(w, col);
402 if (m_parent_window) {
403 int xmax = m_parent_window->GetSize().GetWidth();
404 int ymax = m_parent_window->GetParent()
407 dialog->SetSize(xmax, ymax);
413 wxColour cl = wxColour(214, 218, 222);
414 SetBackColor(dialog, cl);
417 dialog->SetPath(m_base_chart_dir);
418 dialog->SetPreferences(m_preselect_new, m_preselect_updated,
419 m_allow_bulk_update);
426 m_base_chart_dir = dialog->GetPath();
427 dialog->GetPreferences(m_preselect_new, m_preselect_updated,
428 m_allow_bulk_update);
430 if (m_dldrpanel) m_dldrpanel->SetBulkUpdate(m_allow_bulk_update);
433bool getDisplayMetrics() {
439 QAndroidJniObject activity = QAndroidJniObject::callStaticObjectMethod(
440 "org/qtproject/qt5/android/QtNative",
"activity",
441 "()Landroid/app/Activity;");
443 if (!activity.isValid()) {
448 QAndroidJniObject data =
449 activity.callObjectMethod(
"getDisplayMetrics",
"()Ljava/lang/String;");
451 wxString return_string;
452 jstring s = data.object<jstring>();
456 if (java_vm->GetEnv((
void **)&jenv, JNI_VERSION_1_6) != JNI_OK) {
459 const char *ret_string = (jenv)->GetStringUTFChars(s, NULL);
460 return_string = wxString(ret_string, wxConvUTF8);
465 return_string.Replace(_T(
","), _T(
"."));
473 double density = 1.0;
474 wxStringTokenizer tk(return_string, _T(
";"));
475 if (tk.HasMoreTokens()) {
476 wxString token = tk.GetNextToken();
477 token = tk.GetNextToken();
479 long b = ::wxGetDisplaySize().y;
480 token.ToDouble(&density);
482 token = tk.GetNextToken();
484 token = tk.GetNextToken();
485 token = tk.GetNextToken();
486 token = tk.GetNextToken();
487 token = tk.GetNextToken();
488 token = tk.GetNextToken();
489 token = tk.GetNextToken();
491 token = tk.GetNextToken();
499 double ldpi = 160. * density;
508 g_androidDPmm = ldpi / 25.4;
519ChartSource::ChartSource(wxString name, wxString url, wxString localdir) {
523 m_update_data.clear();
526ChartSource::~ChartSource() { m_update_data.clear(); }
528#define ID_MNU_SELALL 2001
529#define ID_MNU_DELALL 2002
530#define ID_MNU_INVSEL 2003
531#define ID_MNU_SELUPD 2004
532#define ID_MNU_SELNEW 2005
534enum { ThreadId = wxID_HIGHEST + 1 };
540 switch (evt.GetId()) {
542 CheckAllCharts(
true);
545 CheckAllCharts(
false);
548 InvertCheckAllCharts();
551 CheckUpdatedCharts(
true);
554 CheckNewCharts(
true);
559void ChartDldrPanelImpl::OnContextMenu(wxMouseEvent &event) {
562 wxPoint mouseScreen = wxGetMousePosition();
563 wxPoint mouseClient = ScreenToClient(mouseScreen);
569 wxMenuItem *item1 =
new wxMenuItem(&menu, ID_MNU_SELALL, _(
"Select all"));
573 wxMenuItem *item2 =
new wxMenuItem(&menu, ID_MNU_DELALL, _(
"Deselect all"));
578 new wxMenuItem(&menu, ID_MNU_INVSEL, _(
"Invert selection"));
582 wxMenuItem *item4 =
new wxMenuItem(&menu, ID_MNU_SELUPD, _(
"Select updated"));
586 wxMenuItem *item5 =
new wxMenuItem(&menu, ID_MNU_SELNEW, _(
"Select new"));
592 menu.Append(ID_MNU_SELALL, _(
"Select all"), wxT(
""));
593 menu.Append(ID_MNU_DELALL, _(
"Deselect all"), wxT(
""));
594 menu.Append(ID_MNU_INVSEL, _(
"Invert selection"), wxT(
""));
595 menu.Append(ID_MNU_SELUPD, _(
"Select updated"), wxT(
""));
596 menu.Append(ID_MNU_SELNEW, _(
"Select new"), wxT(
""));
600 menu.Connect(wxEVT_COMMAND_MENU_SELECTED,
601 (wxObjectEventFunction)&ChartDldrPanelImpl::OnPopupClick, NULL,
604 PopupMenu(&menu, mouseClient.x, mouseClient.y);
607void ChartDldrPanelImpl::OnShowLocalDir(wxCommandEvent &event) {
608 if (pPlugIn->m_pChartSource == NULL)
return;
610 wxExecute(wxString::Format(_T(
"xdg-open %s"),
611 pPlugIn->m_pChartSource->GetDir().c_str()));
614 wxExecute(wxString::Format(_T(
"open %s"),
615 pPlugIn->m_pChartSource->GetDir().c_str()));
618 wxExecute(wxString::Format(_T(
"explorer %s"),
619 pPlugIn->m_pChartSource->GetDir().c_str()));
623void ChartDldrPanelImpl::SetSource(
int id) {
624 pPlugIn->SetSourceId(
id);
626 m_bDeleteSource->Enable(
id >= 0);
627 m_bUpdateChartList->Enable(
id >= 0);
628 m_bEditSource->Enable(
id >= 0);
633 if (
id >= 0 &&
id < (
int)pPlugIn->m_ChartSources.size()) {
634 ::wxBeginBusyCursor();
636 std::unique_ptr<ChartSource> &cs = pPlugIn->m_ChartSources.at(
id);
637 cs->LoadUpdateData();
638 cs->UpdateLocalFiles();
639 pPlugIn->m_pChartSource = cs.get();
640 FillFromFile(cs->GetUrl(), cs->GetDir(), pPlugIn->m_preselect_new,
641 pPlugIn->m_preselect_updated);
642 wxURI url(cs->GetUrl());
643 m_chartsLabel->SetLabel(wxString::Format(
644 _(
"Charts: %s"), (cs->GetName() + _(
" from ") + url.BuildURI() +
645 _T(
" -> ") + cs->GetDir())
647 if (::wxIsBusy()) ::wxEndBusyCursor();
649 pPlugIn->m_pChartSource = NULL;
650 m_chartsLabel->SetLabel(_(
"Charts"));
654void ChartDldrPanelImpl::SelectSource(wxListEvent &event) {
655 int i = GetSelectedCatalog();
656 if (i >= 0) SetSource(i);
660void ChartDldrPanelImpl::SetBulkUpdate(
bool bulk_update) {
661 m_bUpdateAllCharts->Enable(bulk_update);
662 m_bUpdateAllCharts->Show(bulk_update);
667void ChartDldrPanelImpl::CleanForm() {
668#if defined(CHART_LIST)
671 m_scrollWinChartList->ClearBackground();
676void ChartDldrPanelImpl::FillFromFile(wxString url, wxString dir,
bool selnew,
679 wxStringTokenizer tk(url, _T(
"/"));
682 file = tk.GetNextToken();
683 }
while (tk.HasMoreTokens());
685 fn.SetFullName(file);
687 wxString path = fn.GetFullPath();
688 if (wxFileExists(path)) {
689 pPlugIn->m_pChartCatalog.LoadFromFile(path);
696#if !defined(CHART_LIST)
698 m_panelArray.clear();
699 m_scrollWinChartList->ClearBackground();
702 for (
size_t i = 0; i < pPlugIn->m_pChartCatalog.charts.size(); i++) {
707 pPlugIn->m_pChartCatalog.charts.at(i)->GetChartFilename(
true);
708 if (!pPlugIn->m_pChartSource->ExistsLocaly(
709 pPlugIn->m_pChartCatalog.charts.at(i)->number, file_)) {
712 if (selnew) bcheck =
true;
714 if (pPlugIn->m_pChartSource->IsNewerThanLocal(
715 pPlugIn->m_pChartCatalog.charts.at(i)->number, file_,
716 pPlugIn->m_pChartCatalog.charts.at(i)->GetUpdateDatetime())) {
718 status = _(
"Out of date");
719 if (selupd) bcheck =
true;
721 status = _(
"Up to date");
725 pPlugIn->m_pChartCatalog.charts.at(i)->GetUpdateDatetime().Format(
728#if defined(CHART_LIST)
729 wxVector<wxVariant> data;
730 data.push_back(wxVariant(bcheck));
731 data.push_back(wxVariant(status));
732 data.push_back(wxVariant(latest));
734 wxVariant(pPlugIn->m_pChartCatalog.charts.at(i)->GetChartTitle()));
735 getChartList()->AppendItem(data);
737 auto pC = std::make_unique<ChartPanel>(
738 m_scrollWinChartList, wxID_ANY, wxDefaultPosition, wxSize(-1, -1),
739 pPlugIn->m_pChartCatalog.charts.at(i)->GetChartTitle(), status,
740 latest,
this, bcheck);
741 pC->Connect(wxEVT_RIGHT_DOWN,
742 wxMouseEventHandler(ChartDldrPanel::OnContextMenu), NULL,
745 m_boxSizerCharts->Add(pC.get(), 0, wxEXPAND | wxLEFT | wxRIGHT, 2);
746 m_panelArray.push_back(std::move(pC));
750#if !defined(CHART_LIST)
751 m_scrollWinChartList->ClearBackground();
752 m_scrollWinChartList->FitInside();
753 m_scrollWinChartList->GetSizer()->Layout();
755 m_scrollWinChartList->ClearBackground();
756 SetChartInfo(wxString::Format(_(
"%lu charts total, %lu updated, %lu new"),
757 pPlugIn->m_pChartCatalog.charts.size(),
758 m_updatedCharts, m_newCharts));
760 SetChartInfo(wxString::Format(
761 _(
"%lu charts total, %lu updated, %lu new, %lu selected"),
762 pPlugIn->m_pChartCatalog.charts.size(), m_updatedCharts, m_newCharts,
763 GetCheckedChartCount()));
768bool ChartSource::ExistsLocaly(wxString chart_number, wxString filename) {
771 wxStringTokenizer tk(filename, _T(
"."));
772 wxString file = tk.GetNextToken().MakeLower();
774 if (!m_update_data.empty()) {
775 return m_update_data.find(std::string(chart_number.Lower().mb_str())) !=
776 m_update_data.end() ||
777 m_update_data.find(std::string(file.mb_str())) !=
780 for (
size_t i = 0; i < m_localfiles.Count(); i++) {
781 if (m_localfiles.Item(i) == file)
return true;
786bool ChartSource::IsNewerThanLocal(wxString chart_number, wxString filename,
787 wxDateTime validDate) {
788 wxStringTokenizer tk(filename, _T(
"."));
789 wxString file = tk.GetNextToken().MakeLower();
790 if (!m_update_data.empty()) {
791 if (m_update_data[std::string(chart_number.Lower().mbc_str())] <
792 validDate.GetTicks() &&
793 m_update_data[std::string(file.mbc_str())] < validDate.GetTicks())
798 bool update_candidate =
false;
800 for (
size_t i = 0; i < m_localfiles.Count(); i++) {
801 if (m_localfiles.Item(i) == file) {
802 if (validDate.IsLaterThan(m_localdt.at(i))) {
803 update_candidate =
true;
808 return update_candidate;
811int ChartDldrPanelImpl::GetSelectedCatalog() {
813 m_lbChartSources->GetNextItem(-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED);
817void ChartDldrPanelImpl::SelectCatalog(
int item) {
819 m_bDeleteSource->Enable();
820 m_bEditSource->Enable();
821 m_bUpdateChartList->Enable();
823 m_bDeleteSource->Disable();
824 m_bEditSource->Disable();
825 m_bUpdateChartList->Disable();
827 m_lbChartSources->SetItemState(item, wxLIST_STATE_SELECTED,
828 wxLIST_STATE_SELECTED);
831void ChartDldrPanelImpl::AppendCatalog(std::unique_ptr<ChartSource> &cs) {
832 long id = m_lbChartSources->GetItemCount();
833 m_lbChartSources->InsertItem(
id, cs->GetName());
834 m_lbChartSources->SetItem(
id, 1, _(
"(Please update first)"));
835 m_lbChartSources->SetItem(
id, 2, cs->GetDir());
836 wxURI url(cs->GetUrl());
837 if (url.IsReference()) {
838 OCPNMessageBox_PlugIn(
839 this, _(
"Error, the URL to the chart source data seems wrong."),
843 wxFileName fn(url.GetPath());
844 fn.SetPath(cs->GetDir());
845 wxString path = fn.GetFullPath();
846 if (wxFileExists(path)) {
847 if (pPlugIn->m_pChartCatalog.LoadFromFile(path,
true)) {
848 m_lbChartSources->SetItem(
id, 0, pPlugIn->m_pChartCatalog.title);
849 m_lbChartSources->SetItem(
851 pPlugIn->m_pChartCatalog.GetReleaseDate().Format(
852 _T(
"%Y-%m-%d %H:%M")));
853 m_lbChartSources->SetItem(
id, 2, path);
855 m_lbChartSources->GetHandle()->resizeColumnToContents(0);
856 m_lbChartSources->GetHandle()->resizeColumnToContents(1);
857 m_lbChartSources->GetHandle()->resizeColumnToContents(2);
863void ChartDldrPanelImpl::UpdateAllCharts(wxCommandEvent &event) {
864 int failed_to_update = 0;
865 int attempted_to_update = 0;
866 if ((pPlugIn->m_preselect_new) && (pPlugIn->m_preselect_updated)) {
867 wxMessageDialog mess(
869 _(
"You have chosen to update all chart catalogs.\nThen download all "
870 "new and updated charts.\nThis may take a long time."),
871 _(
"Chart Downloader"), wxOK | wxCANCEL);
872 if (mess.ShowModal() == wxID_CANCEL)
return;
873 }
else if (pPlugIn->m_preselect_new) {
874 wxMessageDialog mess(
876 _(
"You have chosen to update all chart catalogs.\nThen download only "
877 "new (but not updated) charts.\nThis may take a long time."),
878 _(
"Chart Downloader"), wxOK | wxCANCEL);
879 if (mess.ShowModal() == wxID_CANCEL)
return;
880 }
else if (pPlugIn->m_preselect_updated) {
881 wxMessageDialog mess(
883 _(
"You have chosen to update all chart catalogs.\nThen download only "
884 "updated (but not new) charts.\nThis may take a long time."),
885 _(
"Chart Downloader"), wxOK | wxCANCEL);
886 if (mess.ShowModal() == wxID_CANCEL)
return;
891 int oldPage = m_DLoadNB->SetSelection(1);
892 for (
long chartIndex = 0; chartIndex < m_lbChartSources->GetItemCount();
894 m_lbChartSources->SetItemState(chartIndex, wxLIST_STATE_SELECTED,
895 wxLIST_STATE_SELECTED);
896 if (cancelled)
break;
897 UpdateChartList(event);
899 attempted_to_update += m_downloading;
900 failed_to_update += m_failed_downloads;
902 wxLogMessage(wxString::Format(
903 _T(
"chartdldr_pi::UpdateAllCharts() downloaded %d out of %d charts."),
904 attempted_to_update - failed_to_update, attempted_to_update));
905 if (failed_to_update > 0)
906 OCPNMessageBox_PlugIn(
908 wxString::Format(_(
"%d out of %d charts failed to download.\nCheck the "
909 "list, verify there is a working Internet "
910 "connection and repeat the operation if needed."),
911 failed_to_update, attempted_to_update),
912 _(
"Chart Downloader"), wxOK | wxICON_ERROR);
917 m_DLoadNB->SetSelection(oldPage);
920void ChartDldrPanelImpl::UpdateChartList(wxCommandEvent &event) {
922 if (!m_lbChartSources->GetSelectedItemCount())
return;
923 std::unique_ptr<ChartSource> &cs =
924 pPlugIn->m_ChartSources.at(GetSelectedCatalog());
925 wxURI url(cs->GetUrl());
926 if (url.IsReference()) {
927 OCPNMessageBox_PlugIn(
928 this, _(
"Error, the URL to the chart source data seems wrong."),
933 wxStringTokenizer tk(url.GetPath(), _T(
"/"));
936 file = tk.GetNextToken();
937 }
while (tk.HasMoreTokens());
939 fn.SetFullName(file);
940 fn.SetPath(cs->GetDir());
941 if (!wxDirExists(cs->GetDir())) {
942 if (!wxFileName::Mkdir(cs->GetDir(), 0755, wxPATH_MKDIR_FULL)) {
943 OCPNMessageBox_PlugIn(
945 wxString::Format(_(
"Directory %s can't be created."),
946 cs->GetDir().c_str()),
947 _(
"Chart Downloader"));
955 wxString file_URI = _T(
"file://") + fn.GetFullPath();
967 cs->GetUrl(), file_URI, _(
"Downloading file"),
968 _(
"Reading Headers: ") + url.BuildURI(), wxNullBitmap,
this,
977 wxFileName tfn = wxFileName::CreateTempFileName(fn.GetFullPath());
978 wxString file_URI = tfn.GetFullPath();
981 cs->GetUrl(), file_URI, _(
"Downloading file"),
982 _(
"Reading Headers: ") + url.BuildURI(), wxNullBitmap,
this,
989 bok = wxCopyFile(tfn.GetFullPath(), fn.GetFullPath());
990 wxRemoveFile(tfn.GetFullPath());
999 long id = GetSelectedCatalog();
1002 m_lbChartSources->SetItem(
id, 0, pPlugIn->m_pChartCatalog.title);
1003 m_lbChartSources->SetItem(
1005 pPlugIn->m_pChartCatalog.GetReleaseDate().Format(
1006 _T(
"%Y-%m-%d %H:%M")));
1007 m_lbChartSources->SetItem(
id, 2, cs->GetDir());
1010 OCPNMessageBox_PlugIn(
1012 wxString::Format(_(
"Failed to Find New Catalog: %s "),
1013 url.BuildURI().c_str()),
1014 _(
"Chart Downloader"), wxOK | wxICON_ERROR);
1018 OCPNMessageBox_PlugIn(
1020 wxString::Format(_(
"Failed to Download Catalog: %s \nVerify there is "
1021 "a working Internet connection."),
1022 url.BuildURI().c_str()),
1023 _(
"Chart Downloader"), wxOK | wxICON_ERROR);
1046void ChartSource::GetLocalFiles() {
1047 if (!UpdateDataExists() || m_update_data.empty()) {
1048 wxArrayString *allFiles =
new wxArrayString;
1049 if (wxDirExists(GetDir())) wxDir::GetAllFiles(GetDir(), allFiles);
1051 m_localfiles.Clear();
1052 wxDateTime ct, mt, at;
1054 for (
size_t i = 0; i < allFiles->Count(); i++) {
1055 wxFileName fn(allFiles->Item(i));
1056 name = fn.GetFullName().Lower();
1060 if (!ExistsLocaly(wxEmptyString, name)) {
1061 fn.GetTimes(&at, &mt, &ct);
1062 m_localdt.push_back(mt);
1063 m_localfiles.Add(fn.GetName().Lower());
1065 wxStringTokenizer tk(name, _T(
"."));
1066 wxString file = tk.GetNextToken().MakeLower();
1067 m_update_data[std::string(file.mbc_str())] = mt.GetTicks();
1078bool ChartSource::UpdateDataExists() {
1079 return wxFileExists(GetDir() + wxFileName::GetPathSeparator() +
1080 _T(UPDATE_DATA_FILENAME));
1083void ChartSource::LoadUpdateData() {
1084 m_update_data.clear();
1086 GetDir() + wxFileName::GetPathSeparator() + _T(UPDATE_DATA_FILENAME);
1088 if (!wxFileExists(fn))
return;
1090 std::ifstream infile(fn.mb_str());
1095 while (infile >> key >> value) m_update_data[key] = value;
1100void ChartSource::SaveUpdateData() {
1102 fn = GetDir() + wxFileName::GetPathSeparator() + _T(UPDATE_DATA_FILENAME);
1105 fn = AndroidGetCacheDir() + wxFileName::GetPathSeparator() +
1106 _T(UPDATE_DATA_FILENAME);
1109 std::ofstream outfile(fn.mb_str());
1110 if (!outfile.is_open())
return;
1112 std::map<std::string, time_t>::iterator iter;
1113 for (iter = m_update_data.begin(); iter != m_update_data.end(); ++iter) {
1114 if (iter->first.find(
" ") == std::string::npos)
1115 if (!iter->first.empty())
1116 outfile << iter->first <<
" " << iter->second <<
"\n";
1122 AndroidSecureCopyFile(
1123 fn, GetDir() + wxFileName::GetPathSeparator() + _T(UPDATE_DATA_FILENAME));
1127void ChartSource::ChartUpdated(wxString chart_number, time_t timestamp) {
1128 m_update_data[std::string(chart_number.Lower().mb_str())] = timestamp;
1132bool ChartDldrPanelImpl::DownloadChart(wxString url, wxString file,
1137void ChartDldrPanelImpl::DisableForDownload(
bool enabled) {
1138 m_bAddSource->Enable(enabled);
1139 m_bDeleteSource->Enable(enabled);
1140 m_bEditSource->Enable(enabled);
1141 m_bUpdateAllCharts->Enable(enabled);
1142 m_bUpdateChartList->Enable(enabled);
1143 m_lbChartSources->Enable(enabled);
1144#if defined(CHART_LIST)
1145 m_bSelectNew->Enable(enabled);
1146 m_bSelectUpdated->Enable(enabled);
1147 m_bSelectAll->Enable(enabled);
1151void ChartDldrPanelImpl::OnDownloadCharts(wxCommandEvent &event) {
1152 if (DownloadIsCancel) {
1158#if defined(CHART_LIST)
1159void ChartDldrPanelImpl::OnSelectChartItem(wxCommandEvent &event) {
1161 SetChartInfo(wxString::Format(
1162 _(
"%lu charts total, %lu updated, %lu new, %lu selected"),
1163 pPlugIn->m_pChartCatalog.charts.size(), m_updatedCharts, m_newCharts,
1164 GetCheckedChartCount()));
1169#if defined(CHART_LIST)
1170void ChartDldrPanelImpl::OnSelectNewCharts(wxCommandEvent &event) {
1171 CheckNewCharts(
true);
1175#if defined(CHART_LIST)
1176void ChartDldrPanelImpl::OnSelectUpdatedCharts(wxCommandEvent &event) {
1177 CheckUpdatedCharts(
true);
1181#if defined(CHART_LIST)
1182void ChartDldrPanelImpl::OnSelectAllCharts(wxCommandEvent &event) {
1183 if (m_bSelectAll->GetLabel() == _(
"Select All")) {
1184 CheckAllCharts(
true);
1185 m_bSelectAll->SetLabel(_(
"Select None"));
1186 m_bSelectAll->SetToolTip(_(
"De-select all charts in the list."));
1188 CheckAllCharts(
false);
1189 m_bSelectAll->SetLabel(_(
"Select All"));
1190 m_bSelectAll->SetToolTip(_(
"Select all charts in the list."));
1195int ChartDldrPanelImpl::GetChartCount() {
1196#if defined(CHART_LIST)
1197 return getChartList()->GetItemCount();
1199 return m_panelArray.size();
1203int ChartDldrPanelImpl::GetCheckedChartCount() {
1204#if defined(CHART_LIST)
1206 int chartCnt = GetChartCount();
1207 for (
int i = 0; i < chartCnt; i++)
1208 if (isChartChecked(i)) cnt++;
1211 for (
int i = 0; i < GetChartCount(); i++) {
1212 if (m_panelArray.at(i)->GetCB()->IsChecked()) cnt++;
1218bool ChartDldrPanelImpl::isChartChecked(
int i) {
1219 wxASSERT_MSG(i >= 0,
1220 wxT(
"This function should be called with non-negative index."));
1221 if (i <= GetChartCount())
1222#if defined(CHART_LIST)
1223 return getChartList()->GetToggleValue(i, 0);
1225 return m_panelArray.at(i)->GetCB()->IsChecked();
1231void ChartDldrPanelImpl::CheckAllCharts(
bool value) {
1232#if defined(CHART_LIST)
1236 for (
int i = 0; i < GetChartCount(); i++) {
1237#if defined(CHART_LIST)
1238 getChartList()->SetToggleValue(value, i, 0);
1240 m_panelArray.at(i)->GetCB()->SetValue(value);
1243#if defined(CHART_LIST)
1244 SetChartInfo(wxString::Format(
1245 _(
"%lu charts total, %lu updated, %lu new, %lu selected"),
1246 pPlugIn->m_pChartCatalog.charts.size(), m_updatedCharts, m_newCharts,
1247 GetCheckedChartCount()));
1248 m_bInfoHold =
false;
1252void ChartDldrPanelImpl::CheckNewCharts(
bool value) {
1253 for (
int i = 0; i < GetChartCount(); i++) {
1254#if defined(CHART_LIST)
1255 if (isNew(i)) getChartList()->SetToggleValue(
true, i, 0);
1257 if (m_panelArray.at(i)->isNew())
1258 m_panelArray.at(i)->GetCB()->SetValue(value);
1261#if defined(CHART_LIST)
1262 SetChartInfo(wxString::Format(
1263 _(
"%lu charts total, %lu updated, %lu new, %lu selected"),
1264 pPlugIn->m_pChartCatalog.charts.size(), m_updatedCharts, m_newCharts,
1265 GetCheckedChartCount()));
1269void ChartDldrPanelImpl::CheckUpdatedCharts(
bool value) {
1270 for (
int i = 0; i < GetChartCount(); i++) {
1271#if defined(CHART_LIST)
1272 if (isUpdated(i)) getChartList()->SetToggleValue(value, i, 0);
1274 if (m_panelArray.at(i)->isUpdated())
1275 m_panelArray.at(i)->GetCB()->SetValue(value);
1278#if defined(CHART_LIST)
1279 SetChartInfo(wxString::Format(
1280 _(
"%lu charts total, %lu updated, %lu new, %lu selected"),
1281 pPlugIn->m_pChartCatalog.charts.size(), m_updatedCharts, m_newCharts,
1282 GetCheckedChartCount()));
1286void ChartDldrPanelImpl::InvertCheckAllCharts() {
1287#if defined(CHART_LIST)
1290 for (
int i = 0; i < GetChartCount(); i++)
1291#
if defined(CHART_LIST)
1292 getChartList()->SetToggleValue(!isChartChecked(i), i, 0);
1294 m_panelArray.at(i)->GetCB()->SetValue(!isChartChecked(i));
1296#if defined(CHART_LIST)
1297 m_bInfoHold =
false;
1298 SetChartInfo(wxString::Format(
1299 _(
"%lu charts total, %lu updated, %lu new, %lu selected"),
1300 pPlugIn->m_pChartCatalog.charts.size(), m_updatedCharts, m_newCharts,
1301 GetCheckedChartCount()));
1306 if (!m_bconnected) {
1308 wxEVT_DOWNLOAD_EVENT,
1309 (wxObjectEventFunction)(wxEventFunction)&ChartDldrPanelImpl::onDLEvent);
1310 m_bconnected =
true;
1313 if (!GetCheckedChartCount() && !updatingAll) {
1314 OCPNMessageBox_PlugIn(
this, _(
"No charts selected for download."));
1317 std::unique_ptr<ChartSource> &cs =
1318 pPlugIn->m_ChartSources.at(GetSelectedCatalog());
1321 to_download = GetCheckedChartCount();
1323 m_failed_downloads = 0;
1324 DisableForDownload(
false);
1326 m_bDnldCharts->SetLabel(_(
"Abort download"));
1327 DownloadIsCancel =
true;
1329 wxFileName downloaded_p;
1332 for (
int i = 0; i < GetChartCount() && to_download; i++) {
1334 if (cancelled)
break;
1336 if (!isChartChecked(i))
continue;
1337 m_bTransferComplete =
false;
1338 m_bTransferSuccess =
true;
1340 m_transferredsize = 0;
1342 if (pPlugIn->m_pChartCatalog.charts.at(index)->NeedsManualDownload()) {
1344 OCPNMessageBox_PlugIn(
1347 _(
"The selected chart '%s' can't be downloaded automatically, do you want me to open a browser window and download them manually?\n\n \
1348After downloading the charts, please extract them to %s"),
1349 pPlugIn->m_pChartCatalog.charts.at(index)->title.c_str(),
1350 pPlugIn->m_pChartSource->GetDir().c_str()),
1351 _(
"Chart Downloader"), wxYES_NO | wxCENTRE | wxICON_QUESTION)) {
1352 wxLaunchDefaultBrowser(
1353 pPlugIn->m_pChartCatalog.charts.at(index)->GetManualDownloadUrl());
1359 wxURI url(pPlugIn->m_pChartCatalog.charts.at(index)->GetDownloadLocation());
1360 if (url.IsReference()) {
1361 OCPNMessageBox_PlugIn(
1364 _(
"Error, the URL to the chart (%s) data seems wrong."),
1365 url.BuildURI().c_str()),
1373 pPlugIn->m_pChartCatalog.charts.at(index)->GetChartFilename();
1375 fn.SetFullName(file);
1376 fn.SetPath(cs->GetDir());
1377 wxString path = fn.GetFullPath();
1378 if (wxFileExists(path)) wxRemoveFile(path);
1379 wxString title = pPlugIn->m_pChartCatalog.charts.at(index)->GetChartTitle();
1383 wxString file_path = _T(
"file://") + fn.GetFullPath();
1385 wxString file_path = fn.GetFullPath();
1389 OCPN_downloadFileBackground(url.BuildURI(), file_path,
this, &handle);
1392 if (pPlugIn->ProcessFile(
1393 downloaded_p.GetFullPath(), downloaded_p.GetPath(),
true,
1394 pPlugIn->m_pChartCatalog.charts.at(idx)->GetUpdateDatetime())) {
1395 cs->ChartUpdated(pPlugIn->m_pChartCatalog.charts.at(idx)->number,
1396 pPlugIn->m_pChartCatalog.charts.at(idx)
1397 ->GetUpdateDatetime()
1400 m_failed_downloads++;
1405 while (!m_bTransferComplete && m_bTransferSuccess && !cancelled) {
1406 if (m_failed_downloads)
1407 SetChartInfo(wxString::Format(
1408 _(
"Downloading chart %u of %u, %u downloads failed (%s / %s)"),
1409 m_downloading, to_download, m_failed_downloads,
1410 FormatBytes(m_transferredsize), FormatBytes(m_totalsize)));
1412 SetChartInfo(wxString::Format(_(
"Downloading chart %u of %u (%s / %s)"),
1413 m_downloading, to_download,
1414 FormatBytes(m_transferredsize),
1415 FormatBytes(m_totalsize)));
1420 wxTheApp->ProcessPendingEvents();
1427 OCPN_cancelDownloadFileBackground(handle);
1430 if (m_bTransferSuccess && !cancelled) {
1432 downloaded_p = path;
1435 if (wxFileExists(path)) wxRemoveFile(path);
1436 m_failed_downloads++;
1440 if (pPlugIn->ProcessFile(
1441 downloaded_p.GetFullPath(), downloaded_p.GetPath(),
true,
1442 pPlugIn->m_pChartCatalog.charts.at(idx)->GetUpdateDatetime())) {
1443 cs->ChartUpdated(pPlugIn->m_pChartCatalog.charts.at(idx)->number,
1444 pPlugIn->m_pChartCatalog.charts.at(idx)
1445 ->GetUpdateDatetime()
1448 m_failed_downloads++;
1451 DisableForDownload(
true);
1452 m_bDnldCharts->SetLabel(_(
"Download selected charts"));
1453 DownloadIsCancel =
false;
1454 SetSource(GetSelectedCatalog());
1455 if (m_failed_downloads > 0 && !updatingAll && !cancelled)
1456 OCPNMessageBox_PlugIn(
1458 wxString::Format(_(
"%d out of %d charts failed to download.\nCheck the "
1459 "list, verify there is a working Internet "
1460 "connection and repeat the operation if needed."),
1461 m_failed_downloads, m_downloading),
1462 _(
"Chart Downloader"), wxOK | wxICON_ERROR);
1465 OCPNMessageBox_PlugIn(
this, _(
"Chart download cancelled."),
1466 _(
"Chart Downloader"), wxOK | wxICON_INFORMATION);
1468 if ((m_downloading - m_failed_downloads > 0) && !updatingAll)
1472ChartDldrPanelImpl::~ChartDldrPanelImpl() {
1474 wxEVT_DOWNLOAD_EVENT,
1475 (wxObjectEventFunction)(wxEventFunction)&ChartDldrPanelImpl::onDLEvent);
1476 m_bconnected =
false;
1479 OCPN_cancelDownloadFileBackground(
1482#if defined(CHART_LIST)
1487ChartDldrPanelImpl::ChartDldrPanelImpl(
chartdldr_pi *plugin, wxWindow *parent,
1488 wxWindowID
id,
const wxPoint &pos,
1489 const wxSize &size,
long style)
1491 m_bDeleteSource->Disable();
1492 m_bUpdateChartList->Disable();
1493 m_bEditSource->Disable();
1494 m_lbChartSources->InsertColumn(0, _(
"Catalog"), wxLIST_FORMAT_LEFT,
1495 CATALOGS_NAME_WIDTH);
1496 m_lbChartSources->InsertColumn(1, _(
"Released"), wxLIST_FORMAT_LEFT,
1497 CATALOGS_DATE_WIDTH);
1498 m_lbChartSources->InsertColumn(2, _(
"Local path"), wxLIST_FORMAT_LEFT,
1499 CATALOGS_PATH_WIDTH);
1500 m_lbChartSources->Enable();
1501 m_bInfoHold =
false;
1505 updatingAll =
false;
1507 m_populated =
false;
1508 DownloadIsCancel =
false;
1509 m_failed_downloads = 0;
1510 SetChartInfo(wxEmptyString);
1511 m_bTransferComplete =
true;
1512 m_bTransferSuccess =
true;
1515 wxEVT_DOWNLOAD_EVENT,
1516 (wxObjectEventFunction)(wxEventFunction)&ChartDldrPanelImpl::onDLEvent);
1517 m_bconnected =
true;
1519 for (
size_t i = 0; i < pPlugIn->m_ChartSources.size(); i++) {
1520 AppendCatalog(pPlugIn->m_ChartSources.at(i));
1525void ChartDldrPanelImpl::OnPaint(wxPaintEvent &event) {
1528 for (
size_t i = 0; i < pPlugIn->m_ChartSources.size(); i++) {
1529 AppendCatalog(pPlugIn->m_ChartSources.at(i));
1534 m_lbChartSources->Refresh(
true);
1539void ChartDldrPanelImpl::DeleteSource(wxCommandEvent &event) {
1540 if (!m_lbChartSources->GetSelectedItemCount())
return;
1541 if (wxID_YES != OCPNMessageBox_PlugIn(
1543 _(
"Do you really want to remove the chart source?\nThe "
1544 "local chart files will not be removed,\nbut you will "
1545 "not be able to update the charts anymore."),
1546 _(
"Chart Downloader"), wxYES_NO | wxCENTRE))
1548 int ToBeRemoved = GetSelectedCatalog();
1549 m_lbChartSources->SetItemState(ToBeRemoved, 0,
1550 wxLIST_STATE_SELECTED | wxLIST_STATE_FOCUSED);
1551 pPlugIn->m_ChartSources.erase(pPlugIn->m_ChartSources.begin() + ToBeRemoved);
1552 m_lbChartSources->DeleteItem(ToBeRemoved);
1554 pPlugIn->SetSourceId(-1);
1556 pPlugIn->SaveConfig();
1560void ChartDldrPanelImpl::AddSource(wxCommandEvent &event) {
1562 dialog->SetBasePath(pPlugIn->GetBaseChartDir());
1564 wxSize sz = GetParent()
1567 dialog->SetSize(sz.GetWidth(), sz.GetHeight());
1571 androidDisableRotation();
1574 if (dialog->ShowModal() == wxID_OK) {
1575 std::unique_ptr<ChartSource> cs =
1576 std::make_unique<ChartSource>(dialog->m_tSourceName->GetValue(),
1577 dialog->m_tChartSourceUrl->GetValue(),
1578 dialog->m_tcChartDirectory->GetValue());
1581 bool covered =
false;
1589 wxString dir = cs->GetDir();
1593 long itemSelectedNow = GetSelectedCatalog();
1594 m_lbChartSources->SetItemState(itemSelectedNow, 0, wxLIST_STATE_SELECTED);
1596 SelectCatalog(m_lbChartSources->GetItemCount() - 1);
1597 pPlugIn->m_ChartSources.push_back(std::move(cs));
1598 pPlugIn->SaveConfig();
1601 androidEnableRotation();
1607void ChartDldrPanelImpl::DoEditSource() {
1608 if (!m_lbChartSources->GetSelectedItemCount())
return;
1609 int cat = GetSelectedCatalog();
1611 dialog->SetBasePath(pPlugIn->GetBaseChartDir());
1612 dialog->SetSourceEdit(pPlugIn->m_ChartSources.at(cat));
1613 dialog->SetTitle(_(
"Edit Chart Source"));
1615 dialog->ShowModal();
1616 int retcode = dialog->GetReturnCode();
1618 if (retcode == wxID_OK) {
1619 pPlugIn->m_ChartSources.at(cat)->SetName(
1620 dialog->m_tSourceName->GetValue());
1621 pPlugIn->m_ChartSources.at(cat)->SetUrl(
1622 dialog->m_tChartSourceUrl->GetValue());
1623 pPlugIn->m_ChartSources.at(cat)->SetDir(
1624 dialog->m_tcChartDirectory->GetValue());
1626 m_lbChartSources->SetItem(cat, 0,
1627 pPlugIn->m_ChartSources.at(cat)->GetName());
1628 m_lbChartSources->SetItem(cat, 1, _(
"(Please update first)"));
1629 m_lbChartSources->SetItem(cat, 2,
1630 pPlugIn->m_ChartSources.at(cat)->GetDir());
1631 wxURI url(pPlugIn->m_ChartSources.at(cat)->GetUrl());
1632 wxFileName fn(url.GetPath());
1633 fn.SetPath(pPlugIn->m_ChartSources.at(cat)->GetDir());
1634 wxString path = fn.GetFullPath();
1635 if (wxFileExists(path)) {
1636 if (pPlugIn->m_pChartCatalog.LoadFromFile(path,
true)) {
1637 m_lbChartSources->SetItem(cat, 0, pPlugIn->m_pChartCatalog.title);
1638 m_lbChartSources->SetItem(
1640 pPlugIn->m_pChartCatalog.GetReleaseDate().Format(
1641 _T(
"%Y-%m-%d %H:%M")));
1642 m_lbChartSources->SetItem(cat, 2, path);
1645 bool covered =
false;
1647 if (pPlugIn->m_ChartSources.at(cat)->GetDir().StartsWith(
1654 OCPNMessageBox_PlugIn(
1657 _(
"Path %s seems not to be covered by your configured Chart "
1658 "Directories.\nTo see the charts you have to adjust the "
1659 "configuration on the 'Chart Files' tab."),
1660 pPlugIn->m_ChartSources.at(cat)->GetDir().c_str()),
1661 _(
"Chart Downloader"));
1663 pPlugIn->SaveConfig();
1669void ChartDldrPanelImpl::EditSource(wxCommandEvent &event) {
1674void ChartDldrPanelImpl::OnLeftDClick(wxMouseEvent &event) {
1679bool chartdldr_pi::ProcessFile(
const wxString &aFile,
1680 const wxString &aTargetDir,
bool aStripPath,
1681 wxDateTime aMTime) {
1682 if (aFile.Lower().EndsWith(_T(
"zip")))
1684 bool ret = ExtractZipFiles(aFile, aTargetDir, aStripPath, aMTime,
false);
1686 wxRemoveFile(aFile);
1688 wxLogError(_T(
"chartdldr_pi: Unable to extract: ") + aFile);
1691#ifdef DLDR_USE_LIBARCHIVE
1692 else if (aFile.Lower().EndsWith(_T(
"rar"))) {
1693#ifdef CHARTDLDR_RAR_UNARR
1694 bool ret = ExtractUnarrFiles(aFile, aTargetDir, aStripPath, aMTime,
false);
1697 ExtractLibArchiveFiles(aFile, aTargetDir, aStripPath, aMTime,
false);
1700 wxRemoveFile(aFile);
1702 wxLogError(_T(
"chartdldr_pi: Unable to extract: ") + aFile);
1704 }
else if (aFile.Lower().EndsWith(_T(
"tar")) ||
1705 aFile.Lower().EndsWith(_T(
"gz")) ||
1706 aFile.Lower().EndsWith(_T(
"bz2")) ||
1707 aFile.Lower().EndsWith(_T(
"lzma")) ||
1708 aFile.Lower().EndsWith(_T(
"7z")) ||
1709 aFile.Lower().EndsWith(_T(
"xz"))) {
1711 ExtractLibArchiveFiles(aFile, aTargetDir, aStripPath, aMTime,
false);
1713 wxRemoveFile(aFile);
1715 wxLogError(_T(
"chartdldr_pi: Unable to extract: ") + aFile);
1719 else if (aFile.Lower().EndsWith(_T(
"rar")) ||
1720 aFile.Lower().EndsWith(_T(
"tar"))
1722 || aFile.Lower().EndsWith(_T(
"bz2"))
1725 || aFile.Lower().EndsWith(_T(
"gz"))
1728 || aFile.Lower().EndsWith(
1733 bool ret = ExtractUnarrFiles(aFile, aTargetDir, aStripPath, aMTime,
false);
1735 wxRemoveFile(aFile);
1737 wxLogError(_T(
"chartdldr_pi: Unable to extract: ") + aFile);
1743 else if (aFile.Lower().EndsWith(_T(
"tar")) ||
1744 aFile.Lower().EndsWith(_T(
"gz")) ||
1745 aFile.Lower().EndsWith(_T(
"bz2")) ||
1746 aFile.Lower().EndsWith(_T(
"lzma")) ||
1747 aFile.Lower().EndsWith(_T(
"7z")) ||
1748 aFile.Lower().EndsWith(_T(
"xz"))) {
1750 if (aStripPath) nStrip = 1;
1752 if (m_dldrpanel) m_dldrpanel->SetChartInfo(_(
"Installing charts."));
1754 androidShowBusyIcon();
1755 bool ret = AndroidUnzip(aFile, aTargetDir, nStrip,
true);
1756 androidHideBusyIcon();
1764 wxFileName fn(aFile);
1765 if (fn.GetPath() != aTargetDir)
1767 if (!wxDirExists(aTargetDir)) {
1768 if (wxFileName::Mkdir(aTargetDir, 0755, wxPATH_MKDIR_FULL)) {
1769 if (!wxRenameFile(aFile, aTargetDir))
return false;
1774 wxString name = fn.GetFullName();
1776 fn.Assign(aTargetDir, name);
1777 fn.SetTimes(&aMTime, &aMTime, &aMTime);
1782#ifdef DLDR_USE_LIBARCHIVE
1784static int copy_data(
struct archive *ar,
struct archive *aw) {
1788 __LA_INT64_T offset;
1791 r = archive_read_data_block(ar, &buff, &size, &offset);
1792 if (r == ARCHIVE_EOF)
return (ARCHIVE_OK);
1793 if (r < ARCHIVE_OK)
return (r);
1794 r = archive_write_data_block(aw, buff, size, offset);
1795 if (r < ARCHIVE_OK) {
1797 wxLogError(wxString::Format(
"Chartdldr_pi: LibArchive error: %s",
1798 archive_error_string(aw)));
1805bool chartdldr_pi::ExtractLibArchiveFiles(
const wxString &aArchiveFile,
1806 const wxString &aTargetDir,
1807 bool aStripPath, wxDateTime aMTime,
1808 bool aRemoveArchive) {
1811 struct archive *ext;
1812 struct archive_entry *entry;
1817 flags = ARCHIVE_EXTRACT_TIME;
1824 a = archive_read_new();
1825 archive_read_support_format_all(a);
1826 archive_read_support_filter_all(a);
1827 archive_read_support_compression_all(a);
1828 ext = archive_write_disk_new();
1829 archive_write_disk_set_options(ext, flags);
1830 archive_write_disk_set_standard_lookup(ext);
1831 if ((r = archive_read_open_filename(a, aArchiveFile.c_str(), 10240))) {
1835 r = archive_read_next_header(a, &entry);
1836 if (r == ARCHIVE_EOF)
break;
1839 wxLogError(wxString::Format(
"Chartdldr_pi: LibArchive error: %s",
1840 archive_error_string(a)));
1841 if (r < ARCHIVE_WARN)
return false;
1843 const char *currentFile = archive_entry_pathname(entry);
1844 std::string fullOutputPath = currentFile;
1845 size_t sep = fullOutputPath.find_last_of(
"\\/");
1846 if (sep != std::string::npos)
1848 fullOutputPath.substr(sep + 1, fullOutputPath.size() - sep - 1);
1849 archive_entry_set_pathname(entry, fullOutputPath.c_str());
1851 if (aTargetDir != wxEmptyString) {
1852 const char *currentFile = archive_entry_pathname(entry);
1853 const std::string fullOutputPath =
1854 aTargetDir.ToStdString() +
1855 wxString(wxFileName::GetPathSeparator()).ToStdString() + currentFile;
1856 archive_entry_set_pathname(entry, fullOutputPath.c_str());
1858 r = archive_write_header(ext, entry);
1861 wxLogError(wxString::Format(
"Chartdldr_pi: LibArchive error: %s",
1862 archive_error_string(ext)));
1863 else if (archive_entry_size(entry) > 0) {
1864 r = copy_data(a, ext);
1867 wxLogError(wxString::Format(
"Chartdldr_pi: LibArchive error: %s",
1868 archive_error_string(ext)));
1869 if (r < ARCHIVE_WARN)
return false;
1871 r = archive_write_finish_entry(ext);
1875 wxLogError(wxString::Format(
"Chartdldr_pi: LibArchive error: %s",
1876 archive_error_string(ext)));
1877 if (r < ARCHIVE_WARN)
return false;
1879 archive_read_close(a);
1881 archive_write_close(ext);
1884 if (aRemoveArchive) wxRemoveFile(aArchiveFile);
1895#if defined(CHARTDLDR_RAR_UNARR) || !defined(DLDR_USE_LIBARCHIVE)
1896ar_archive *ar_open_any_archive(ar_stream *stream,
const char *fileext) {
1897 ar_archive *ar = ar_open_rar_archive(stream);
1900 ar_open_zip_archive(stream, fileext && (strcmp(fileext,
".xps") == 0 ||
1901 strcmp(fileext,
".epub") == 0));
1902 if (!ar) ar = ar_open_7z_archive(stream);
1903 if (!ar) ar = ar_open_tar_archive(stream);
1907bool chartdldr_pi::ExtractUnarrFiles(
const wxString &aRarFile,
1908 const wxString &aTargetDir,
1909 bool aStripPath, wxDateTime aMTime,
1911 ar_stream *stream = NULL;
1912 ar_archive *ar = NULL;
1913 int entry_count = 1;
1914 int entry_skips = 0;
1918 stream = ar_open_file(aRarFile.c_str());
1920 wxLogError(_T(
"Can not open file '") + aRarFile + _T(
"'."));
1921 ar_close_archive(ar);
1925 ar = ar_open_any_archive(stream, strrchr(aRarFile.c_str(),
'.'));
1927 wxLogError(_T(
"Can not open archive '") + aRarFile + _T(
"'."));
1928 ar_close_archive(ar);
1932 while (ar_parse_entry(ar)) {
1933 size_t size = ar_entry_get_size(ar);
1934 wxString name = ar_entry_get_name(ar);
1936 wxFileName fn(name);
1941 if (fn.GetDirCount() > 0) {
1943 name = aTargetDir + wxFileName::GetPathSeparator() + fn.GetFullPath();
1945 name = aTargetDir + wxFileName::GetPathSeparator() + name;
1948 wxFileName fn(name);
1949 if (!fn.DirExists()) {
1950 if (!wxFileName::Mkdir(fn.GetPath())) {
1951 wxLogError(_T(
"Can not create directory '") + fn.GetPath() + _T(
"'."));
1956 wxFileOutputStream file(name);
1958 wxLogError(_T(
"Can not create file '") + name + _T(
"'."));
1963 unsigned char buffer[1024];
1964 size_t count = size <
sizeof(buffer) ? size : sizeof(buffer);
1965 if (!ar_entry_uncompress(ar, buffer, count))
break;
1966 file.Write(buffer, count);
1970 fn.SetTimes(&aMTime, &aMTime, &aMTime);
1972 wxLogError(
"Warning: Failed to uncompress... skipping");
1977 if (!ar_at_eof(ar)) {
1978 wxLogError(
"Error: Failed to parse entry %d!", entry_count);
1981 ar_close_archive(ar);
1984 if (aRemoveRar) wxRemoveFile(aRarFile);
1989 setlocale(LC_NUMERIC,
"C");
1996bool chartdldr_pi::ExtractZipFiles(
const wxString &aZipFile,
1997 const wxString &aTargetDir,
bool aStripPath,
1998 wxDateTime aMTime,
bool aRemoveZip) {
2003 if (aStripPath) nStrip = 1;
2005 ret = AndroidUnzip(aZipFile, aTargetDir, nStrip,
true);
2007 std::unique_ptr<wxZipEntry> entry(
new wxZipEntry());
2010 wxLogMessage(_T(
"chartdldr_pi: Going to extract '") + aZipFile + _T(
"'."));
2011 wxFileInputStream in(aZipFile);
2014 wxLogMessage(_T(
"Can not open file '") + aZipFile + _T(
"'."));
2018 wxZipInputStream zip(in);
2021 while (entry.reset(zip.GetNextEntry()), entry.get() != NULL) {
2023 wxString name = entry->GetName();
2025 wxFileName fn(name);
2030 if (fn.GetDirCount() > 0) fn.RemoveDir(0);
2031 name = aTargetDir + wxFileName::GetPathSeparator() + fn.GetFullPath();
2033 name = aTargetDir + wxFileName::GetPathSeparator() + name;
2037 if (entry->IsDir()) {
2038 int perm = entry->GetMode();
2039 if (!wxFileName::Mkdir(name, perm, wxPATH_MKDIR_FULL)) {
2040 wxLogMessage(_T(
"Can not create directory '") + name + _T(
"'."));
2045 if (!zip.OpenEntry(*entry.get())) {
2046 wxLogMessage(_T(
"Can not open zip entry '") + entry->GetName() +
2051 if (!zip.CanRead()) {
2052 wxLogMessage(_T(
"Can not read zip entry '") + entry->GetName() +
2058 wxFileName fn(name);
2059 if (!fn.DirExists()) {
2060 if (!wxFileName::Mkdir(fn.GetPath())) {
2061 wxLogMessage(_T(
"Can not create directory '") + fn.GetPath() +
2068 wxFileOutputStream file(name);
2071 wxLogMessage(_T(
"Can not create file '") + name + _T(
"'."));
2076 fn.SetTimes(&aMTime, &aMTime, &aMTime);
2083 if (aRemoveZip) wxRemoveFile(aZipFile);
2089ChartDldrGuiAddSourceDlg::ChartDldrGuiAddSourceDlg(wxWindow *parent)
2093 fn.AppendDir(_T(
"plugins"));
2094 fn.AppendDir(_T(
"chartdldr_pi"));
2095 fn.AppendDir(_T(
"data"));
2101 w = 6 * g_androidDPmm;
2104 p_buttonIconList =
new wxImageList(w, h);
2106 fn.SetFullName(_T(
"button_right.png"));
2107 wxImage im1(fn.GetFullPath(), wxBITMAP_TYPE_PNG);
2108 im1.Rescale(w, h, wxIMAGE_QUALITY_HIGH);
2109 p_buttonIconList->Add(im1);
2111 fn.SetFullName(_T(
"button_right.png"));
2112 wxImage im2(fn.GetFullPath(), wxBITMAP_TYPE_PNG);
2113 im2.Rescale(w, h, wxIMAGE_QUALITY_HIGH);
2114 p_buttonIconList->Add(im2);
2116 fn.SetFullName(_T(
"button_down.png"));
2117 wxImage im3(fn.GetFullPath(), wxBITMAP_TYPE_PNG);
2118 im3.Rescale(w, h, wxIMAGE_QUALITY_HIGH);
2119 p_buttonIconList->Add(im3);
2121 fn.SetFullName(_T(
"button_down.png"));
2122 wxImage im4(fn.GetFullPath(), wxBITMAP_TYPE_PNG);
2123 im4.Rescale(w, h, wxIMAGE_QUALITY_HIGH);
2124 p_buttonIconList->Add(im4);
2126 m_treeCtrlPredefSrcs->AssignButtonsImageList(p_buttonIconList);
2128 p_iconList =
new wxImageList(w, h);
2130 fn.SetFullName(_T(
"folder.png"));
2131 wxImage ima(fn.GetFullPath(), wxBITMAP_TYPE_PNG);
2132 ima.Rescale(w, h, wxIMAGE_QUALITY_HIGH);
2133 p_iconList->Add(ima);
2135 fn.SetFullName(_T(
"file.png"));
2136 wxImage imb(fn.GetFullPath(), wxBITMAP_TYPE_PNG);
2137 imb.Rescale(w, h, wxIMAGE_QUALITY_HIGH);
2138 p_iconList->Add(imb);
2140 m_treeCtrlPredefSrcs->AssignImageList(p_iconList);
2143 m_treeCtrlPredefSrcs->SetIndent(w);
2145 m_base_path = wxEmptyString;
2146 m_last_path = wxEmptyString;
2148 m_nbChoice->SetSelection(0);
2156bool ChartDldrGuiAddSourceDlg::LoadSources() {
2157 wxTreeItemId tree = m_treeCtrlPredefSrcs->AddRoot(_T(
"root"));
2161 fn.SetFullName(_T(
"chartdldr_pi-chart_sources.xml"));
2162 if (!fn.FileExists()) {
2164 fn.AppendDir(_T(
"plugins"));
2165 fn.AppendDir(_T(
"chartdldr_pi"));
2166 fn.AppendDir(_T(
"data"));
2167 fn.SetFullName(_T(
"chart_sources.xml"));
2168 if (!fn.FileExists()) {
2169 wxLogMessage(wxString::Format(
2170 _T(
"Error: chartdldr_pi::LoadSources() %s not found!"),
2171 fn.GetFullPath().c_str()));
2175 wxString path = fn.GetFullPath();
2178 bool ret = doc->load_file(path.mb_str());
2183 element = element.next_sibling()) {
2184 if (!strcmp(element.name(),
"sections")) {
2185 LoadSections(tree, element);
2193bool ChartDldrGuiAddSourceDlg::LoadSections(
const wxTreeItemId &root,
2196 element = element.next_sibling()) {
2197 if (!strcmp(element.name(),
"section")) {
2198 LoadSection(root, element);
2204bool ChartDldrGuiAddSourceDlg::LoadSection(
const wxTreeItemId &root,
2208 element = element.next_sibling()) {
2209 if (!strcmp(element.name(),
"name")) {
2210 item = m_treeCtrlPredefSrcs->AppendItem(
2211 root, wxString::FromUTF8(element.first_child().value()), 0, 0);
2214 if (pFont) m_treeCtrlPredefSrcs->SetItemFont(item, *pFont);
2216 if (!strcmp(element.name(),
"sections")) LoadSections(item, element);
2217 if (!strcmp(element.name(),
"catalogs")) LoadCatalogs(item, element);
2223bool ChartDldrGuiAddSourceDlg::LoadCatalogs(
const wxTreeItemId &root,
2226 element = element.next_sibling()) {
2227 if (!strcmp(element.name(),
"catalog")) LoadCatalog(root, element);
2233bool ChartDldrGuiAddSourceDlg::LoadCatalog(
const wxTreeItemId &root,
2235 wxString name, type, location, dir;
2237 element = element.next_sibling()) {
2238 if (!strcmp(element.name(),
"name"))
2239 name = wxString::FromUTF8(element.first_child().value());
2240 else if (!strcmp(element.name(),
"type"))
2241 type = wxString::FromUTF8(element.first_child().value());
2242 else if (!strcmp(element.name(),
"location"))
2243 location = wxString::FromUTF8(element.first_child().value());
2244 else if (!strcmp(element.name(),
"dir"))
2245 dir = wxString::FromUTF8(element.first_child().value());
2248 wxTreeItemId
id = m_treeCtrlPredefSrcs->AppendItem(root, name, 1, 1, cs);
2251 if (pFont) m_treeCtrlPredefSrcs->SetItemFont(
id, *pFont);
2256ChartDldrGuiAddSourceDlg::~ChartDldrGuiAddSourceDlg() {}
2258wxString ChartDldrGuiAddSourceDlg::FixPath(wxString path) {
2259 wxString sep(wxFileName::GetPathSeparator());
2261 s.Replace(_T(
"/"), sep,
true);
2262 s.Replace(_T(USERDATA), m_base_path);
2263 s.Replace(sep + sep, sep);
2267void ChartDldrGuiAddSourceDlg::OnChangeType(wxCommandEvent &event) {
2268 m_treeCtrlPredefSrcs->Enable(m_nbChoice->GetSelection() == 0);
2269 m_tSourceName->Enable(m_nbChoice->GetSelection() == 1);
2270 m_tChartSourceUrl->Enable(m_nbChoice->GetSelection() == 1);
2273void ChartDldrGuiAddSourceDlg::OnSourceSelected(wxTreeEvent &event) {
2274 wxTreeItemId item = m_treeCtrlPredefSrcs->GetSelection();
2277 m_dirExpanded = FixPath(cs->GetDir());
2279 m_tSourceName->SetValue(cs->GetName());
2280 m_tChartSourceUrl->SetValue(cs->GetUrl());
2281 if (m_tcChartDirectory->GetValue() == m_last_path) {
2282 m_tcChartDirectory->SetValue(FixPath(cs->GetDir()));
2283 m_panelChartDirectory->SetText(FixPath(cs->GetDir()));
2285 m_buttonChartDirectory->Enable();
2286 m_last_path = m_tcChartDirectory->GetValue();
2292void ChartDldrGuiAddSourceDlg::SetSourceEdit(std::unique_ptr<ChartSource> &cs) {
2293 m_nbChoice->SetSelection(1);
2294 m_tChartSourceUrl->Enable();
2295 m_treeCtrlPredefSrcs->Disable();
2296 m_tSourceName->SetValue(cs->GetName());
2297 m_tChartSourceUrl->SetValue(cs->GetUrl());
2298 m_tcChartDirectory->SetValue(FixPath(cs->GetDir()));
2299 m_panelChartDirectory->SetText(FixPath(cs->GetDir()));
2301 m_buttonChartDirectory->Enable();
2304ChartDldrPrefsDlgImpl::ChartDldrPrefsDlgImpl(wxWindow *parent)
2307ChartDldrPrefsDlgImpl::~ChartDldrPrefsDlgImpl() {}
2309void ChartDldrPrefsDlgImpl::SetPath(
const wxString path) {
2317 m_tcDefaultDir->SetValue(path);
2320void ChartDldrPrefsDlgImpl::GetPreferences(
bool &preselect_new,
2321 bool &preselect_updated,
2322 bool &bulk_update) {
2323 preselect_new = m_cbSelectNew->GetValue();
2324 preselect_updated = m_cbSelectUpdated->GetValue();
2325 bulk_update = m_cbBulkUpdate->GetValue();
2327void ChartDldrPrefsDlgImpl::SetPreferences(
bool preselect_new,
2328 bool preselect_updated,
2330 m_cbSelectNew->SetValue(preselect_new);
2331 m_cbSelectUpdated->SetValue(preselect_updated);
2332 m_cbBulkUpdate->SetValue(bulk_update);
2335void ChartDldrGuiAddSourceDlg::OnOkClick(wxCommandEvent &event) {
2336 wxString msg = wxEmptyString;
2338 if (m_nbChoice->GetSelection() == 0) {
2339 wxTreeItemId item = m_treeCtrlPredefSrcs->GetSelection();
2340 if (m_treeCtrlPredefSrcs->GetSelection().IsOk()) {
2342 (
ChartSource *)(m_treeCtrlPredefSrcs->GetItemData(item));
2345 _(
"You must select one of the predefined chart sources or create "
2346 "one of your own.\n");
2349 _(
"You must select one of the predefined chart sources or create one "
2352 if (m_nbChoice->GetSelection() == 1 &&
2353 m_tSourceName->GetValue() == wxEmptyString)
2354 msg += _(
"The chart source must have a name.\n");
2355 wxURI url(m_tChartSourceUrl->GetValue());
2356 if (m_nbChoice->GetSelection() == 1 &&
2357 (m_tChartSourceUrl->GetValue() == wxEmptyString ||
2358 !ValidateUrl(m_tChartSourceUrl->GetValue())))
2359 msg += _(
"The chart source must have a valid URL.\n");
2360 if (m_tcChartDirectory->GetValue() == wxEmptyString)
2361 msg += _(
"You must select a local folder to store the charts.\n");
2362 else if (!wxDirExists(m_tcChartDirectory->GetValue()))
2363 if (!wxFileName::Mkdir(m_tcChartDirectory->GetValue(), 0755,
2365 msg += wxString::Format(_(
"Directory %s can't be created."),
2366 m_tcChartDirectory->GetValue().c_str()) +
2369 if (msg != wxEmptyString)
2370 OCPNMessageBox_PlugIn(
this, msg, _(
"Chart source definition problem"),
2371 wxOK | wxCENTRE | wxICON_ERROR);
2374 SetReturnCode(wxID_OK);
2379void ChartDldrGuiAddSourceDlg::OnCancelClick(wxCommandEvent &event) {
2380 SetReturnCode(wxID_CANCEL);
2381 EndModal(wxID_CANCEL);
2384void ChartDldrPrefsDlgImpl::OnOkClick(wxCommandEvent &event) {
2385 if (!wxDirExists(m_tcDefaultDir->GetValue())) {
2386 if (!wxFileName::Mkdir(m_tcDefaultDir->GetValue(), 0755,
2387 wxPATH_MKDIR_FULL)) {
2388 OCPNMessageBox_PlugIn(
2390 wxString::Format(_(
"Directory %s can't be created."),
2391 m_tcDefaultDir->GetValue().c_str()),
2392 _(
"Chart Downloader"));
2398 g_pi->UpdatePrefs(
this);
2408void ChartDldrPrefsDlg::OnCancelClick(wxCommandEvent &event) {
2410 EndModal(wxID_CANCEL);
2414void ChartDldrPrefsDlg::OnOkClick(wxCommandEvent &event) {
2419bool ChartDldrGuiAddSourceDlg::ValidateUrl(
const wxString Url,
2424 _T(
"^https?\\://[a-zA-Z0-9\\./_-]*\\.[xX][mM][lL]$"));
2430 _T(
"^https?\\://[a-zA-Z0-9\\./_-]*$"));
2432 return re.Matches(Url);
2440 switch (ev.getDLEventCondition()) {
2442 m_bTransferComplete =
true;
2443 m_bTransferSuccess =
2448 if (ev.getTransferred() > m_transferredsize) {
2449 m_totalsize = ev.getTotal();
2450 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.
@ 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.
wxString * GetpSharedDataLocation(void)
Gets shared application data location.
wxWindow * GetOCPNCanvasWindow()
Gets OpenCPN's main canvas window.
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.
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.
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.
wxFileConfig * GetOCPNConfigObject(void)
Gets OpenCPN's configuration object.
void ForceChartDBUpdate()
Forces an update of the chart database.
wxString * GetpPrivateApplicationDataLocation(void)
Gets private application data directory.