OpenCPN Partial API docs
Loading...
Searching...
No Matches
data_monitor.cpp
1#include <chrono>
2#include <iostream> // debug junk
3#include <fstream>
4#include <sstream>
5
6#include "data_monitor.h"
7#include "data_monitor_src.h"
8
9#include <wx/app.h>
10#include <wx/button.h>
11#include <wx/choice.h>
12#include <wx/filedlg.h>
13#include <wx/menu.h>
14#include <wx/panel.h>
15#include <wx/platinfo.h>
16#include <wx/sizer.h>
17#include <wx/sstream.h>
18#include <wx/statline.h>
19#include <wx/stattext.h>
20#include <wx/translation.h>
21#include <wx/wrapsizer.h>
22
23#ifndef ocpnUSE_wxBitmapBundle
24#include <wxSVG/svg.h>
25#endif
26
27#ifdef __ANDROID__
28#include "androidUTIL.h"
29#endif
30
32#include "model/navmsg_filter.h"
33#include "model/nmea_log.h"
34#include "model/gui.h"
35
36#include "data_monitor_src.h"
37#include "svg_icons.h"
38#include "tty_scroll.h"
39#include "filter_dlg.h"
40
41#include "std_filesystem.h"
42
43#pragma clang diagnostic push
44#pragma ide diagnostic ignored "UnreachableCode"
45
46// Make _() return std::string instead of wxString;
47#undef _
48#if wxCHECK_VERSION(3, 2, 0)
49#define _(s) wxGetTranslation(wxASCII_STR(s)).ToStdString()
50#else
51#define _(s) wxGetTranslation((s)).ToStdString()
52#endif
53
54static const char* const kFilterChoiceName = "FilterChoiceWindow";
55
56// clang-format: off
57static const std::unordered_map<NavAddr::Bus, std::string> kSourceByBus = {
58 {NavAddr::Bus::N0183, "NMEA0183"},
59 {NavAddr::Bus::N2000, "NMEA2000"},
60 {NavAddr::Bus::Signalk, "SignalK"}};
61// clang-format: on
62
64static bool IsUserFilter(const std::string& filter_name) {
65 std::vector<std::string> filters = filters_on_disk::List();
66 auto found = std::find(filters.begin(), filters.end(), filter_name);
67 if (found != filters.end()) return true;
68 for (auto& f : filters) {
70 if (nf.m_description == filter_name) return true;
71 }
72 return false;
73};
80static std::string VdrQuote(const std::string& arg) {
81 auto static const npos = std::string::npos;
82 if (arg.find(',') == npos && arg.find('"') == npos) return arg;
83 std::string s;
84 for (auto c : arg) {
85 if (c == '"')
86 s += "\"\"";
87 else
88 s += c;
89 }
90 return "\"" + s + "\"";
91}
92
97static void AddVdrLogline(const Logline& ll, std::ostream& stream) {
98 if (kSourceByBus.find(ll.navmsg->bus) == kSourceByBus.end()) return;
99
100 using namespace std::chrono;
101 auto now = steady_clock::now();
102 auto ms = duration_cast<milliseconds>(now.time_since_epoch()).count();
103 stream << ms << ",";
104
105 stream << kSourceByBus.at(ll.navmsg->bus) << ",";
106 stream << ll.navmsg->source->iface << ",";
107 switch (ll.navmsg->bus) {
108 case NavAddr::Bus::N0183: {
109 auto msg0183 = std::dynamic_pointer_cast<const Nmea0183Msg>(ll.navmsg);
110 stream << msg0183->talker << msg0183->type << ",";
111 } break;
112 case NavAddr::Bus::N2000: {
113 auto msg2000 = std::dynamic_pointer_cast<const Nmea2000Msg>(ll.navmsg);
114 stream << msg2000->PGN.to_string() << ",";
115 } break;
116 case NavAddr::Bus::Signalk: {
117 auto msgSignalK = std::dynamic_pointer_cast<const SignalkMsg>(ll.navmsg);
118 stream << "\"" << msgSignalK->context_self << "\",";
119 } break;
120 default:
121 assert(false && "Illegal message type");
122 };
123 stream << VdrQuote(ll.navmsg->to_vdr()) << "\n";
124}
125
127static void AddStdLogline(const Logline& ll, std::ostream& stream, char fs) {
128 wxString ws;
129#ifndef __WXQT__ // Date/Time on Qt are broken, at least for android
130 ws << wxDateTime::Now().FormatISOTime() << fs;
131#else
132 ws << "- ";
133#endif
134 if (ll.state.direction == NavmsgStatus::Direction::kOutput)
135 ws << kUtfRightArrow << fs;
136 else if (ll.state.direction == NavmsgStatus::Direction::kInput)
137 ws << kUtfLeftwardsArrowToBar << fs;
138 else if (ll.state.direction == NavmsgStatus::Direction::kInternal)
139 ws << kUtfLeftRightArrow << fs;
140 else
141 ws << kUtfLeftArrow << fs;
142 if (ll.state.status != NavmsgStatus::State::kOk)
143 ws << kUtfMultiplicationX << fs;
144 else if (ll.state.accepted == NavmsgStatus::Accepted::kFilteredNoOutput)
145 ws << kUtfFallingDiagonal << fs;
146 else if (ll.state.accepted == NavmsgStatus::Accepted::kFilteredDropped)
147 ws << kUtfCircledDivisionSlash << fs;
148 else
149 ws << kUtfCheckMark << fs;
150
151 ws << (ll.navmsg ? ll.navmsg->source->iface : ".") << fs;
152 ws << (ll.navmsg ? NavAddr::BusToString(ll.navmsg->bus) : "-") << fs;
153 if (ll.state.status != NavmsgStatus::State::kOk)
154 ws << (ll.error_msg.size() > 0 ? ll.error_msg : "Unknown errror");
155 else
156 ws << "ok";
157 ws << fs << ll.message << "\n";
158 stream << ws;
159}
160
162class TtyPanel : public wxPanel, public NmeaLog {
163public:
164 TtyPanel(wxWindow* parent, size_t lines)
165 : wxPanel(parent, wxID_ANY, wxDefaultPosition, wxDefaultSize,
166 wxTAB_TRAVERSAL, "TtyPanel"),
167 m_tty_scroll(nullptr),
168 m_filter(this, wxID_ANY),
169 m_lines(lines),
170 m_on_right_click([] {}) {
171 auto vbox = new wxBoxSizer(wxVERTICAL);
172 m_tty_scroll = new TtyScroll(this, m_lines);
173 m_tty_scroll->Bind(wxEVT_RIGHT_UP,
174 [&](wxMouseEvent&) { m_on_right_click(); });
175 vbox->Add(m_tty_scroll, wxSizerFlags(1).Expand().Border());
176 m_filter.Hide();
177 SetSizer(vbox);
178 Fit();
179 }
180
181 void Add(const Logline& ll) override { m_tty_scroll->Add(ll); }
182
183 bool IsActive() const override { return IsShownOnScreen(); }
184
185 void OnStop(bool stop) {
186 m_tty_scroll->Pause(stop);
187 if (stop)
188 m_tty_scroll->ShowScrollbars(wxSHOW_SB_DEFAULT, wxSHOW_SB_DEFAULT);
189 else
190 m_tty_scroll->ShowScrollbars(wxSHOW_SB_NEVER, wxSHOW_SB_NEVER);
191 }
192
193 void SetFilter(const NavmsgFilter& f) { m_tty_scroll->SetFilter(f); };
194
195 void SetQuickFilter(const std::string& filter) {
196 m_tty_scroll->SetQuickFilter(filter);
197 }
198
199 void SetOnRightClick(std::function<void()> f) {
200 m_on_right_click = std::move(f);
201 }
202
204 static void AddIfExists(const Logline& ll) {
205 auto window = wxWindow::FindWindowByName("TtyPanel");
206 if (!window) return;
207 auto tty_panel = dynamic_cast<TtyPanel*>(window);
208 if (tty_panel) tty_panel->Add(ll);
209 }
210
211protected:
212 wxSize DoGetBestClientSize() const override {
213 return wxSize(-1, m_lines * GetCharHeight());
214 }
215
216private:
217 TtyScroll* m_tty_scroll;
218 wxTextCtrl m_filter;
219 size_t m_lines;
220 std::function<void()> m_on_right_click;
221};
222
223class QuickFilterPanel : public wxPanel {
224public:
225 QuickFilterPanel(wxWindow* parent, std::function<void()> on_text_evt)
226 : wxPanel(parent),
227 m_text_ctrl(new wxTextCtrl(this, wxID_ANY)),
228 m_on_text_evt(std::move(on_text_evt)) {
229 auto hbox = new wxBoxSizer(wxHORIZONTAL);
230 auto flags = wxSizerFlags(0).Border();
231 auto label_box = new wxBoxSizer(wxVERTICAL);
232 label_box->Add(new wxStaticText(this, wxID_ANY, _("Quick filter:")));
233 hbox->Add(label_box, flags.Align(wxALIGN_CENTER_VERTICAL));
234 hbox->Add(m_text_ctrl, flags);
235 SetSizer(hbox);
236 Fit();
237 Show();
238 m_text_ctrl->Bind(wxEVT_TEXT, [&](wxCommandEvent&) { m_on_text_evt(); });
239 }
240
241 bool Show(bool show = true) override {
242 if (!show) m_text_ctrl->SetValue("");
243 return wxWindow::Show(show);
244 }
245
246 std::string GetValue() { return m_text_ctrl->GetValue().ToStdString(); }
247
248private:
249 wxTextCtrl* m_text_ctrl;
250 std::function<void()> m_on_text_evt;
251};
252
254class LogButton : public wxButton {
255public:
256 LogButton(wxWindow* parent, DataLogger& logger)
257 : wxButton(parent, wxID_ANY), is_logging(true), m_logger(logger) {
258 Bind(wxEVT_BUTTON, [&](wxCommandEvent&) { OnClick(); });
259 OnClick();
260 Disable();
261 Enable(false);
262 UpdateTooltip();
263 }
264
265 void UpdateTooltip() {
266 if (!IsThisEnabled())
267 SetToolTip(_("Set log file using menu to enable"));
268 else if (is_logging)
269 SetToolTip(_("Click to stop logging"));
270 else
271 SetToolTip(_("Click to start logging"));
272 }
273
274 bool Enable(bool enable) override {
275 bool result = wxWindow::Enable(enable);
276 UpdateTooltip();
277 return result;
278 }
279
280private:
281 bool is_logging;
282 DataLogger& m_logger;
283
284 void OnClick() {
285 is_logging = !is_logging;
286 SetLabel(is_logging ? _("Stop") : _("Start"));
287 UpdateTooltip();
288 m_logger.SetLogging(is_logging);
289 }
290};
291
293class FilterChoice : public wxChoice {
294public:
295 FilterChoice(wxWindow* parent, TtyPanel* tty_panel)
296 : wxChoice(parent, wxID_ANY), m_tty_panel(tty_panel) {
297 SetName(kFilterChoiceName);
298 Bind(wxEVT_CHOICE, [&](wxCommandEvent&) { OnChoice(); });
299 OnFilterListChange();
300 int ix = FindString("Default settings");
301 if (ix != wxNOT_FOUND) SetSelection(ix);
302 NavmsgFilter filter = filters_on_disk::Read("default.filter");
303 m_tty_panel->SetFilter(filter);
304 }
305
306 void OnFilterListChange() {
307 m_filters = NavmsgFilter::GetAllFilters();
308 int select_ix = GetSelection();
309 std::string selected;
310 if (select_ix != wxNOT_FOUND) selected = GetString(select_ix).ToStdString();
311 Clear();
312 for (auto& filter : m_filters) {
313 try {
314 Append(kLabels.at(filter.m_name));
315 } catch (std::out_of_range&) {
316 if (filter.m_description.empty())
317 Append(filter.m_name);
318 else
319 Append(filter.m_description);
320 }
321 }
322 if (!selected.empty()) {
323 int ix = FindString(selected);
324 SetSelection(ix == wxNOT_FOUND ? 0 : ix);
325 }
326 }
327
328 void OnFilterUpdate(const std::string& name) {
329 m_filters = NavmsgFilter::GetAllFilters();
330 int select_ix = GetSelection();
331 if (select_ix == wxNOT_FOUND) return;
332
333 std::string selected = GetString(select_ix).ToStdString();
334 if (selected != name) return;
335
336 NavmsgFilter filter = filters_on_disk::Read(name);
337 m_tty_panel->SetFilter(filter);
338 }
339
340 void OnApply(const std::string& name) {
341 int found = FindString(name);
342 if (found == wxNOT_FOUND) {
343 for (auto& filter : m_filters) {
344 if (filter.m_name == name) {
345 found = FindString(filter.m_description);
346 break;
347 }
348 }
349 }
350 if (found == wxNOT_FOUND) return;
351
352 SetSelection(found);
353 OnFilterUpdate(name);
354 }
355
356private:
357 // Translated labels for system filters by filter name. If not
358 // found the untranslated json description is used.
359 const std::unordered_map<std::string, std::string> kLabels = {
360 {"all-data", _("All data")},
361 {"all-nmea", _("All NMEA data")},
362 {"malformed", _("Malformed messages")},
363 {"nmea-input", _("NMEA input data")},
364 {"nmea-output", _("NMEA output data")},
365 {"plugins", _("Messages to plugins")},
366 };
367
368 std::vector<NavmsgFilter> m_filters;
369 TtyPanel* m_tty_panel;
370
371 void OnChoice() {
372 wxString label = GetString(GetSelection());
373 NavmsgFilter filter = FilterByLabel(label.ToStdString());
374 m_tty_panel->SetFilter(filter);
375 }
376
377 NavmsgFilter FilterByLabel(const std::string& label) {
378 std::string name;
379 for (const auto& kv : kLabels) {
380 if (kv.second == label) {
381 name = kv.first;
382 break;
383 }
384 }
385 if (!name.empty()) {
386 for (auto& f : m_filters)
387 if (f.m_name == name) return f;
388 } else {
389 for (auto& f : m_filters)
390 if (f.m_description == label) return f;
391 }
392 return NavmsgFilter();
393 }
394};
395
397class PauseResumeButton : public wxButton {
398public:
399 PauseResumeButton(wxWindow* parent, std::function<void(bool)> on_stop)
400 : wxButton(parent, wxID_ANY),
401 is_paused(true),
402 m_on_stop(std::move(on_stop)) {
403 Bind(wxEVT_BUTTON, [&](wxCommandEvent&) { OnClick(); });
404 OnClick();
405 }
406
407private:
408 bool is_paused;
409 std::function<void(bool)> m_on_stop;
410
411 void OnClick() {
412 is_paused = !is_paused;
413 m_on_stop(is_paused);
414 SetLabel(is_paused ? _("Resume") : _("Pause"));
415 }
416};
417
419class TheMenu : public wxMenu {
420public:
421 enum class Id : char {
422 kNewFilter = 1, // MacOS does not want ids to be 0.
423 kEditFilter,
424 kDeleteFilter,
425 kEditActiveFilter,
426 kLogFile,
427 kLogFormatDefault,
428 kLogFormatCsv,
429 kLogFormatVdr,
430 kViewStdColors,
431 kViewNoColors,
432 kViewTimestamps,
433 kViewSource,
434 kViewCopy
435 };
436
437 TheMenu(wxWindow* parent, wxStaticText* log_label, DataLogger& logger,
438 wxWindow* log_button)
439 : m_parent(parent),
440 m_log_button(log_button),
441 m_logger(logger),
442 m_log_label(log_label) {
443 auto filters = new wxMenu("");
444 AppendId(filters, Id::kNewFilter, _("Create new..."));
445 AppendId(filters, Id::kEditFilter, _("Edit..."));
446 AppendId(filters, Id::kDeleteFilter, _("Delete..."));
447 AppendSubMenu(filters, _("Filters..."));
448 if (IsUserFilter(m_filter))
449 Append(static_cast<int>(Id::kEditActiveFilter), _("Edit active filter"));
450 auto logging = new wxMenu("");
451 AppendId(logging, Id::kLogFile, _("Log file..."));
452 AppendRadioId(logging, Id::kLogFormatDefault, _("Log format: standard"));
453 AppendRadioId(logging, Id::kLogFormatCsv, _("Log format: CSV"));
454 AppendRadioId(logging, Id::kLogFormatVdr, _("Log format: VDR"));
455 AppendSubMenu(logging, _("Logging..."));
456
457 auto view = new wxMenu("");
458 AppendCheckId(view, Id::kViewStdColors, _("Use colors"));
459 AppendId(view, Id::kViewCopy, _("Copy messages to clipboard"));
460 AppendSubMenu(view, _("View..."));
461
462 Bind(wxEVT_MENU, [&](wxCommandEvent& ev) {
463 switch (static_cast<Id>(ev.GetId())) {
464 case Id::kLogFormatDefault:
465 SetLogFormat(DataLogger::Format::kDefault, _("Log format: default"));
466 break;
467
468 case Id::kLogFormatVdr:
469 SetLogFormat(DataLogger::Format::kVdr, _("Log format: VDR"));
470 break;
471
472 case Id::kLogFormatCsv:
473 SetLogFormat(DataLogger::Format::kCsv, _("Log format: csv"));
474 break;
475
476 case Id::kLogFile:
477 SetLogfile();
478 break;
479
480 case Id::kViewStdColors:
481 SetColor(static_cast<int>(Id::kViewStdColors));
482 break;
483
484 case Id::kViewCopy:
485 CopyToClipboard();
486 break;
487
488 case Id::kNewFilter:
489 CreateFilterDlg(parent);
490 break;
491
492 case Id::kEditFilter:
493 EditFilterDlg(wxTheApp->GetTopWindow());
494 break;
495
496 case Id::kEditActiveFilter:
497 EditOneFilterDlg(wxTheApp->GetTopWindow(), m_filter);
498 break;
499
500 case Id::kDeleteFilter:
501 RemoveFilterDlg(parent);
502 break;
503
504 default:
505 std::cout << "Menu id: " << ev.GetId() << "\n";
506 break;
507 }
508 });
509 }
510
511 void SetFilterName(const std::string& filter) {
512 int id = static_cast<int>(Id::kEditActiveFilter);
513 if (FindItem(id)) Delete(id);
514 if (IsUserFilter(filter)) Append(id, _("Edit active filter"));
515 m_filter = filter;
516 }
517
518private:
519 wxWindow* m_parent;
520 wxWindow* m_log_button;
521 DataLogger& m_logger;
522 wxStaticText* m_log_label;
523 std::string m_filter;
524
525 wxMenuItem* AppendId(wxMenu* root, Id id, const wxString& label) {
526 return root->Append(static_cast<int>(id), label);
527 }
528
529 void AppendRadioId(wxMenu* root, Id id, const wxString& label) {
530 root->AppendRadioItem(static_cast<int>(id), label);
531 }
532
533 void AppendCheckId(wxMenu* root, Id id, const wxString& label) {
534 root->AppendCheckItem(static_cast<int>(id), label);
535 }
536
537 void SetLogFormat(DataLogger::Format format, const std::string& label) {
538 m_log_label->SetLabel(label);
539 m_logger.SetFormat(format);
540 m_log_button->Disable();
541 m_parent->Layout();
542 }
543
544 void SetLogfile() {
545 wxFileDialog dlg(m_parent, _("Select logfile"), "",
546 m_logger.GetLogfile().string(), _("Log Files (*.log)"),
547 wxFD_SAVE | wxFD_OVERWRITE_PROMPT);
548 if (dlg.ShowModal() == wxID_CANCEL) return;
549 m_logger.SetLogfile(fs::path(dlg.GetPath().ToStdString()));
550 m_log_button->Enable();
551 }
552
553 void SetColor(int id) {
554 auto* w = wxWindow::FindWindowByName("TtyScroll");
555 auto tty_scroll = dynamic_cast<TtyScroll*>(w);
556 if (!tty_scroll) return;
557
558 wxMenuItem* item = FindItem(id);
559 if (!item) return;
560
561 if (item->IsCheck() && item->IsChecked())
562 tty_scroll->SetColors(std::make_unique<StdColorsByState>());
563 else
564 tty_scroll->SetColors(
565 std::make_unique<NoColorsByState>(tty_scroll->GetForegroundColour()));
566 }
567
568 void CopyToClipboard() {
569 auto* tty_scroll =
570 dynamic_cast<TtyScroll*>(wxWindow::FindWindowByName("TtyScroll"));
571 if (!tty_scroll) return;
572 tty_scroll->CopyToClipboard();
573 }
574};
575
577class FilterButton : public wxButton {
578public:
579 FilterButton(wxWindow* parent, wxWindow* quick_filter)
580 : wxButton(parent, wxID_ANY, wxEmptyString, wxDefaultPosition,
581 wxDefaultSize, wxBU_EXACTFIT | wxBU_BOTTOM),
582 m_quick_filter(quick_filter),
583 m_show_filter(true) {
584 Bind(wxEVT_BUTTON, [&](wxCommandEvent&) { OnClick(); });
585 OnClick();
586 }
587
588private:
589 wxWindow* m_quick_filter;
590 bool m_show_filter;
591
592 void OnClick() {
593 m_show_filter = !m_show_filter;
594 char buffer[2048];
595 strcpy(buffer, m_show_filter ? kNoFunnelSvg : kFunnelSvg);
596#ifdef ocpnUSE_wxBitmapBundle
597 auto icon_size = wxSize(2 * GetCharWidth(), GetCharHeight());
598 auto bundle = wxBitmapBundle::FromSVG(buffer, icon_size);
599 SetBitmap(bundle);
600#else
601 wxStringInputStream wis(buffer);
602 wxSVGDocument svg_doc(wis);
603 wxImage image = svg_doc.Render(GetCharHeight(), GetCharHeight());
604 SetBitmap(wxBitmap(image));
605#endif
606 m_quick_filter->Show(m_show_filter);
607 SetToolTip(m_show_filter ? _("Close quick filter")
608 : _("Open quick filter"));
609 GetGrandParent()->Layout();
610 }
611};
612
614class MenuButton : public wxButton {
615public:
616 MenuButton(wxWindow* parent, TheMenu& menu,
617 std::function<std::string()> get_current_filter)
618 : wxButton(parent, wxID_ANY, wxEmptyString, wxDefaultPosition,
619 wxDefaultSize, wxBU_EXACTFIT),
620 m_menu(menu),
621 m_get_current_filter(std::move(get_current_filter)) {
622 Bind(wxEVT_BUTTON, [&](wxCommandEvent&) { OnClick(); });
623 SetLabel(kUtfIdenticalTo);
624 SetToolTip(_("Open menu"));
625 }
626
627private:
628 TheMenu& m_menu;
629 std::function<std::string()> m_get_current_filter;
630
631 void OnClick() {
632 m_menu.SetFilterName(m_get_current_filter());
633 PopupMenu(&m_menu);
634 }
635};
636
638class StatusLine : public wxPanel {
639public:
640 StatusLine(wxWindow* parent, wxWindow* quick_filter, TtyPanel* tty_panel,
641 std::function<void(bool)> on_stop, DataLogger& logger)
642 : wxPanel(parent),
643 m_is_resized(false),
644 m_log_button(new LogButton(this, logger)),
645 m_log_label(new wxStaticText(this, wxID_ANY, _("Logging: Default"))),
646 m_filter_choice(new FilterChoice(this, tty_panel)),
647 m_menu(this, m_log_label, logger, m_log_button) {
648 // Add a containing sizer for labels, so they can be aligned vertically
649 auto log_label_box = new wxBoxSizer(wxVERTICAL);
650 log_label_box->Add(m_log_label);
651 auto filter_label_box = new wxBoxSizer(wxVERTICAL);
652 filter_label_box->Add(new wxStaticText(this, wxID_ANY, _("View")));
653
654 auto flags = wxSizerFlags(0).Border();
655 auto wbox = new wxWrapSizer(wxHORIZONTAL);
656 wbox->Add(log_label_box, flags.Align(wxALIGN_CENTER_VERTICAL));
657 wbox->Add(m_log_button, flags);
658 wbox->Add(GetCharWidth() * 2, 0, 1); // Stretching horizontal space
659 wbox->Add(filter_label_box, flags.Align(wxALIGN_CENTER_VERTICAL));
660 wbox->Add(m_filter_choice, flags);
661 wbox->Add(new PauseResumeButton(this, std::move(on_stop)), flags);
662 wbox->Add(new FilterButton(this, quick_filter), flags);
663 auto get_current_filter = [&] {
664 return m_filter_choice->GetStringSelection().ToStdString();
665 };
666 wbox->Add(new MenuButton(this, m_menu, get_current_filter), flags);
667 SetSizer(wbox);
668 Layout();
669 Show();
670
671 Bind(wxEVT_SIZE, [&](wxSizeEvent& ev) {
672 m_is_resized = true;
673 ev.Skip();
674 });
675 Bind(wxEVT_RIGHT_UP, [&](wxMouseEvent& ev) {
676 m_menu.SetFilterName(m_filter_choice->GetStringSelection().ToStdString());
677 PopupMenu(&m_menu);
678 });
679 }
680
681 void OnContextClick() {
682 m_menu.SetFilterName(m_filter_choice->GetStringSelection().ToStdString());
683 PopupMenu(&m_menu);
684 }
685
686protected:
687 // Make sure the initial size is sane, don't meddle when user resizes
688 // dialog
689 wxSize DoGetBestClientSize() const override {
690 if (m_is_resized)
691 return wxSize(-1, -1);
692 else
693 return wxSize(85 * GetCharWidth(), 2.5 * GetCharHeight());
694 }
695
696private:
697 bool m_is_resized;
698 wxButton* m_log_button;
699 wxStaticText* m_log_label;
700 wxChoice* m_filter_choice;
701 TheMenu m_menu;
702};
703
704DataLogger::DataLogger(wxWindow* parent, const fs::path& path)
705 : m_parent(parent),
706 m_path(path),
707 m_stream(path, std::ios_base::app),
708 m_is_logging(false),
709 m_format(Format::kDefault) {}
710
711DataLogger::DataLogger(wxWindow* parent)
712 : DataLogger(parent, DefaultLogfile()) {}
713
714void DataLogger::SetLogging(bool logging) { m_is_logging = logging; }
715
716void DataLogger::SetLogfile(const fs::path& path) {
717 m_stream = std::ofstream(path);
718 m_stream << "# timestamp_format: EPOCH_MILLIS\n";
719 m_stream << "received_at,protocol,msg_type,source,raw_data\n";
720 m_stream << std::flush;
721}
722
723void DataLogger::SetFormat(DataLogger::Format format) { m_format = format; }
724
725fs::path DataLogger::DefaultLogfile() {
726 if (wxPlatformInfo::Get().GetOperatingSystemId() & wxOS_WINDOWS)
727 return "NUL:";
728 else
729 return "/dev/null";
730}
731
732void DataLogger::Add(const Logline& ll) {
733 if (!m_is_logging || !ll.navmsg) return;
734 if (m_format == Format::kVdr && ll.navmsg->to_vdr().empty()) return;
735 if (m_format == DataLogger::Format::kVdr)
736 AddVdrLogline(ll, m_stream);
737 else
738 AddStdLogline(ll, m_stream,
739 m_format == DataLogger::Format::kCsv ? '|' : ' ');
740}
741
742DataMonitor::DataMonitor(wxWindow* parent)
743 : wxFrame(parent, wxID_ANY, "Data Monitor", wxDefaultPosition,
744 wxDefaultSize, wxDEFAULT_FRAME_STYLE, kDataMonitorWindowName),
745 m_monitor_src([&](const std::shared_ptr<const NavMsg>& navmsg) {
746 auto msg = std::dynamic_pointer_cast<const Nmea0183Msg>(navmsg);
747 TtyPanel::AddIfExists(navmsg);
748 }),
749 m_quick_filter(nullptr),
750 m_logger(parent) {
751 auto vbox = new wxBoxSizer(wxVERTICAL);
752 auto tty_panel = new TtyPanel(this, 12);
753 vbox->Add(tty_panel, wxSizerFlags(1).Expand().Border());
754 vbox->Add(new wxStaticLine(this), wxSizerFlags().Expand().Border());
755
756 auto on_quick_filter_evt = [&, tty_panel] {
757 auto* quick_filter = dynamic_cast<QuickFilterPanel*>(m_quick_filter);
758 assert(quick_filter);
759 std::string value = quick_filter->GetValue();
760 tty_panel->SetQuickFilter(value);
761 };
762 m_quick_filter = new QuickFilterPanel(this, on_quick_filter_evt);
763 vbox->Add(m_quick_filter, wxSizerFlags());
764
765 auto on_stop = [&, tty_panel](bool stop) { tty_panel->OnStop(stop); };
766 auto status_line =
767 new StatusLine(this, m_quick_filter, tty_panel, on_stop, m_logger);
768 vbox->Add(status_line, wxSizerFlags().Expand());
769 SetSizer(vbox);
770 Fit();
771 Show();
772
773 m_quick_filter->Bind(wxEVT_TEXT, [&, tty_panel](wxCommandEvent&) {
774 tty_panel->SetQuickFilter(GetLabel().ToStdString());
775 });
776 m_quick_filter->Hide();
777 tty_panel->SetOnRightClick(
778 [&, status_line] { status_line->OnContextClick(); });
779
780 Bind(wxEVT_CLOSE_WINDOW, [this](wxCloseEvent& ev) { Hide(); });
781 Bind(wxEVT_RIGHT_UP, [status_line](wxMouseEvent& ev) {
782 status_line->OnContextClick();
783 ev.Skip();
784 });
785 m_filter_list_lstnr.Init(FilterEvents::GetInstance().filter_list_change,
786 [&](ObservedEvt&) { OnFilterListChange(); });
787 m_filter_update_lstnr.Init(
788 FilterEvents::GetInstance().filter_update,
789 [&](ObservedEvt& ev) { OnFilterUpdate(ev.GetString().ToStdString()); });
790
791 m_filter_apply_lstnr.Init(
792 FilterEvents::GetInstance().filter_apply,
793 [&](ObservedEvt& ev) { OnFilterApply(ev.GetString().ToStdString()); });
794}
795
796void DataMonitor::Add(const Logline& ll) {
798 m_logger.Add(ll);
799}
800
802 wxWindow* w = wxWindow::FindWindowByName("TtyPanel");
803 assert(w && "No TtyPanel found");
804 return w->IsShownOnScreen();
805}
806
807void DataMonitor::OnFilterListChange() {
808 wxWindow* w = wxWindow::FindWindowByName(kFilterChoiceName);
809 if (!w) return;
810 auto filter_choice = dynamic_cast<FilterChoice*>(w);
811 assert(filter_choice && "Wrong FilterChoice type (!)");
812 filter_choice->OnFilterListChange();
813}
814
815void DataMonitor::OnFilterUpdate(const std::string& name) {
816 wxWindow* w = wxWindow::FindWindowByName("TtyScroll");
817 if (!w) return;
818 auto tty_scroll = dynamic_cast<TtyScroll*>(w);
819 assert(tty_scroll && "Wrong TtyScroll type (!)");
820 tty_scroll->SetFilter(filters_on_disk::Read(name));
821}
822
823void DataMonitor::OnFilterApply(const std::string& name) {
824 wxWindow* w = wxWindow::FindWindowByName(kFilterChoiceName);
825 if (!w) return;
826 auto filter_choice = dynamic_cast<FilterChoice*>(w);
827 assert(filter_choice && "Wrong FilterChoice type (!)");
828 filter_choice->OnApply(name);
829}
830
831#pragma clang diagnostic pop
bool IsActive() const override
Return true if log is visible i.
void Add(std::string msg)
Add an input line to log output.
Button opening the filter dialog.
Offer user to select current filter.
Button to start/stop logging.
Button invoking the popup menu.
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.
Button to stop/resume messages in main log window.
Overall bottom status line.
The monitor popup menu.
Main window, a rolling log of messages.
void Add(const Logline &ll) override
Add an formatted string to log output.
static void AddIfExists(const Logline &ll)
Invoke Add(s) for possibly existing instance.
bool IsActive() const override
Return true if log is visible i.
Scrolled TTY-like window for logging, etc.
Definition tty_scroll.h:75
void SetQuickFilter(const std::string s)
Apply a quick filter directly matched against lines.
Definition tty_scroll.h:106
void Pause(bool pause)
Set the window to ignore Add() or not depending on pause.
Definition tty_scroll.h:94
void SetFilter(const NavmsgFilter &filter)
Apply a display filter.
Definition tty_scroll.h:100
virtual void Add(const Logline &line)
Add a line to bottom of window, typically discarding top-most line.
void CopyToClipboard() const
Copy message contents to clipboard.
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.
NavmsgFilter Read(const std::string &name)
Read filter with given name from disk.
Filter storage routines.
Hooks into gui available in model.
Data monitor filter definitions.
Item in the log window.
Definition nmea_log.h:10
Scrolled TTY-like window for logging, related utilities.