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"
44#include "libgui/svg_button.h"
56#include "std_filesystem.h"
59#include "user_colors_dlg.h"
62#pragma clang diagnostic push
63#pragma ide diagnostic ignored "UnreachableCode"
67#if wxCHECK_VERSION(3, 2, 0)
68#define _(s) wxGetTranslation(wxASCII_STR(s)).ToStdString()
70#define _(s) wxGetTranslation((s)).ToStdString()
73using SetFormatFunc = std::function<void(DataLogger::Format, std::string)>;
78 return dynamic_cast<T*
>(wxWindow::FindWindowById(
id));
81static const char*
const kFilterChoiceName =
"FilterChoiceWindow";
84static const std::unordered_map<NavAddr::Bus, std::string> kSourceByBus = {
85 {NavAddr::Bus::N0183,
"NMEA0183"},
86 {NavAddr::Bus::N2000,
"NMEA2000"},
87 {NavAddr::Bus::Signalk,
"SignalK"}};
90static bool IsUserFilter(
const std::string& filter_name) {
92 auto found = std::find(filters.begin(), filters.end(), filter_name);
93 if (found != filters.end())
return true;
95 filters.begin(), filters.end(),
96 [filter_name](
const std::string& f) { return f == filter_name; });
100static std::string TimeStamp(
const NavmsgTimePoint& when,
101 const NavmsgTimePoint& since) {
102 using namespace std::chrono;
105 auto duration = when - since;
106 std::stringstream ss;
107 auto hrs = duration_cast<hours>(duration) % 24;
108 duration -= duration_cast<hours>(duration) / 24;
109 auto mins = duration_cast<minutes>(duration) % 60;
110 duration -= duration_cast<minutes>(duration) / 60;
111 auto secs = duration_cast<seconds>(duration) % 60;
112 duration -= duration_cast<seconds>(duration) / 60;
113 const auto msecs = duration_cast<milliseconds>(duration);
114 ss << setw(2) << setfill(
'0') << hrs.count() <<
":" << setw(2) << mins.count()
115 <<
":" << setw(2) << secs.count() <<
"." << setw(3)
116 << msecs.count() % 1000;
120static fs::path NullLogfile() {
121 if (wxPlatformInfo::Get().GetOperatingSystemId() & wxOS_WINDOWS)
132static std::string VdrQuote(
const std::string& arg) {
133 auto static constexpr npos = std::string::npos;
134 if (arg.find(
',') == npos && arg.find(
'"') == npos)
return arg;
136 for (
const auto c : arg) {
142 return "\"" + s +
"\"";
149static void AddVdrLogline(
const Logline& ll, std::ostream& stream) {
150 if (kSourceByBus.find(ll.navmsg->bus) == kSourceByBus.end())
return;
152 using namespace std::chrono;
153 const auto now = system_clock::now();
154 const auto ms = duration_cast<milliseconds>(now.time_since_epoch()).count();
157 stream << kSourceByBus.at(ll.navmsg->bus) <<
",";
158 stream << ll.navmsg->source->iface <<
",";
159 switch (ll.navmsg->bus) {
160 case NavAddr::Bus::N0183: {
161 auto msg0183 = std::dynamic_pointer_cast<const Nmea0183Msg>(ll.navmsg);
162 stream << msg0183->talker << msg0183->type <<
",";
164 case NavAddr::Bus::N2000: {
165 auto msg2000 = std::dynamic_pointer_cast<const Nmea2000Msg>(ll.navmsg);
166 stream << msg2000->PGN.to_string() <<
",";
168 case NavAddr::Bus::Signalk: {
169 auto msgSignalK = std::dynamic_pointer_cast<const SignalkMsg>(ll.navmsg);
170 stream <<
"\"" << msgSignalK->context_self <<
"\",";
173 assert(
false &&
"Illegal message type");
175 stream << VdrQuote(ll.navmsg->to_vdr()) <<
"\n";
179static void AddStdLogline(
const Logline& ll, std::ostream& stream,
char fs,
180 const NavmsgTimePoint log_start) {
181 if (!ll.navmsg)
return;
183 ws << TimeStamp(ll.navmsg->created_at, log_start) << fs;
184 if (ll.state.direction == NavmsgStatus::Direction::kOutput)
185 ws << kUtfRightArrow << fs;
186 else if (ll.state.direction == NavmsgStatus::Direction::kInput)
187 ws << kUtfLeftwardsArrowToBar << fs;
188 else if (ll.state.direction == NavmsgStatus::Direction::kInternal)
189 ws << kUtfLeftRightArrow << fs;
191 ws << kUtfLeftArrow << fs;
192 if (ll.state.status != NavmsgStatus::State::kOk)
193 ws << kUtfMultiplicationX << fs;
194 else if (ll.state.accepted == NavmsgStatus::Accepted::kFilteredNoOutput)
195 ws << kUtfFallingDiagonal << fs;
196 else if (ll.state.accepted == NavmsgStatus::Accepted::kFilteredDropped)
197 ws << kUtfCircledDivisionSlash << fs;
199 ws << kUtfCheckMark << fs;
201 ws << ll.navmsg->source->iface << fs;
202 ws << NavAddr::BusToString(ll.navmsg->bus) << fs;
203 if (ll.state.status != NavmsgStatus::State::kOk)
204 ws << (!ll.error_msg.empty() ? ll.error_msg :
"Unknown error");
207 ws << fs << ll.message <<
"\n";
215 : wxWindow(parent, wxID_ANY), m_on_click(on_click) {
216 fs::path icon_path(
g_BasePlatform->GetSharedDataDir().ToStdString());
217 icon_path /= fs::path(
"uidata") /
"MUI_flat" /
"cross-small-symbolic.svg";
218 int size = parent->GetTextExtent(
"X").y;
219 m_bitmap =
LoadSVG(icon_path.string(), size, size);
220 assert(m_bitmap.IsOk());
221 SetInitialSize({size, size});
223 Bind(wxEVT_LEFT_DOWN, [&](wxMouseEvent&) { m_on_click(); });
224 Bind(wxEVT_PAINT, [&](wxPaintEvent& ev) { OnPaint(ev); });
228 void OnPaint(wxPaintEvent& event) {
231 dc.DrawBitmap(m_bitmap, 0, 0,
true);
235 std::function<void()> m_on_click;
241 TtyPanel(wxWindow* parent,
size_t lines)
242 : wxPanel(parent, wxID_ANY, wxDefaultPosition, wxDefaultSize,
243 wxTAB_TRAVERSAL,
"TtyPanel"),
244 m_tty_scroll(
nullptr),
245 m_filter(
this, wxID_ANY),
247 m_on_right_click([] {}) {
248 const auto vbox =
new wxBoxSizer(wxVERTICAL);
249 m_tty_scroll =
new TtyScroll(
this,
static_cast<int>(m_lines));
250 m_tty_scroll->Bind(wxEVT_RIGHT_UP,
251 [&](wxMouseEvent&) { m_on_right_click(); });
252 vbox->
Add(m_tty_scroll, wxSizerFlags(1).Expand().Border());
260 bool IsVisible()
const override {
return IsShownOnScreen(); }
262 void OnStop(
bool stop)
const {
263 m_tty_scroll->
Pause(stop);
265 m_tty_scroll->ShowScrollbars(wxSHOW_SB_DEFAULT, wxSHOW_SB_DEFAULT);
267 m_tty_scroll->ShowScrollbars(wxSHOW_SB_NEVER, wxSHOW_SB_NEVER);
272 void SetQuickFilter(
const std::string& filter)
const {
276 void SetOnRightClick(std::function<
void()> f) {
277 m_on_right_click = std::move(f);
282 auto window = wxWindow::FindWindowByName(
"TtyPanel");
284 auto tty_panel =
dynamic_cast<TtyPanel*
>(window);
285 if (tty_panel) tty_panel->
Add(ll);
289 wxSize DoGetBestClientSize()
const override {
290 return {1,
static_cast<int>(m_lines * GetCharHeight())};
297 std::function<void()> m_on_right_click;
304 std::function<
void()> on_close)
306 m_text_ctrl(
new wxTextCtrl(
this, wxID_ANY)),
307 m_on_text_evt(std::move(on_text_evt)),
308 m_on_close(std::move(on_close)) {
309 auto hbox =
new wxBoxSizer(wxHORIZONTAL);
310 auto flags = wxSizerFlags(0).Border();
311 auto label_box =
new wxBoxSizer(wxVERTICAL);
312 label_box->Add(
new wxStaticText(
this, wxID_ANY, _(
"Quick filter:")));
313 hbox->Add(label_box, flags.Align(wxALIGN_CENTER_VERTICAL));
314 hbox->Add(m_text_ctrl, flags);
315 hbox->AddStretchSpacer();
320 m_text_ctrl->Bind(wxEVT_TEXT, [&](wxCommandEvent&) { m_on_text_evt(); });
323 bool Show(
bool show)
override {
324 if (!show) m_text_ctrl->SetValue(
"");
325 return wxWindow::Show(show);
328 [[nodiscard]] std::string GetValue()
const {
329 return m_text_ctrl->GetValue().ToStdString();
333 wxTextCtrl* m_text_ctrl;
334 std::function<void()> m_on_text_evt;
335 std::function<void()> m_on_close;
342 : wxChoice(parent, wxID_ANY), m_tty_panel(tty_panel) {
343 wxWindow::SetName(kFilterChoiceName);
344 Bind(wxEVT_CHOICE, [&](wxCommandEvent&) { OnChoice(); });
345 OnFilterListChange();
346 const int ix = wxChoice::FindString(kLabels.at(
"default"));
347 if (ix != wxNOT_FOUND) wxChoice::SetSelection(ix);
348 NavmsgFilter filter = filters_on_disk::Read(
"default.filter");
349 m_tty_panel->SetFilter(filter);
352 void OnFilterListChange() {
354 int select_ix = GetSelection();
355 std::string selected;
356 if (select_ix != wxNOT_FOUND) selected = GetString(select_ix).ToStdString();
358 for (
auto& filter : m_filters) {
360 Append(kLabels.at(filter.m_name));
361 }
catch (std::out_of_range&) {
362 if (filter.m_description.empty())
363 Append(filter.m_name);
365 Append(filter.m_description);
368 if (!selected.empty()) {
369 int ix = FindString(selected);
370 SetSelection(ix == wxNOT_FOUND ? 0 : ix);
374 void OnFilterUpdate(
const std::string& name) {
376 int select_ix = GetSelection();
377 if (select_ix == wxNOT_FOUND)
return;
379 std::string selected = GetString(select_ix).ToStdString();
380 if (selected != name)
return;
383 m_tty_panel->SetFilter(filter);
386 void OnApply(
const std::string& name) {
387 int found = FindString(name);
388 if (found == wxNOT_FOUND) {
389 for (
auto& filter : m_filters) {
390 if (filter.m_name == name) {
391 found = FindString(filter.m_description);
396 if (found == wxNOT_FOUND)
return;
399 OnFilterUpdate(name);
405 const std::unordered_map<std::string, std::string> kLabels = {
406 {
"all-data", _(
"All data")},
407 {
"all-nmea", _(
"All NMEA data")},
408 {
"default", _(
"Default settings")},
409 {
"malformed", _(
"Malformed messages")},
410 {
"nmea-input", _(
"NMEA input data")},
411 {
"nmea-output", _(
"NMEA output data")},
412 {
"plugins", _(
"Messages to plugins")},
415 std::vector<NavmsgFilter> m_filters;
419 wxString label = GetString(GetSelection());
420 NavmsgFilter filter = FilterByLabel(label.ToStdString());
421 m_tty_panel->SetFilter(filter);
425 std::string name = label;
426 for (
const auto& kv : kLabels) {
427 if (kv.second == label) {
433 for (
auto& f : m_filters)
434 if (f.m_name == name)
return f;
436 for (
auto& f : m_filters)
437 if (f.m_description == label)
return f;
447 : wxButton(parent, wxID_ANY),
449 m_on_stop(std::move(on_stop)) {
450 Bind(wxEVT_BUTTON, [&](wxCommandEvent&) { OnClick(); });
456 std::function<void(
bool)> m_on_stop;
459 is_paused = !is_paused;
460 m_on_stop(is_paused);
461 SetLabel(is_paused ? _(
"Resume") : _(
"Pause"));
468 CloseButton(wxWindow* parent, std::function<
void()> on_close)
469 : wxButton(parent, wxID_ANY), m_on_close(std::move(on_close)) {
470 wxButton::SetLabel(_(
"Close"));
471 Bind(wxEVT_BUTTON, [&](wxCommandEvent&) { OnClick(); });
476 std::function<void()> m_on_close;
478 void OnClick()
const { m_on_close(); }
490 m_set_logtype(std::move(set_logtype)),
492 kFilenameLabelId(wxWindow::NewControlId()) {
493 auto flags = wxSizerFlags(0).Border();
496 auto vdr_btn =
new wxRadioButton(
this, wxID_ANY,
"VDR");
497 vdr_btn->Bind(wxEVT_RADIOBUTTON, [&](
const wxCommandEvent& e) {
498 m_set_logtype(DataLogger::Format::kVdr,
"VDR");
500 auto default_btn =
new wxRadioButton(
this, wxID_ANY,
"Default");
501 default_btn->Bind(wxEVT_RADIOBUTTON, [&](
const wxCommandEvent& e) {
502 m_set_logtype(DataLogger::Format::kDefault, _(
"Default"));
504 default_btn->SetValue(
true);
505 auto csv_btn =
new wxRadioButton(
this, wxID_ANY,
"CSV");
506 csv_btn->Bind(wxEVT_RADIOBUTTON, [&](
const wxCommandEvent& e) {
507 m_set_logtype(DataLogger::Format::kCsv,
"CSV");
509 auto left_vbox =
new wxStaticBoxSizer(wxVERTICAL,
this, _(
"Log format"));
510 left_vbox->Add(default_btn, flags.DoubleBorder());
511 left_vbox->Add(vdr_btn, flags);
512 left_vbox->Add(csv_btn, flags);
515 m_logger.SetLogfile(m_logger.GetDefaultLogfile());
516 auto label =
new wxStaticText(
this, kFilenameLabelId,
517 m_logger.GetDefaultLogfile().string());
518 auto path_btn =
new wxButton(
this, wxID_ANY, _(
"Change..."));
519 path_btn->Bind(wxEVT_BUTTON, [&](wxCommandEvent&) { OnFileDialog(); });
521 new wxCheckBox(
this, wxID_ANY, _(
"Overwrite existing file"));
522 force_box->Bind(wxEVT_CHECKBOX, [&](
const wxCommandEvent& e) {
523 m_overwrite = e.IsChecked();
525 auto right_vbox =
new wxStaticBoxSizer(wxVERTICAL,
this, _(
"Log file"));
526 right_vbox->Add(label, flags);
527 right_vbox->Add(path_btn, flags);
528 right_vbox->Add(force_box, flags);
531 auto hbox =
new wxBoxSizer(wxHORIZONTAL);
532 hbox->Add(left_vbox, flags);
533 hbox->Add(wxWindow::GetCharWidth() * 10, 0, 1);
534 hbox->Add(right_vbox, flags);
540 GetWindowById<wxStaticText>(kFilenameLabelId)->SetLabel(ev.GetString());
541 g_dm_logfile = ev.GetString();
545 void OnFileDialog()
const {
547 if (!m_overwrite)
options |= wxFD_OVERWRITE_PROMPT;
548 wxFileDialog dlg(m_parent, _(
"Select logfile"),
549 m_logger.GetDefaultLogfile().parent_path().string(),
550 m_logger.GetDefaultLogfile().stem().string(),
551 m_logger.GetFileDlgTypes(),
options);
552 if (dlg.ShowModal() == wxID_CANCEL)
return;
553 m_logger.SetLogfile(fs::path(dlg.GetPath().ToStdString()));
554 auto file_label = GetWindowById<wxStaticText>(kFilenameLabelId);
555 file_label->SetLabel(dlg.GetPath());
559 SetFormatFunc m_set_logtype;
561 const int kFilenameLabelId;
562 obs::Listener FilenameLstnr;
566 : wxDialog(parent, wxID_ANY, _(
"Logging setup"), wxDefaultPosition,
567 wxDefaultSize, wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER) {
568 auto flags = wxSizerFlags(0).Border();
571 auto buttons =
new wxStdDialogButtonSizer();
572 auto close_btn =
new wxButton(
this, wxID_CLOSE);
573 close_btn->Bind(wxEVT_COMMAND_BUTTON_CLICKED,
574 [&](wxCommandEvent& ev) { EndModal(0); });
575 buttons->AddButton(close_btn);
577 buttons->Fit(parent);
580 auto panel =
new ThePanel(
this, std::move(set_logtype), logger);
581 auto vbox =
new wxBoxSizer(wxVERTICAL);
582 vbox->Add(panel, flags.Expand());
583 vbox->Add(
new wxStaticLine(
this, wxID_ANY), flags.Expand());
584 vbox->Add(buttons, flags.Expand());
589 obs::Listener FilenameLstnr;
595 enum class Id :
char {
608 : m_parent(parent), m_logger(logger), m_is_logging_configured(
false) {
609 AppendCheckItem(
static_cast<int>(Id::kViewStdColors), _(
"Use colors"));
610 Append(
static_cast<int>(Id::kUserColors), _(
"Colors..."));
611 Append(
static_cast<int>(Id::kClear), _(
"Clear..."));
612 Append(
static_cast<int>(Id::kLogSetup), _(
"Logging..."));
613 auto filters =
new wxMenu(
"");
614 AppendId(filters, Id::kNewFilter, _(
"Create new..."));
615 AppendId(filters, Id::kEditFilter, _(
"Edit..."));
616 AppendId(filters, Id::kDeleteFilter, _(
"Delete..."));
617 AppendId(filters, Id::kRenameFilter, _(
"Rename..."));
618 AppendSubMenu(filters, _(
"Filters..."));
619 if (IsUserFilter(m_filter))
620 Append(
static_cast<int>(Id::kEditActiveFilter), _(
"Edit active filter"));
622 Bind(wxEVT_MENU, [&](
const wxCommandEvent& ev) {
623 switch (
static_cast<Id
>(ev.GetId())) {
628 case Id::kViewStdColors:
629 SetColor(static_cast<int>(Id::kViewStdColors));
633 CreateFilterDlg(parent);
636 case Id::kEditFilter:
637 EditFilterDlg(wxTheApp->GetTopWindow());
640 case Id::kRenameFilter:
641 RenameFilterDlg(wxTheApp->GetTopWindow());
644 case Id::kEditActiveFilter:
645 EditOneFilterDlg(wxTheApp->GetTopWindow(), m_filter);
648 case Id::kDeleteFilter:
649 RemoveFilterDlg(parent);
652 case Id::kUserColors:
653 UserColorsDlg(wxTheApp->GetTopWindow());
661 Check(
static_cast<int>(Id::kViewStdColors),
true);
664 void ClearLogWindow() {
665 auto* w = wxWindow::FindWindowByName(
"TtyScroll");
666 auto tty_scroll =
dynamic_cast<TtyScroll*
>(w);
667 if (tty_scroll) tty_scroll->
Clear();
670 void SetFilterName(
const std::string& filter) {
671 int id =
static_cast<int>(Id::kEditActiveFilter);
672 if (FindItem(
id)) Delete(
id);
673 if (IsUserFilter(filter)) Append(
id, _(
"Edit active filter"));
677 void ConfigureLogging() {
680 [&](DataLogger::Format f,
const std::string& s) { SetLogFormat(f, s); },
683 m_is_logging_configured =
true;
684 auto monitor = wxWindow::FindWindowByName(kDataMonitorWindowName);
689 bool IsLoggingConfigured()
const {
return m_is_logging_configured; }
692 static wxMenuItem* AppendId(wxMenu* root, Id
id,
const wxString& label) {
693 return root->Append(
static_cast<int>(
id), label);
696 void SetLogFormat(DataLogger::Format format,
const std::string& label)
const {
697 m_logger.SetFormat(format);
698 std::string extension =
699 format == DataLogger::Format::kDefault ?
".log" :
".csv";
700 fs::path path = m_logger.GetLogfile();
701 path = path.parent_path() / (path.stem().string() + extension);
702 m_logger.SetLogfile(path);
705 void SetColor(
int id)
const {
706 auto* w = wxWindow::FindWindowByName(
"TtyScroll");
707 auto tty_scroll =
dynamic_cast<TtyScroll*
>(w);
708 if (!tty_scroll)
return;
710 wxMenuItem* item = FindItem(
id);
712 if (item->IsCheck() && item->IsChecked())
713 tty_scroll->SetColors(std::make_unique<StdColorsByState>());
715 tty_scroll->SetColors(
716 std::make_unique<NoColorsByState>(tty_scroll->GetForegroundColour()));
721 std::string m_filter;
722 bool m_is_logging_configured;
729 : wxButton(parent, wxID_ANY),
734 Bind(wxEVT_BUTTON, [&](wxCommandEvent&) { OnClick(); });
739 void UpdateTooltip() {
741 SetToolTip(_(
"Click to stop logging"));
743 SetToolTip(_(
"Click to start logging"));
752 void OnClick(
bool ctor =
false) {
753 if (!m_is_inited && !ctor && !m_menu.IsLoggingConfigured()) {
754 m_menu.ConfigureLogging();
757 is_logging = !is_logging;
758 SetLabel(is_logging ? _(
"Stop logging") : _(
"Start logging"));
760 m_logger.SetLogging(is_logging);
769 SetToolTip(_(
"Copy to clipboard"));
770 Bind(wxEVT_BUTTON, [&](wxCommandEvent&) {
772 dynamic_cast<TtyScroll*
>(wxWindow::FindWindowByName(
"TtyScroll"));
782 :
SvgButton(parent), m_quick_filter(quick_filter), m_show_filter(
true) {
783 Bind(wxEVT_BUTTON, [&](wxCommandEvent&) { OnClick(); });
789 wxWindow* m_quick_filter;
793 m_quick_filter->Show(m_show_filter);
794 SetToolTip(_(
"Open quick filter"));
795 GetGrandParent()->Layout();
796 m_quick_filter->SetFocus();
804 std::function<std::string()> get_current_filter)
807 m_get_current_filter(std::move(get_current_filter)) {
809 Bind(wxEVT_BUTTON, [&](wxCommandEvent&) { OnClick(); });
810 SetToolTip(_(
"Open menu"));
815 std::function<std::string()> m_get_current_filter;
818 m_menu.SetFilterName(m_get_current_filter());
827 std::function<
void(
bool)> on_stop,
828 const std::function<
void()>& on_hide,
DataLogger& logger)
832 m_menu(
this, logger),
833 m_log_button(
new LogButton(
this, logger, m_menu)) {
835 auto filter_label_box =
new wxBoxSizer(wxVERTICAL);
836 filter_label_box->Add(
new wxStaticText(
this, wxID_ANY, _(
"Filter")));
838 auto flags = wxSizerFlags(0).Border();
839 auto wbox =
new wxWrapSizer(wxHORIZONTAL);
840 wbox->Add(m_log_button, flags);
843 wbox->Add(wxWindow::GetCharWidth() * 5, 0, 1);
844 wbox->Add(filter_label_box, flags.Align(wxALIGN_CENTER_VERTICAL));
845 wbox->Add(m_filter_choice, flags);
848 auto get_current_filter = [&] {
849 return m_filter_choice->GetStringSelection().ToStdString();
852 wbox->Add(
new MenuButton(
this, m_menu, get_current_filter), flags);
854 wbox->Add(
new CloseButton(
this, std::move(on_hide)), flags);
860 Bind(wxEVT_SIZE, [&](wxSizeEvent& ev) {
864 Bind(wxEVT_RIGHT_UP, [&](wxMouseEvent& ev) {
865 m_menu.SetFilterName(m_filter_choice->GetStringSelection().ToStdString());
870 void OnContextClick() {
871 m_menu.SetFilterName(m_filter_choice->GetStringSelection().ToStdString());
878 [[nodiscard]] wxSize DoGetBestClientSize()
const override {
882 return {85 * GetCharWidth(), 5 * GetCharHeight() / 2};
887 wxChoice* m_filter_choice;
889 wxButton* m_log_button;
892DataLogger::DataLogger(wxWindow* parent,
const fs::path& path)
895 m_stream(path, std::ios_base::app),
897 m_format(Format::kDefault),
898 m_log_start(NavmsgClock::now()) {}
900DataLogger::DataLogger(wxWindow* parent) :
DataLogger(parent, NullLogfile()) {}
902void DataLogger::SetLogging(
bool logging) { m_is_logging = logging; }
904void DataLogger::SetLogfile(
const fs::path& path) {
905 const auto now = std::chrono::system_clock::now();
906 const std::time_t t_c = std::chrono::system_clock::to_time_t(now);
908 std::stringstream ss;
909 ss <<
"# timestamp_format: EPOCH_MILLIS\n";
910 ss <<
"# Created at: " << std::ctime(&t_c) <<
" \n";
911 ss <<
"received_at,protocol,source,msg_type,raw_data\n";
916void DataLogger::SetFormat(DataLogger::Format format) { m_format = format; }
918fs::path DataLogger::GetDefaultLogfile() {
920 if (m_path.stem() != NullLogfile().stem())
return m_path;
923 path += (m_format == Format::kDefault ?
".log" :
".csv");
927std::string DataLogger::GetFileDlgTypes()
const {
928 if (m_format == Format::kDefault)
929 return _(
"Log file (*.log)|*.log");
931 return _(
"Spreadsheet csv file(*.csv)|*.csv");
934void DataLogger::Add(
const Logline& ll) {
935 if (!m_is_logging || !ll.navmsg)
return;
936 if (!m_header.empty()) {
937 m_stream = std::ofstream(m_path);
938 m_stream << m_header;
941 if (m_format == Format::kVdr && ll.navmsg->to_vdr().empty())
return;
942 if (m_format == DataLogger::Format::kVdr)
943 AddVdrLogline(ll, m_stream);
945 AddStdLogline(ll, m_stream,
946 m_format == DataLogger::Format::kCsv ?
'|' :
' ',
950DataMonitor::DataMonitor(wxWindow* parent)
951 : wxFrame(parent, wxID_ANY, _(
"Data Monitor"), wxPoint(0, 0), wxDefaultSize,
952 wxDEFAULT_FRAME_STYLE | wxFRAME_FLOAT_ON_PARENT,
953 kDataMonitorWindowName),
954 m_monitor_src([&](const std::shared_ptr<const
NavMsg>& navmsg) {
957 m_quick_filter(
nullptr),
959 auto vbox =
new wxBoxSizer(wxVERTICAL);
960 auto tty_panel =
new TtyPanel(
this, 12);
961 vbox->Add(tty_panel, wxSizerFlags(1).Expand().Border());
962 vbox->Add(
new wxStaticLine(
this), wxSizerFlags().Expand().Border());
964 auto on_quick_filter_evt = [&, tty_panel] {
966 assert(quick_filter);
967 std::string value = quick_filter->GetValue();
968 tty_panel->SetQuickFilter(value);
970 auto on_dismiss = [&] {
971 m_quick_filter->Hide();
974 m_quick_filter =
new QuickFilterPanel(
this, on_quick_filter_evt, on_dismiss);
975 vbox->Add(m_quick_filter, wxSizerFlags().Expand());
977 auto on_stop = [&, tty_panel](
bool stop) { tty_panel->OnStop(stop); };
978 auto on_close = [&,
this]() { this->OnHide(); };
979 auto status_line =
new StatusLine(
this, m_quick_filter, tty_panel, on_stop,
981 vbox->Add(status_line, wxSizerFlags().Expand());
986 m_quick_filter->Bind(wxEVT_TEXT, [&, tty_panel](wxCommandEvent&) {
987 tty_panel->SetQuickFilter(GetLabel().ToStdString());
989 m_quick_filter->Hide();
990 tty_panel->SetOnRightClick(
991 [&, status_line] { status_line->OnContextClick(); });
993 Bind(wxEVT_CLOSE_WINDOW, [
this](wxCloseEvent& ev) { Hide(); });
994 Bind(wxEVT_RIGHT_UP, [status_line](wxMouseEvent& ev) {
995 status_line->OnContextClick();
998 m_filter_list_lstnr.Init(FilterEvents::GetInstance().filter_list_change,
1000 m_filter_update_lstnr.Init(FilterEvents::GetInstance().filter_update,
1002 OnFilterUpdate(ev.GetString().ToStdString());
1005 m_filter_apply_lstnr.Init(FilterEvents::GetInstance().filter_apply,
1007 OnFilterApply(ev.GetString().ToStdString());
1017 wxWindow* w = wxWindow::FindWindowByName(
"TtyPanel");
1018 assert(w &&
"No TtyPanel found");
1019 return w->IsShownOnScreen();
1022void DataMonitor::OnFilterListChange() {
1023 wxWindow* w = wxWindow::FindWindowByName(kFilterChoiceName);
1026 assert(filter_choice &&
"Wrong FilterChoice type (!)");
1027 filter_choice->OnFilterListChange();
1030void DataMonitor::OnFilterUpdate(
const std::string& name)
const {
1031 if (name != m_current_filter)
return;
1032 wxWindow* w = wxWindow::FindWindowByName(
"TtyScroll");
1034 auto tty_scroll =
dynamic_cast<TtyScroll*
>(w);
1035 assert(tty_scroll &&
"Wrong TtyScroll type (!)");
1036 tty_scroll->SetFilter(filters_on_disk::Read(name));
1039void DataMonitor::OnFilterApply(
const std::string& name) {
1040 wxWindow* w = wxWindow::FindWindowByName(kFilterChoiceName);
1043 assert(filter_choice &&
"Wrong FilterChoice type (!)");
1044 m_current_filter = name;
1045 filter_choice->OnApply(name);
1048void DataMonitor::OnHide() { Hide(); }
1050#pragma clang diagnostic pop
Clickable crossmark icon window.
obs::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().
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.
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().
void Notify() override
Notify all listeners, no data supplied.
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...