31#include <wx/filedlg.h>
35#include <wx/statline.h>
36#include <wx/stattext.h>
37#include <wx/translation.h>
38#include <wx/wrapsizer.h>
41#include "androidUTIL.h"
54#include "std_filesystem.h"
55#include "svg_button.h"
58#include "user_colors_dlg.h"
61#pragma clang diagnostic push
62#pragma ide diagnostic ignored "UnreachableCode"
66#if wxCHECK_VERSION(3, 2, 0)
67#define _(s) wxGetTranslation(wxASCII_STR(s)).ToStdString()
69#define _(s) wxGetTranslation((s)).ToStdString()
72using SetFormatFunc = std::function<void(DataLogger::Format, std::string)>;
77 return dynamic_cast<T*
>(wxWindow::FindWindowById(
id));
80static const char*
const kFilterChoiceName =
"FilterChoiceWindow";
83static const std::unordered_map<NavAddr::Bus, std::string> kSourceByBus = {
84 {NavAddr::Bus::N0183,
"NMEA0183"},
85 {NavAddr::Bus::N2000,
"NMEA2000"},
86 {NavAddr::Bus::Signalk,
"SignalK"}};
89static bool IsUserFilter(
const std::string& filter_name) {
91 auto found = std::find(filters.begin(), filters.end(), filter_name);
92 if (found != filters.end())
return true;
94 filters.begin(), filters.end(),
95 [filter_name](
const std::string& f) { return f == filter_name; });
99static std::string TimeStamp(
const NavmsgTimePoint& when,
100 const NavmsgTimePoint& since) {
101 using namespace std::chrono;
104 auto duration = when - since;
105 std::stringstream ss;
106 auto hrs = duration_cast<hours>(duration) % 24;
107 duration -= duration_cast<hours>(duration) / 24;
108 auto mins = duration_cast<minutes>(duration) % 60;
109 duration -= duration_cast<minutes>(duration) / 60;
110 auto secs = duration_cast<seconds>(duration) % 60;
111 duration -= duration_cast<seconds>(duration) / 60;
112 const auto msecs = duration_cast<milliseconds>(duration);
113 ss << setw(2) << setfill(
'0') << hrs.count() <<
":" << setw(2) << mins.count()
114 <<
":" << setw(2) << secs.count() <<
"." << setw(3)
115 << msecs.count() % 1000;
119static fs::path NullLogfile() {
120 if (wxPlatformInfo::Get().GetOperatingSystemId() & wxOS_WINDOWS)
131static std::string VdrQuote(
const std::string& arg) {
132 auto static constexpr npos = std::string::npos;
133 if (arg.find(
',') == npos && arg.find(
'"') == npos)
return arg;
135 for (
const auto c : arg) {
141 return "\"" + s +
"\"";
148static void AddVdrLogline(
const Logline& ll, std::ostream& stream) {
149 if (kSourceByBus.find(ll.navmsg->bus) == kSourceByBus.end())
return;
151 using namespace std::chrono;
152 const auto now = system_clock::now();
153 const auto ms = duration_cast<milliseconds>(now.time_since_epoch()).count();
156 stream << kSourceByBus.at(ll.navmsg->bus) <<
",";
157 stream << ll.navmsg->source->iface <<
",";
158 switch (ll.navmsg->bus) {
159 case NavAddr::Bus::N0183: {
160 auto msg0183 = std::dynamic_pointer_cast<const Nmea0183Msg>(ll.navmsg);
161 stream << msg0183->talker << msg0183->type <<
",";
163 case NavAddr::Bus::N2000: {
164 auto msg2000 = std::dynamic_pointer_cast<const Nmea2000Msg>(ll.navmsg);
165 stream << msg2000->PGN.to_string() <<
",";
167 case NavAddr::Bus::Signalk: {
168 auto msgSignalK = std::dynamic_pointer_cast<const SignalkMsg>(ll.navmsg);
169 stream <<
"\"" << msgSignalK->context_self <<
"\",";
172 assert(
false &&
"Illegal message type");
174 stream << VdrQuote(ll.navmsg->to_vdr()) <<
"\n";
178static void AddStdLogline(
const Logline& ll, std::ostream& stream,
char fs,
179 const NavmsgTimePoint log_start) {
180 if (!ll.navmsg)
return;
182 ws << TimeStamp(ll.navmsg->created_at, log_start) << fs;
183 if (ll.state.direction == NavmsgStatus::Direction::kOutput)
184 ws << kUtfRightArrow << fs;
185 else if (ll.state.direction == NavmsgStatus::Direction::kInput)
186 ws << kUtfLeftwardsArrowToBar << fs;
187 else if (ll.state.direction == NavmsgStatus::Direction::kInternal)
188 ws << kUtfLeftRightArrow << fs;
190 ws << kUtfLeftArrow << fs;
191 if (ll.state.status != NavmsgStatus::State::kOk)
192 ws << kUtfMultiplicationX << fs;
193 else if (ll.state.accepted == NavmsgStatus::Accepted::kFilteredNoOutput)
194 ws << kUtfFallingDiagonal << fs;
195 else if (ll.state.accepted == NavmsgStatus::Accepted::kFilteredDropped)
196 ws << kUtfCircledDivisionSlash << fs;
198 ws << kUtfCheckMark << fs;
200 ws << ll.navmsg->source->iface << fs;
201 ws << NavAddr::BusToString(ll.navmsg->bus) << fs;
202 if (ll.state.status != NavmsgStatus::State::kOk)
203 ws << (!ll.error_msg.empty() ? ll.error_msg :
"Unknown error");
206 ws << fs << ll.message <<
"\n";
214 : wxWindow(parent, wxID_ANY), m_on_click(on_click) {
215 fs::path icon_path(
g_BasePlatform->GetSharedDataDir().ToStdString());
216 icon_path /= fs::path(
"uidata") /
"MUI_flat" /
"cross-small-symbolic.svg";
217 int size = parent->GetTextExtent(
"X").y;
218 m_bitmap =
LoadSVG(icon_path.string(), size, size);
219 assert(m_bitmap.IsOk());
220 SetInitialSize({size, size});
222 Bind(wxEVT_LEFT_DOWN, [&](wxMouseEvent&) { m_on_click(); });
223 Bind(wxEVT_PAINT, [&](wxPaintEvent& ev) { OnPaint(ev); });
227 void OnPaint(wxPaintEvent& event) {
230 dc.DrawBitmap(m_bitmap, 0, 0,
true);
234 std::function<void()> m_on_click;
240 TtyPanel(wxWindow* parent,
size_t lines)
241 : wxPanel(parent, wxID_ANY, wxDefaultPosition, wxDefaultSize,
242 wxTAB_TRAVERSAL,
"TtyPanel"),
243 m_tty_scroll(
nullptr),
244 m_filter(
this, wxID_ANY),
246 m_on_right_click([] {}) {
247 const auto vbox =
new wxBoxSizer(wxVERTICAL);
248 m_tty_scroll =
new TtyScroll(
this,
static_cast<int>(m_lines));
249 m_tty_scroll->Bind(wxEVT_RIGHT_UP,
250 [&](wxMouseEvent&) { m_on_right_click(); });
251 vbox->
Add(m_tty_scroll, wxSizerFlags(1).Expand().Border());
259 bool IsVisible()
const override {
return IsShownOnScreen(); }
261 void OnStop(
bool stop)
const {
262 m_tty_scroll->
Pause(stop);
264 m_tty_scroll->ShowScrollbars(wxSHOW_SB_DEFAULT, wxSHOW_SB_DEFAULT);
266 m_tty_scroll->ShowScrollbars(wxSHOW_SB_NEVER, wxSHOW_SB_NEVER);
271 void SetQuickFilter(
const std::string& filter)
const {
275 void SetOnRightClick(std::function<
void()> f) {
276 m_on_right_click = std::move(f);
281 auto window = wxWindow::FindWindowByName(
"TtyPanel");
283 auto tty_panel =
dynamic_cast<TtyPanel*
>(window);
284 if (tty_panel) tty_panel->
Add(ll);
288 wxSize DoGetBestClientSize()
const override {
289 return {1,
static_cast<int>(m_lines * GetCharHeight())};
296 std::function<void()> m_on_right_click;
303 std::function<
void()> on_close)
305 m_text_ctrl(
new wxTextCtrl(
this, wxID_ANY)),
306 m_on_text_evt(std::move(on_text_evt)),
307 m_on_close(std::move(on_close)) {
308 auto hbox =
new wxBoxSizer(wxHORIZONTAL);
309 auto flags = wxSizerFlags(0).Border();
310 auto label_box =
new wxBoxSizer(wxVERTICAL);
311 label_box->Add(
new wxStaticText(
this, wxID_ANY, _(
"Quick filter:")));
312 hbox->Add(label_box, flags.Align(wxALIGN_CENTER_VERTICAL));
313 hbox->Add(m_text_ctrl, flags);
314 hbox->AddStretchSpacer();
319 m_text_ctrl->Bind(wxEVT_TEXT, [&](wxCommandEvent&) { m_on_text_evt(); });
322 bool Show(
bool show)
override {
323 if (!show) m_text_ctrl->SetValue(
"");
324 return wxWindow::Show(show);
327 [[nodiscard]] std::string GetValue()
const {
328 return m_text_ctrl->GetValue().ToStdString();
332 wxTextCtrl* m_text_ctrl;
333 std::function<void()> m_on_text_evt;
334 std::function<void()> m_on_close;
341 : wxChoice(parent, wxID_ANY), m_tty_panel(tty_panel) {
342 wxWindow::SetName(kFilterChoiceName);
343 Bind(wxEVT_CHOICE, [&](wxCommandEvent&) { OnChoice(); });
344 OnFilterListChange();
345 const int ix = wxChoice::FindString(kLabels.at(
"default"));
346 if (ix != wxNOT_FOUND) wxChoice::SetSelection(ix);
347 NavmsgFilter filter = filters_on_disk::Read(
"default.filter");
348 m_tty_panel->SetFilter(filter);
351 void OnFilterListChange() {
353 int select_ix = GetSelection();
354 std::string selected;
355 if (select_ix != wxNOT_FOUND) selected = GetString(select_ix).ToStdString();
357 for (
auto& filter : m_filters) {
359 Append(kLabels.at(filter.m_name));
360 }
catch (std::out_of_range&) {
361 if (filter.m_description.empty())
362 Append(filter.m_name);
364 Append(filter.m_description);
367 if (!selected.empty()) {
368 int ix = FindString(selected);
369 SetSelection(ix == wxNOT_FOUND ? 0 : ix);
373 void OnFilterUpdate(
const std::string& name) {
375 int select_ix = GetSelection();
376 if (select_ix == wxNOT_FOUND)
return;
378 std::string selected = GetString(select_ix).ToStdString();
379 if (selected != name)
return;
382 m_tty_panel->SetFilter(filter);
385 void OnApply(
const std::string& name) {
386 int found = FindString(name);
387 if (found == wxNOT_FOUND) {
388 for (
auto& filter : m_filters) {
389 if (filter.m_name == name) {
390 found = FindString(filter.m_description);
395 if (found == wxNOT_FOUND)
return;
398 OnFilterUpdate(name);
404 const std::unordered_map<std::string, std::string> kLabels = {
405 {
"all-data", _(
"All data")},
406 {
"all-nmea", _(
"All NMEA data")},
407 {
"default", _(
"Default settings")},
408 {
"malformed", _(
"Malformed messages")},
409 {
"nmea-input", _(
"NMEA input data")},
410 {
"nmea-output", _(
"NMEA output data")},
411 {
"plugins", _(
"Messages to plugins")},
414 std::vector<NavmsgFilter> m_filters;
418 wxString label = GetString(GetSelection());
419 NavmsgFilter filter = FilterByLabel(label.ToStdString());
420 m_tty_panel->SetFilter(filter);
424 std::string name = label;
425 for (
const auto& kv : kLabels) {
426 if (kv.second == label) {
432 for (
auto& f : m_filters)
433 if (f.m_name == name)
return f;
435 for (
auto& f : m_filters)
436 if (f.m_description == label)
return f;
446 : wxButton(parent, wxID_ANY),
448 m_on_stop(std::move(on_stop)) {
449 Bind(wxEVT_BUTTON, [&](wxCommandEvent&) { OnClick(); });
455 std::function<void(
bool)> m_on_stop;
458 is_paused = !is_paused;
459 m_on_stop(is_paused);
460 SetLabel(is_paused ? _(
"Resume") : _(
"Pause"));
467 CloseButton(wxWindow* parent, std::function<
void()> on_close)
468 : wxButton(parent, wxID_ANY), m_on_close(std::move(on_close)) {
469 wxButton::SetLabel(_(
"Close"));
470 Bind(wxEVT_BUTTON, [&](wxCommandEvent&) { OnClick(); });
475 std::function<void()> m_on_close;
477 void OnClick()
const { m_on_close(); }
489 m_set_logtype(std::move(set_logtype)),
491 kFilenameLabelId(wxWindow::NewControlId()) {
492 auto flags = wxSizerFlags(0).Border();
495 auto vdr_btn =
new wxRadioButton(
this, wxID_ANY,
"VDR");
496 vdr_btn->Bind(wxEVT_RADIOBUTTON, [&](
const wxCommandEvent& e) {
497 m_set_logtype(DataLogger::Format::kVdr,
"VDR");
499 auto default_btn =
new wxRadioButton(
this, wxID_ANY,
"Default");
500 default_btn->Bind(wxEVT_RADIOBUTTON, [&](
const wxCommandEvent& e) {
501 m_set_logtype(DataLogger::Format::kDefault, _(
"Default"));
503 default_btn->SetValue(
true);
504 auto csv_btn =
new wxRadioButton(
this, wxID_ANY,
"CSV");
505 csv_btn->Bind(wxEVT_RADIOBUTTON, [&](
const wxCommandEvent& e) {
506 m_set_logtype(DataLogger::Format::kCsv,
"CSV");
508 auto left_vbox =
new wxStaticBoxSizer(wxVERTICAL,
this, _(
"Log format"));
509 left_vbox->Add(default_btn, flags.DoubleBorder());
510 left_vbox->Add(vdr_btn, flags);
511 left_vbox->Add(csv_btn, flags);
514 m_logger.SetLogfile(m_logger.GetDefaultLogfile());
515 auto label =
new wxStaticText(
this, kFilenameLabelId,
516 m_logger.GetDefaultLogfile().string());
517 auto path_btn =
new wxButton(
this, wxID_ANY, _(
"Change..."));
518 path_btn->Bind(wxEVT_BUTTON, [&](wxCommandEvent&) { OnFileDialog(); });
520 new wxCheckBox(
this, wxID_ANY, _(
"Overwrite existing file"));
521 force_box->Bind(wxEVT_CHECKBOX, [&](
const wxCommandEvent& e) {
522 m_overwrite = e.IsChecked();
524 auto right_vbox =
new wxStaticBoxSizer(wxVERTICAL,
this, _(
"Log file"));
525 right_vbox->Add(label, flags);
526 right_vbox->Add(path_btn, flags);
527 right_vbox->Add(force_box, flags);
530 auto hbox =
new wxBoxSizer(wxHORIZONTAL);
531 hbox->Add(left_vbox, flags);
532 hbox->Add(wxWindow::GetCharWidth() * 10, 0, 1);
533 hbox->Add(right_vbox, flags);
539 GetWindowById<wxStaticText>(kFilenameLabelId)->SetLabel(ev.GetString());
540 g_dm_logfile = ev.GetString();
544 void OnFileDialog()
const {
546 if (!m_overwrite)
options |= wxFD_OVERWRITE_PROMPT;
547 wxFileDialog dlg(m_parent, _(
"Select logfile"),
548 m_logger.GetDefaultLogfile().parent_path().string(),
549 m_logger.GetDefaultLogfile().stem().string(),
550 m_logger.GetFileDlgTypes(),
options);
551 if (dlg.ShowModal() == wxID_CANCEL)
return;
552 m_logger.SetLogfile(fs::path(dlg.GetPath().ToStdString()));
553 auto file_label = GetWindowById<wxStaticText>(kFilenameLabelId);
554 file_label->SetLabel(dlg.GetPath());
558 SetFormatFunc m_set_logtype;
560 const int kFilenameLabelId;
565 : wxDialog(parent, wxID_ANY, _(
"Logging setup"), wxDefaultPosition,
566 wxDefaultSize, wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER) {
567 auto flags = wxSizerFlags(0).Border();
570 auto buttons =
new wxStdDialogButtonSizer();
571 auto close_btn =
new wxButton(
this, wxID_CLOSE);
572 close_btn->Bind(wxEVT_COMMAND_BUTTON_CLICKED,
573 [&](wxCommandEvent& ev) { EndModal(0); });
574 buttons->AddButton(close_btn);
576 buttons->Fit(parent);
579 auto panel =
new ThePanel(
this, std::move(set_logtype), logger);
580 auto vbox =
new wxBoxSizer(wxVERTICAL);
581 vbox->Add(panel, flags.Expand());
582 vbox->Add(
new wxStaticLine(
this, wxID_ANY), flags.Expand());
583 vbox->Add(buttons, flags.Expand());
594 enum class Id :
char {
607 : m_parent(parent), m_logger(logger), m_is_logging_configured(
false) {
608 AppendCheckItem(
static_cast<int>(Id::kViewStdColors), _(
"Use colors"));
609 Append(
static_cast<int>(Id::kUserColors), _(
"Colors..."));
610 Append(
static_cast<int>(Id::kClear), _(
"Clear..."));
611 Append(
static_cast<int>(Id::kLogSetup), _(
"Logging..."));
612 auto filters =
new wxMenu(
"");
613 AppendId(filters, Id::kNewFilter, _(
"Create new..."));
614 AppendId(filters, Id::kEditFilter, _(
"Edit..."));
615 AppendId(filters, Id::kDeleteFilter, _(
"Delete..."));
616 AppendId(filters, Id::kRenameFilter, _(
"Rename..."));
617 AppendSubMenu(filters, _(
"Filters..."));
618 if (IsUserFilter(m_filter))
619 Append(
static_cast<int>(Id::kEditActiveFilter), _(
"Edit active filter"));
621 Bind(wxEVT_MENU, [&](
const wxCommandEvent& ev) {
622 switch (
static_cast<Id
>(ev.GetId())) {
627 case Id::kViewStdColors:
628 SetColor(static_cast<int>(Id::kViewStdColors));
632 CreateFilterDlg(parent);
635 case Id::kEditFilter:
636 EditFilterDlg(wxTheApp->GetTopWindow());
639 case Id::kRenameFilter:
640 RenameFilterDlg(wxTheApp->GetTopWindow());
643 case Id::kEditActiveFilter:
644 EditOneFilterDlg(wxTheApp->GetTopWindow(), m_filter);
647 case Id::kDeleteFilter:
648 RemoveFilterDlg(parent);
651 case Id::kUserColors:
652 UserColorsDlg(wxTheApp->GetTopWindow());
660 Check(
static_cast<int>(Id::kViewStdColors),
true);
663 void ClearLogWindow() {
664 auto* w = wxWindow::FindWindowByName(
"TtyScroll");
665 auto tty_scroll =
dynamic_cast<TtyScroll*
>(w);
666 if (tty_scroll) tty_scroll->
Clear();
669 void SetFilterName(
const std::string& filter) {
670 int id =
static_cast<int>(Id::kEditActiveFilter);
671 if (FindItem(
id)) Delete(
id);
672 if (IsUserFilter(filter)) Append(
id, _(
"Edit active filter"));
676 void ConfigureLogging() {
679 [&](DataLogger::Format f,
const std::string& s) { SetLogFormat(f, s); },
682 m_is_logging_configured =
true;
683 auto monitor = wxWindow::FindWindowByName(kDataMonitorWindowName);
688 bool IsLoggingConfigured()
const {
return m_is_logging_configured; }
691 static wxMenuItem* AppendId(wxMenu* root, Id
id,
const wxString& label) {
692 return root->Append(
static_cast<int>(
id), label);
695 void SetLogFormat(DataLogger::Format format,
const std::string& label)
const {
696 m_logger.SetFormat(format);
697 std::string extension =
698 format == DataLogger::Format::kDefault ?
".log" :
".csv";
699 fs::path path = m_logger.GetLogfile();
700 path = path.parent_path() / (path.stem().string() + extension);
701 m_logger.SetLogfile(path);
704 void SetColor(
int id)
const {
705 auto* w = wxWindow::FindWindowByName(
"TtyScroll");
706 auto tty_scroll =
dynamic_cast<TtyScroll*
>(w);
707 if (!tty_scroll)
return;
709 wxMenuItem* item = FindItem(
id);
711 if (item->IsCheck() && item->IsChecked())
712 tty_scroll->SetColors(std::make_unique<StdColorsByState>());
714 tty_scroll->SetColors(
715 std::make_unique<NoColorsByState>(tty_scroll->GetForegroundColour()));
720 std::string m_filter;
721 bool m_is_logging_configured;
728 : wxButton(parent, wxID_ANY),
733 Bind(wxEVT_BUTTON, [&](wxCommandEvent&) { OnClick(); });
738 void UpdateTooltip() {
740 SetToolTip(_(
"Click to stop logging"));
742 SetToolTip(_(
"Click to start logging"));
751 void OnClick(
bool ctor =
false) {
752 if (!m_is_inited && !ctor && !m_menu.IsLoggingConfigured()) {
753 m_menu.ConfigureLogging();
756 is_logging = !is_logging;
757 SetLabel(is_logging ? _(
"Stop logging") : _(
"Start logging"));
759 m_logger.SetLogging(is_logging);
768 SetToolTip(_(
"Copy to clipboard"));
769 Bind(wxEVT_BUTTON, [&](wxCommandEvent&) {
771 dynamic_cast<TtyScroll*
>(wxWindow::FindWindowByName(
"TtyScroll"));
781 :
SvgButton(parent), m_quick_filter(quick_filter), m_show_filter(
true) {
782 Bind(wxEVT_BUTTON, [&](wxCommandEvent&) { OnClick(); });
788 wxWindow* m_quick_filter;
792 m_quick_filter->Show(m_show_filter);
793 SetToolTip(_(
"Open quick filter"));
794 GetGrandParent()->Layout();
802 std::function<std::string()> get_current_filter)
805 m_get_current_filter(std::move(get_current_filter)) {
807 Bind(wxEVT_BUTTON, [&](wxCommandEvent&) { OnClick(); });
808 SetToolTip(_(
"Open menu"));
813 std::function<std::string()> m_get_current_filter;
816 m_menu.SetFilterName(m_get_current_filter());
825 std::function<
void(
bool)> on_stop,
826 const std::function<
void()>& on_hide,
DataLogger& logger)
830 m_menu(
this, logger),
831 m_log_button(
new LogButton(
this, logger, m_menu)) {
833 auto filter_label_box =
new wxBoxSizer(wxVERTICAL);
834 filter_label_box->Add(
new wxStaticText(
this, wxID_ANY, _(
"Filter")));
836 auto flags = wxSizerFlags(0).Border();
837 auto wbox =
new wxWrapSizer(wxHORIZONTAL);
838 wbox->Add(m_log_button, flags);
841 wbox->Add(wxWindow::GetCharWidth() * 5, 0, 1);
842 wbox->Add(filter_label_box, flags.Align(wxALIGN_CENTER_VERTICAL));
843 wbox->Add(m_filter_choice, flags);
846 auto get_current_filter = [&] {
847 return m_filter_choice->GetStringSelection().ToStdString();
850 wbox->Add(
new MenuButton(
this, m_menu, get_current_filter), flags);
852 wbox->Add(
new CloseButton(
this, std::move(on_hide)), flags);
858 Bind(wxEVT_SIZE, [&](wxSizeEvent& ev) {
862 Bind(wxEVT_RIGHT_UP, [&](wxMouseEvent& ev) {
863 m_menu.SetFilterName(m_filter_choice->GetStringSelection().ToStdString());
868 void OnContextClick() {
869 m_menu.SetFilterName(m_filter_choice->GetStringSelection().ToStdString());
876 [[nodiscard]] wxSize DoGetBestClientSize()
const override {
880 return {85 * GetCharWidth(), 5 * GetCharHeight() / 2};
885 wxChoice* m_filter_choice;
887 wxButton* m_log_button;
890DataLogger::DataLogger(wxWindow* parent,
const fs::path& path)
893 m_stream(path, std::ios_base::app),
895 m_format(Format::kDefault),
896 m_log_start(NavmsgClock::now()) {}
898DataLogger::DataLogger(wxWindow* parent) :
DataLogger(parent, NullLogfile()) {}
900void DataLogger::SetLogging(
bool logging) { m_is_logging = logging; }
902void DataLogger::SetLogfile(
const fs::path& path) {
903 m_stream = std::ofstream(path);
904 m_stream <<
"# timestamp_format: EPOCH_MILLIS\n";
905 const auto now = std::chrono::system_clock::now();
906 const std::time_t t_c = std::chrono::system_clock::to_time_t(now);
907 m_stream <<
"# Created at: " << std::ctime(&t_c) <<
" \n";
908 m_stream <<
"received_at,protocol,source,msg_type,raw_data\n";
909 m_stream << std::flush;
914void DataLogger::SetFormat(DataLogger::Format format) { m_format = format; }
916fs::path DataLogger::GetDefaultLogfile() {
918 if (m_path.stem() != NullLogfile().stem())
return m_path;
921 path += (m_format == Format::kDefault ?
".log" :
".csv");
925std::string DataLogger::GetFileDlgTypes()
const {
926 if (m_format == Format::kDefault)
927 return _(
"Log file (*.log)|*.log");
929 return _(
"Spreadsheet csv file(*.csv)|*.csv");
932void DataLogger::Add(
const Logline& ll) {
933 if (!m_is_logging || !ll.navmsg)
return;
934 if (m_format == Format::kVdr && ll.navmsg->to_vdr().empty())
return;
935 if (m_format == DataLogger::Format::kVdr)
936 AddVdrLogline(ll, m_stream);
938 AddStdLogline(ll, m_stream,
939 m_format == DataLogger::Format::kCsv ?
'|' :
' ',
943DataMonitor::DataMonitor(wxWindow* parent)
944 : wxFrame(parent, wxID_ANY, _(
"Data Monitor"), wxPoint(0, 0), wxDefaultSize,
945 wxDEFAULT_FRAME_STYLE | wxFRAME_FLOAT_ON_PARENT,
946 kDataMonitorWindowName),
947 m_monitor_src([&](const std::shared_ptr<const
NavMsg>& navmsg) {
950 m_quick_filter(
nullptr),
952 auto vbox =
new wxBoxSizer(wxVERTICAL);
953 auto tty_panel =
new TtyPanel(
this, 12);
954 vbox->Add(tty_panel, wxSizerFlags(1).Expand().Border());
955 vbox->Add(
new wxStaticLine(
this), wxSizerFlags().Expand().Border());
957 auto on_quick_filter_evt = [&, tty_panel] {
959 assert(quick_filter);
960 std::string value = quick_filter->GetValue();
961 tty_panel->SetQuickFilter(value);
963 auto on_dismiss = [&] {
964 m_quick_filter->Hide();
967 m_quick_filter =
new QuickFilterPanel(
this, on_quick_filter_evt, on_dismiss);
968 vbox->Add(m_quick_filter, wxSizerFlags().Expand());
970 auto on_stop = [&, tty_panel](
bool stop) { tty_panel->OnStop(stop); };
971 auto on_close = [&,
this]() { this->OnHide(); };
972 auto status_line =
new StatusLine(
this, m_quick_filter, tty_panel, on_stop,
974 vbox->Add(status_line, wxSizerFlags().Expand());
979 m_quick_filter->Bind(wxEVT_TEXT, [&, tty_panel](wxCommandEvent&) {
980 tty_panel->SetQuickFilter(GetLabel().ToStdString());
982 m_quick_filter->Hide();
983 tty_panel->SetOnRightClick(
984 [&, status_line] { status_line->OnContextClick(); });
986 Bind(wxEVT_CLOSE_WINDOW, [
this](wxCloseEvent& ev) { Hide(); });
987 Bind(wxEVT_RIGHT_UP, [status_line](wxMouseEvent& ev) {
988 status_line->OnContextClick();
991 m_filter_list_lstnr.Init(FilterEvents::GetInstance().filter_list_change,
993 m_filter_update_lstnr.Init(FilterEvents::GetInstance().filter_update,
995 OnFilterUpdate(ev.GetString().ToStdString());
998 m_filter_apply_lstnr.Init(FilterEvents::GetInstance().filter_apply,
1000 OnFilterApply(ev.GetString().ToStdString());
1010 wxWindow* w = wxWindow::FindWindowByName(
"TtyPanel");
1011 assert(w &&
"No TtyPanel found");
1012 return w->IsShownOnScreen();
1015void DataMonitor::OnFilterListChange() {
1016 wxWindow* w = wxWindow::FindWindowByName(kFilterChoiceName);
1019 assert(filter_choice &&
"Wrong FilterChoice type (!)");
1020 filter_choice->OnFilterListChange();
1023void DataMonitor::OnFilterUpdate(
const std::string& name)
const {
1024 if (name != m_current_filter)
return;
1025 wxWindow* w = wxWindow::FindWindowByName(
"TtyScroll");
1027 auto tty_scroll =
dynamic_cast<TtyScroll*
>(w);
1028 assert(tty_scroll &&
"Wrong TtyScroll type (!)");
1029 tty_scroll->SetFilter(filters_on_disk::Read(name));
1032void DataMonitor::OnFilterApply(
const std::string& name) {
1033 wxWindow* w = wxWindow::FindWindowByName(kFilterChoiceName);
1036 assert(filter_choice &&
"Wrong FilterChoice type (!)");
1037 m_current_filter = name;
1038 filter_choice->OnApply(name);
1041void DataMonitor::OnHide() { Hide(); }
1043#pragma clang diagnostic pop
Clickable crossmark icon window.
EventVar OnNewLogfile
Notified with new path on filename change.
void Add(const Logline &ll) override
Add an input line to log output.
bool IsVisible() const override
Return true if log is visible i.e., if it's any point using Add().
void Notify() override
Notify all listeners, no data supplied.
Offer user to select current filter.
Log setup window invoked from menu "Logging" item.
Actual data sent between application and transport layer.
static std::vector< NavmsgFilter > GetAllFilters()
Return list of all filters, system + user defined.
Define an action to be performed when a KeyProvider is notified.
void Init(const KeyProvider &kp, const std::function< void(ObservedEvt &ev)> &action)
Initiate an object yet not listening.
Custom event class for OpenCPN's notification system.
The quick filter above the status line, invoked by funnel button.
Overall bottom status line.
Main window, a rolling log of messages.
void Add(const Logline &ll) override
Add a formatted string to log output.
static void AddIfExists(const Logline &ll)
Invoke Add(s) for possibly existing instance.
bool IsVisible() const override
Return true if log is visible i.e., if it's any point using Add().
wxString g_dm_logfile
Last Data Monitor log file.
Global variables stored in configuration file.
T * GetWindowById(int id)
Return window with given id (which must exist) cast to T*.
New NMEA Debugger successor main window.
Provide a data stream of input messages for the Data Monitor.
Dialogs handing user defined filters.
std::vector< std::string > List(bool include_system)
Return list of filters, possibly including also the system ones.
Data Monitor filter storage routines.
Hooks into gui available in model.
Data monitor filter definitions.
Basic DataMonitor logging interface: LogLine (reflects a line in the log) and NmeaLog,...
wxBitmap LoadSVG(const wxString filename, const unsigned int width, const unsigned int height, wxBitmap *default_bitmap, bool use_cache)
Load SVG file and return it's bitmap representation of requested size In case file can't be loaded an...