30#include <wx/checkbox.h>
31#include <wx/checklst.h>
32#include <wx/choicdlg.h>
37#include <wx/radiobox.h>
38#include <wx/radiobut.h>
40#include <wx/statline.h>
41#include <wx/stattext.h>
42#include <wx/textdlg.h>
43#include <wx/wrapsizer.h>
45#ifndef ocpnUSE_wxBitmapBundle
46#include <wx/sstream.h>
50#include "libgui/edit_button.h"
56#include "std_filesystem.h"
61#if wxCHECK_VERSION(3, 2, 0)
62#define _(s) wxGetTranslation(wxASCII_STR(s)).ToStdString().c_str()
64#define _(s) wxGetTranslation((s)).ToStdString().c_str()
67static const std::string kEditFilterFrameName(
"EditFilterFrame::");
69static const char*
const kFilterExists =
70 R
"( Filter already exists. Please use Edit to update
71or Remove to delete it prior to create.)";
73static const std::unordered_map<NavmsgStatus::Direction, std::string>
74 kStringByDirection = {{NavmsgStatus::Direction::kInput,
"Input"},
75 {NavmsgStatus::Direction::kHandled,
"Handled"},
76 {NavmsgStatus::Direction::kOutput,
"Output"},
77 {NavmsgStatus::Direction::kInternal,
"Internal"}};
82 return dynamic_cast<T*
>(wxWindow::FindWindowById(
id));
85static NavmsgStatus::Direction StringToDirection(
const std::string& s) {
86 for (
const auto& it : kStringByDirection)
87 if (it.second == s)
return it.first;
88 return NavmsgStatus::Direction::kNone;
91static wxArrayString GetUserFilters() {
93 wxArrayString wx_filters;
94 for (
auto& f : std_filters) wx_filters.Add(f);
99static std::vector<std::string> GetActiveMessages() {
100 auto v = NavMsgBus::GetInstance().GetActiveMessages();
101 std::vector<std::string> result;
102 for (
const auto& m : NavMsgBus::GetInstance().GetActiveMessages()) {
103 size_t pos = m.find(
"::");
104 if (pos == std::string::npos) {
107 result.push_back(m.substr(pos + 2));
113class NewFilterDlg :
public wxTextEntryDialog {
115 NewFilterDlg(wxWindow*)
116 : wxTextEntryDialog(wxTheApp->GetTopWindow(), _(
"New filter name")) {}
119class DeleteFilterDlg :
public wxSingleChoiceDialog {
121 DeleteFilterDlg(wxWindow*)
122 : wxSingleChoiceDialog(wxTheApp->GetTopWindow(),
123 _(
"Remove filter (name):"), _(
"Remove filter"),
127class SelectFilterDlg :
public wxSingleChoiceDialog {
129 SelectFilterDlg(wxWindow*)
130 : wxSingleChoiceDialog(wxTheApp->GetTopWindow(), _(
"Edit filter (name):"),
131 _(
"Edit filter"), GetUserFilters()) {}
134class RenameFilterChoiceDlg :
public wxSingleChoiceDialog {
136 RenameFilterChoiceDlg(wxWindow*)
137 : wxSingleChoiceDialog(wxTheApp->GetTopWindow(),
138 _(
"Rename filter (name):"), _(
"Rename filter"),
142class BadFilterNameDlg :
public wxMessageDialog {
144 BadFilterNameDlg(wxWindow* parent)
145 : wxMessageDialog(parent, _(kFilterExists)) {}
152class MsgTypePanel :
public wxPanel {
155 std::function<
void()> on_update)
156 : wxPanel(parent, wxID_ANY),
158 m_on_update(std::move(on_update)),
159 kIncludeBtnId(wxWindow::NewControlId()),
160 kExcludeBtnId(wxWindow::NewControlId()),
161 kEditBtnId(wxWindow::NewControlId()),
162 kListboxId(wxWindow::NewControlId()),
163 kSummaryId(wxWindow::NewControlId()),
164 kSetBtnId(wxWindow::NewControlId()),
165 kClearBtnId(wxWindow::NewControlId()) {
166 auto flags = wxSizerFlags(0).Border();
167 auto vbox =
new wxStaticBoxSizer(wxVERTICAL,
this, _(
"Message Types"));
168 auto hbox =
new wxBoxSizer(wxHORIZONTAL);
170 auto radiobox =
new wxBoxSizer(wxVERTICAL);
171 radiobox->Add(
new wxRadioButton(
this, kIncludeBtnId, _(
"Include messages"),
172 wxDefaultPosition, wxDefaultSize,
175 new wxRadioButton(
this, kExcludeBtnId, _(
"Exclude messages")));
176 radiobox->Add(1, 1, 1);
177 radiobox->Add(
new wxButton(
this, kSetBtnId, _(
"Check all")), flags);
178 radiobox->Add(
new wxButton(
this, kClearBtnId, _(
"Clear all")), flags);
179 hbox->Add(radiobox, flags.Expand());
182 wxArrayString choices;
183 auto msg_types = GetActiveMessages();
184 for (
const auto& msg_type : msg_types) choices.Add(msg_type);
185 auto listbox =
new wxCheckListBox(
this, kListboxId, wxDefaultPosition,
186 wxDefaultSize, choices, 1);
187 hbox->Add(
new wxStaticText(
this, kSummaryId,
""), flags);
188 hbox->Add(listbox, flags);
190 auto edit_box =
new wxBoxSizer(wxVERTICAL);
191 auto edit_btn =
new EditButton(
this, kEditBtnId, [&] { OnEditClick(); });
192 edit_box->Add(edit_btn, wxSizerFlags());
193 hbox->Add(edit_box, flags.Expand());
197 btn->Bind(wxEVT_BUTTON, [&](wxCommandEvent&) { SetAllItems(
true); });
200 btn->Bind(wxEVT_BUTTON, [&](wxCommandEvent&) { SetAllItems(
false); });
202 vbox->Add(hbox, flags.Expand());
209 listbox->Bind(wxEVT_CHECKLISTBOX,
210 [&](wxCommandEvent& ev) { OnItemCheck(ev.GetInt()); });
214 std::string GetListboxItem(wxCheckListBox* listbox,
unsigned i)
const {
215 return listbox->GetString(i).ToStdString();
218 void SetAllItems(
bool value) {
220 for (
unsigned i = 0; i < listbox->GetCount(); i += 1) {
221 listbox->Check(i, value);
226 std::string GetSummary() {
229 if (m_filter.exclude_msg.empty() && m_filter.include_msg.empty())
231 bool excluded = !m_filter.exclude_msg.empty();
233 excluded ? m_filter.exclude_msg.size() : m_filter.include_msg.size();
234 size_t all = listbox->GetCount();
235 if (all == checked)
return _(
"All");
237 std::stringstream ss;
238 ss <<
"[" << checked << _(
" of ") << all <<
"]";
250 bool is_editing = listbox->IsShown();
251 listbox->Show(!is_editing);
252 summary->Show(is_editing);
253 summary->SetLabel(GetSummary());
254 edit_button->SetIcon(!is_editing);
265 void OnItemCheck(
int ix) {
268 for (
unsigned i = 0; i < listbox->GetCount(); i += 1)
269 if (listbox->IsChecked(i)) checked += 1;
279 void ImportFromFilter() {
284 exclude_btn->SetValue(m_filter.exclude_msg.size() > 0);
285 if (include_btn->GetValue()) {
286 if (m_filter.include_msg.empty()) {
287 for (
unsigned i = 0; i < listbox->GetCount(); i++) listbox->Check(i);
289 for (
unsigned i = 0; i < listbox->GetCount(); i += 1) {
290 std::string item = GetListboxItem(listbox, i);
291 if (m_filter.include_msg.count(item)) listbox->Check(i);
295 if (m_filter.exclude_msg.empty()) {
296 for (
unsigned i = 0; i < listbox->GetCount(); i++) listbox->Check(i);
298 for (
unsigned i = 0; i < listbox->GetCount(); i += 1) {
299 std::string item = GetListboxItem(listbox, i);
300 if (m_filter.exclude_msg.count(item)) listbox->Check(i);
307 void ExportToFilter() {
310 m_filter.include_msg.clear();
311 m_filter.exclude_msg.clear();
313 include_btn->GetValue() ? m_filter.include_msg : m_filter.exclude_msg;
314 for (
unsigned i = 0; i < listbox->GetCount(); i += 1) {
315 if (!listbox->IsChecked(i))
continue;
316 std::string item = GetListboxItem(listbox, i);
317 the_set.insert(item);
319 if (the_set.size() == listbox->GetCount()) {
320 m_filter.include_msg.clear();
321 m_filter.exclude_msg.clear();
327 std::function<void()> m_on_update;
328 const int kIncludeBtnId;
329 const int kExcludeBtnId;
330 const int kEditBtnId;
331 const int kListboxId;
332 const int kSummaryId;
334 const int kClearBtnId;
346class SetPanel :
public wxPanel {
348 SetPanel(wxWindow* parent, std::set<T>& set, std::function<
void()> on_update,
349 const std::string& label, std::function<wxArrayString()> get_choices)
352 m_on_update(std::move(on_update)),
353 kEditBtnId(wxWindow::NewControlId()),
354 kListboxId(wxWindow::NewControlId()),
355 kListLabelId(wxWindow::NewControlId()) {
356 wxPanel::Create(parent, wxID_ANY);
357 auto flags = wxSizerFlags(0).Border();
358 auto vbox =
new wxBoxSizer(wxVERTICAL);
359 auto hbox =
new wxBoxSizer(wxHORIZONTAL);
360 hbox->Add(
new wxStaticText(
this, wxID_ANY, label), flags);
364 wxArrayString array = get_choices();
366 for (
auto string : array) max_char = wxMax(max_char,
string.Length());
367 int hsize = (max_char + 8) * wxWindow::GetCharWidth();
370 auto listbox =
new wxCheckListBox(
this, kListboxId, wxDefaultPosition,
371 wxSize(hsize, -1), get_choices());
372 hbox->Add(listbox, flags);
373 auto list_label =
new wxStaticText(
this, kListLabelId,
"");
374 hbox->Add(list_label, flags);
375 auto edit_btn =
new EditButton(
this, kEditBtnId, [&] { OnEditClick(); });
376 hbox->Add(edit_btn, flags);
377 vbox->Add(hbox, flags.Expand());
381 list_label->SetLabel(GetListLabel());
392 listbox->Bind(wxEVT_CHECKLISTBOX,
393 [&](wxCommandEvent& ev) { OnItemCheck(ev.GetInt()); });
398 T GetListboxItem(wxCheckListBox* listbox,
unsigned i)
const {
399 if constexpr (std::is_same<T, std::string>::value)
400 return listbox->GetString(i).ToStdString();
401 else if constexpr (std::is_same<T, NavAddr::Bus>::value)
402 return NavAddr::StringToBus(listbox->GetString(i).ToStdString());
403 else if constexpr (std::is_same<T, NavmsgStatus::Direction>::value)
404 return StringToDirection(listbox->GetString(i).ToStdString());
405 else if constexpr (std::is_same<T, NavmsgStatus::Accepted>::value)
407 listbox->GetString(i).ToStdString());
409 assert(
false &&
"bad type...");
413 std::string GetListLabel() {
415 if (m_set.empty() || m_set.size() == listbox->GetCount())
return _(
"All");
416 std::stringstream ss;
417 ss <<
"[" << m_set.size() << _(
" of ") << listbox->GetCount() <<
"]";
430 bool is_editing = listbox->IsShown();
431 listbox->Show(!is_editing);
432 list_label->Show(is_editing);
433 list_label->SetLabel(GetListLabel());
434 edit_button->SetIcon(!is_editing);
442 void OnItemCheck(
int ix) {
446 for (
unsigned i = 0; i < listbox->GetCount(); i += 1)
447 if (listbox->IsChecked(i)) checked += 1;
452 list_label->SetLabel(GetListLabel());
457 void ImportFromFilter() {
460 for (
unsigned i = 0; i < listbox->GetCount(); i++) listbox->Check(i);
462 for (
unsigned i = 0; i < listbox->GetCount(); i += 1) {
463 T item = GetListboxItem(listbox, i);
464 if (m_set.count(item) > 0) listbox->Check(i);
470 void ExportToFilter() {
473 for (
unsigned i = 0; i < listbox->GetCount(); i += 1) {
474 if (!listbox->IsChecked(i))
continue;
475 T item = GetListboxItem(listbox, i);
478 if (m_set.size() == listbox->GetCount()) m_set.clear();
483 std::function<void()> m_on_update;
484 const int kEditBtnId;
485 const int kListboxId;
486 const int kListLabelId;
490class IfacePanel :
public SetPanel<std::string> {
493 std::function<
void()> on_update)
494 : SetPanel<std::string>(parent, filter.interfaces, std::move(on_update),
496 [&]() {
return IfacePanel::GetChoices(); }) {}
499 wxArrayString GetChoices()
const {
500 wxArrayString choices;
501 for (
auto& driver : CommDriverRegistry::GetInstance().GetDrivers())
502 choices.Add(driver->iface);
503 choices.Add(
"Internal");
512class BusPanel :
public SetPanel<NavAddr::Bus> {
515 std::function<
void()> on_update)
516 : SetPanel<NavAddr::Bus>(parent, filter.buses, std::move(on_update),
517 _(
"Use message buses"),
518 [&]() {
return BusPanel::GetChoices(); }) {}
521 wxArrayString GetChoices()
const {
522 static const char* choices[] = {
"nmea0183",
"nmea2000",
"SignalK",
"Onenet",
532class DirectionPanel :
public SetPanel<NavmsgStatus::Direction> {
535 std::function<
void()> on_update)
536 : SetPanel<NavmsgStatus::Direction>(
537 parent, filter.directions, std::move(on_update),
538 _(
"Use directions"), [&] {
return DirectionPanel::GetChoices(); }) {
542 wxArrayString GetChoices()
const {
543 static const char* choices[] = {
"Input",
"Handled",
"Output",
"Internal"};
548class AcceptedPanel :
public SetPanel<NavmsgStatus::Accepted> {
551 std::function<
void()> on_update)
552 : SetPanel<NavmsgStatus::Accepted>(
553 parent, filter.accepted, std::move(on_update),
554 _(
"Use Accepted states"),
555 [&]() {
return AcceptedPanel::GetChoices(); }) {}
558 wxArrayString GetChoices()
const {
559 static const char* choices[] = {
"Ok",
"FilteredNoOutput",
565class EditFilterFrame :
public wxFrame {
566 class Buttons :
public wxPanel {
568 Buttons(wxWindow* parent) : wxPanel(parent, wxID_ANY) {
569 auto vbox =
new wxBoxSizer(wxVERTICAL);
570 auto buttons =
new wxStdDialogButtonSizer();
571 buttons->AddButton(
new wxButton(
this, wxID_CLOSE));
572 buttons->AddButton(
new wxButton(
this, wxID_APPLY));
573 vbox->Add(buttons, wxSizerFlags().Expand());
581 EditFilterFrame(wxWindow* parent,
const std::string& name,
582 std::function<
void(
const std::string&)> on_update,
583 std::function<
void(
const std::string&)> on_apply)
584 : wxFrame(parent, wxID_ANY,
585 [name] {
return _(
"Edit filter: ") + name; }()),
586 m_on_update(std::move(on_update)),
587 m_on_apply(std::move(on_apply)),
589 SetName(kEditFilterFrameName + name);
592 auto flags = wxSizerFlags().Border().Expand();
593 auto vbox =
new wxBoxSizer(wxVERTICAL);
595 vbox->Add(
new IfacePanel(
this, m_filter, [&] { Update(); }), flags);
596 vbox->Add(
new wxStaticLine(
this), flags.Expand());
597 auto buspanel =
new BusPanel(
this, m_filter, [&] { Update(); });
598 vbox->Add(buspanel, flags);
599 vbox->Add(
new wxStaticLine(
this), flags.Expand());
600 vbox->Add(
new DirectionPanel(
this, m_filter, [&] { Update(); }), flags);
601 vbox->Add(
new wxStaticLine(
this), flags.Expand());
602 vbox->Add(
new AcceptedPanel(
this, m_filter, [&] { Update(); }), flags);
603 vbox->Add(
new MsgTypePanel(
this, m_filter, [&] { Update(); }), flags);
604 vbox->Add(
new Buttons(
this), flags);
608 Bind(wxEVT_CLOSE_WINDOW, [&](wxCloseEvent&) { Destroy(); });
609 Bind(wxEVT_BUTTON, [&](wxCommandEvent& evt) { OnButtonEvent(evt); });
618 void OnButtonEvent(wxCommandEvent& evt) {
619 if (evt.GetId() == wxID_CLOSE) {
622 }
else if (evt.GetId() == wxID_APPLY) {
628 std::function<void(
const std::string&)> m_on_update;
629 std::function<void(
const std::string&)> m_on_apply;
634void CreateFilterDlg(wxWindow* parent) {
637 auto name = dlg.GetValue().ToStdString();
639 wxMessageDialog msg_dlg(wxTheApp->GetTopWindow(), _(
"Illegal name"));
648 filter.m_name = name;
650 FilterEvents::GetInstance().filter_list_change.Notify();
653void RemoveFilterDlg(wxWindow* parent) {
654 if (GetUserFilters().empty()) {
655 wxMessageDialog dlg(wxTheApp->GetTopWindow(), _(
"No filters created"));
660 int sts = dlg.ShowModal();
661 if (sts != wxID_OK)
return;
663 fs::path path(dlg.GetStringSelection().ToStdString());
665 FilterEvents::GetInstance().filter_list_change.Notify();
666 wxMessageDialog msg_dlg(wxTheApp->GetTopWindow(), _(
"Filter removed"));
669 wxMessageDialog msg_dlg(wxTheApp->GetTopWindow(),
670 _(
"Cannot remove filter"));
675void RenameFilterDlg(wxWindow* parent) {
676 if (GetUserFilters().empty()) {
677 wxMessageDialog dlg(wxTheApp->GetTopWindow(), _(
"No filters to rename"));
682 int sts = dlg.ShowModal();
683 if (sts != wxID_OK)
return;
685 fs::path old_name(dlg.GetStringSelection().ToStdString());
686 wxString caption = wxString(_(
"Renaming ")) + old_name.string();
687 auto new_name_dlg =
new wxTextEntryDialog(parent, _(
"New name:"), caption);
688 int result = new_name_dlg->ShowModal();
689 if (result != wxID_OK)
return;
691 new_name_dlg->GetValue().ToStdString());
694void EditOneFilterDlg(wxWindow* parent,
const std::string& filter) {
695 std::string window_name = kEditFilterFrameName + filter;
696 wxWindow* frame = wxWindow::FindWindowByName(window_name);
701 auto on_update = [](
const std::string& _name) {
702 FilterEvents::GetInstance().filter_update.Notify(_name);
704 auto on_apply = [](
const std::string& _name) {
705 FilterEvents::GetInstance().filter_apply.Notify(_name);
708 frame = wxWindow::FindWindowByName(window_name);
709 assert(frame &&
"Cannot create EditFilter frame");
713void EditFilterDlg(wxWindow* parent) {
714 if (GetUserFilters().empty()) {
715 wxMessageDialog dlg(wxTheApp->GetTopWindow(), _(
"No filters created"));
720 int sts = dlg.ShowModal();
721 if (sts != wxID_OK)
return;
723 EditOneFilterDlg(parent, dlg.GetStringSelection().ToStdString());
The bus panel, filter w r t message types (nmea2000, nmea1083, etc.).
Direction panel, set up filter w r t direction (input, output, etc..) Bound to filter....
Interfaces filtering panel, bound to filter.interfaces.
Message type filter setup GUI bound to filter.include_msg and filter.exclude_msg.
static Accepted StringToAccepted(const std::string &s)
Return Accepted value corresponding to argument s.
Driver registration container, a singleton.
Raw messages layer, supports sending and recieving navmsg messages.
T * GetWindowById(int id)
Return window with given id (which must exist) cast to T*.
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.
bool Rename(const std::string &old_name, const std::string &new_name)
Rename old_name on disk to new.
bool Remove(const std::string &name)
Remove a filter, return ok if no errors.
bool Write(const NavmsgFilter &filter, const std::string &name)
Write contents for given filter to disk.
bool Exists(const std::string &name)
Return true iff filter with given name exists, either system or user defined.
Data Monitor filter storage routines.