OpenCPN Partial API docs
Loading...
Searching...
No Matches
connections_dlg.cpp
Go to the documentation of this file.
1/***************************************************************************
2 * Copyright (C) 2025 Alec Leamas *
3 * *
4 * This program is free software; you can redistribute it and/or modify *
5 * it under the terms of the GNU General Public License as published by *
6 * the Free Software Foundation; either version 2 of the License, or *
7 * (at your option) any later version. *
8 * *
9 * This program is distributed in the hope that it will be useful, *
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of *
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
12 * GNU General Public License for more details. *
13 * *
14 * You should have received a copy of the GNU General Public License *
15 * along with this program; if not, see <https://www.gnu.org/licenses/>. *
16 **************************************************************************/
17
24#include <array>
25#include <algorithm>
26#include <sstream>
27#include <string>
28#include <vector>
29
30#include "gl_headers.h" // Must be included before anything using GL stuff
31
32#include <wx/arrstr.h>
33#include <wx/bitmap.h>
34#include <wx/grid.h>
35#include <wx/panel.h>
36#include <wx/sizer.h>
37#include <wx/spinctrl.h>
38#include <wx/statbox.h>
39#include <wx/statline.h>
40#include <wx/textctrl.h>
41#include <wx/window.h>
42
43#include "model/base_platform.h"
45#include "model/comm_util.h"
46#include "model/config_vars.h"
47#include "model/conn_params.h"
48#include "model/conn_states.h"
49#include "model/gui_events.h"
51
52#include "connections_dlg.h"
53
54#include "color_handler.h"
55#include "color_types.h"
56#include "connection_edit.h"
57#include "conn_params_panel.h"
58#include "gui_lib.h"
59#include "navutil.h"
60#include "ocpn_platform.h"
61#include "options.h"
62#include "priority_gui.h"
63#include "std_filesystem.h"
64#include "model/svg_utils.h"
65
66static constexpr auto TopScrollWindowName = "TopScroll";
67
68static const char* kInfoHeader = _("OpenCPN help").c_str();
69static const char* kInfo = _(R"---(
70Normally OpenCPN sends the RMB (distance and heading to waypoint
71etc) and RMC (position, heading etc.) sentences when there is an
72active waypoint. The talker ID is by default EC.
73
74In certain cases these messages (ECRMB and ECRMC) are required
75also when there is no active waypoint. This option enables this.
76
77Obviously, this option carries risks for vessels which have an
78autopilot which is not engaged manually -- the "fake" RMB messages
79could possibly initiate the autopilot in dangerous ways.
80
81Devices needing this includes the NASA Clipper GPS Repeater. In this
82case the output must also be filtered so that ECRMB and ECRMC are
83the only transmitted messages.
84)---")
85 .c_str();
86
87static const char* kInterfaceExistsMessage =
88 _("Warning: A driver using this interface already exists.").c_str();
89
90static wxString UtfArrowDown() {
91 return wxString::FromUTF8(reinterpret_cast<const char*>(u8"\u25bc"));
92}
93static wxString UtfArrowRight() {
94 return wxString::FromUTF8(reinterpret_cast<const char*>(u8"\u25ba"));
95}
96static wxString UtfCheckMark() {
97 return wxString::FromUTF8(reinterpret_cast<const char*>(u8"\u2713"));
98}
99static wxString UtfGear() {
100 return wxString::FromUTF8(reinterpret_cast<const char*>(u8"\u2699"));
101}
102static wxString UtfFilledCircle() {
103 return wxString::FromUTF8(reinterpret_cast<const char*>(u8"\u25cf"));
104}
105static wxString UtfOpenCircle() {
106 return wxString::FromUTF8(reinterpret_cast<const char*>(u8"\u25c8"));
107}
108static wxString UtfWastebasket() {
109 return wxString::FromUTF8(reinterpret_cast<const char*>(u8"\U0001f5d1"));
110}
111
112static bool IsWindows() {
113 return wxPlatformInfo::Get().GetOperatingSystemId() & wxOS_WINDOWS;
114}
115
116static bool IsAndroid() {
117#ifdef ANDROID
118 return true;
119#else
120 return false;
121#endif
122}
123
124static std::string BitsToDottedMask(const unsigned bits) {
125 const uint32_t mask = 0xffffffff << (32 - bits);
126 std::stringstream ss;
127 ss << ((mask & 0xff000000) >> 24) << ".";
128 ss << ((mask & 0x00ff0000) >> 16) << ".";
129 ss << ((mask & 0x0000ff00) >> 8) << ".";
130 ss << (mask & 0x000000ff);
131 return ss.str();
132}
133
134static ConnectionParams* FindConnectionByIface(const ConnectionParams* new_cp) {
135 for (const auto& cp : TheConnectionParams()) {
136 if (cp->type != new_cp->type) continue;
137 switch (cp->type) {
138 case SERIAL:
139 if (cp->serial_port != new_cp->serial_port) continue;
140 return cp;
141 break;
142 case NETWORK:
143 if (cp->net_protocol != new_cp->net_protocol) continue;
144 if (cp->network_address != new_cp->network_address) continue;
145 if (cp->network_port != new_cp->network_port) continue;
146 return cp;
147 break;
148 case INTERNAL_GPS:
149 return cp;
150 break;
151 case INTERNAL_BT:
152 return cp;
153 break;
154 case SOCKETCAN:
155 if (cp->socket_can_port != new_cp->socket_can_port) continue;
156 return cp;
157 break;
158 case CANABLE:
159 if (cp->serial_port != new_cp->serial_port) continue;
160 return cp;
161 break;
162 default:
163 continue;
164 }
165 }
166 return nullptr;
167}
168
173class BitmapEnableCellRenderer : public wxGridCellBoolRenderer {
174public:
175 BitmapEnableCellRenderer(int _size, ColorScheme cs) : size(_size), m_cs(cs) {}
176
177 [[nodiscard]] BitmapEnableCellRenderer* Clone() const override {
178 return new BitmapEnableCellRenderer(size, m_cs);
179 }
180
181 wxSize GetBestSize(wxGrid& grid, wxGridCellAttr& attr, wxDC& dc, int row,
182 int col) override {
183 return {size, size};
184 }
185
186private:
187 int size;
188 ColorScheme m_cs;
189};
190
194public:
195 explicit ConnCompare(int col) : m_col(col) {}
196
197 bool operator()(const ConnectionParams* p1,
198 const ConnectionParams* p2) const {
199 switch (m_col) {
200 case 0:
201 return static_cast<int>(p1->is_enabled) >
202 static_cast<int>(p2->is_enabled);
203 case 1:
204 return p1->GetCommProtocol() < p2->GetCommProtocol();
205 case 2:
207 case 3:
208 return p1->GetStrippedDSPort() < p2->GetStrippedDSPort();
209 default:
210 return false;
211 }
212 }
213
214private:
215 const int m_col;
216};
217
222public:
226 virtual ~ApplyCancel() = default;
227
229 virtual void Apply() = 0;
230
234 virtual void Cancel() = 0;
235};
236
238class AddConnectionButton final : public wxButton {
239public:
240 AddConnectionButton(wxWindow* parent, obs::EventVar& evt_add_connection,
241 const std::function<void(ConnectionParams* p,
242 bool editing)>& _start_edit_conn)
243 : wxButton(parent, wxID_ANY, _("Add new connection...")),
244 m_evt_add_connection(evt_add_connection),
245 m_start_edit_conn(_start_edit_conn) {
246 Bind(wxEVT_COMMAND_BUTTON_CLICKED,
247 [&](wxCommandEvent&) { OnAddConnection(); });
248 }
249
250private:
251 void OnAddConnection() const { m_start_edit_conn(nullptr, false); }
252
253 obs::EventVar& m_evt_add_connection;
254 std::function<void(ConnectionParams* p, bool editing)> m_start_edit_conn;
255};
256
258class Connections final : public wxGrid {
259public:
260 Connections(wxWindow* parent,
261 const std::vector<ConnectionParams*>& connections,
262 obs::EventVar& on_conn_update,
263 const std::function<void(ConnectionParams* p, bool editing)>&
264 on_edit_conn)
265 : wxGrid(parent, wxID_ANY),
266 m_connections(connections),
267 m_header_column_widths({0, 0, 0, 0, 0, 0, 0}),
268 m_last_tooltip_cell(100),
269 m_cs(GLOBAL_COLOR_SCHEME_DAY),
270 m_on_conn_delete(on_conn_update),
271 m_on_edit_conn(on_edit_conn) {
272 ShowScrollbars(wxSHOW_SB_NEVER, wxSHOW_SB_NEVER);
273 SetTable(new wxGridStringTable(), true);
274 GetTable()->AppendCols(8);
275 HideCol(7);
276 if (IsAndroid()) {
277 SetDefaultRowSize(wxWindow::GetCharHeight() * 2);
278 SetColLabelSize(wxWindow::GetCharHeight() * 2);
279 }
280 static const std::array<wxString, 7> headers = {
281 "", _("Protocol") + " ", _("In/Out"), _("Data port"), _("Status"), "",
282 ""};
283 int ic = 0;
284 for (auto hdr = headers.begin(); hdr != headers.end(); hdr++, ic++) {
285 SetColLabelValue(static_cast<int>(hdr - headers.begin()), *hdr);
286 unsigned col_width = hdr->Length() * wxWindow::GetCharWidth();
287 col_width = wxMax(col_width, 6 * wxWindow::GetCharWidth());
288 m_header_column_widths[ic] = static_cast<int>(col_width);
289 SetColSize(ic, static_cast<int>(col_width));
290 }
291
292 if (IsWindows()) {
293 SetLabelBackgroundColour(GetGlobalColor("DILG1"));
294 SetLabelTextColour(GetGlobalColor("DILG3"));
295 }
296 HideRowLabels();
297 SetColAttributes(parent);
298
299 OnColorChange();
300 ReloadGrid(connections);
301 DisableDragColSize();
302 DisableDragRowSize();
303 wxWindow::Show(GetNumberRows() > 0);
304
305 GetGridWindow()->Bind(wxEVT_MOTION, [&](wxMouseEvent& ev) {
306 OnMouseMove(ev);
307 ev.Skip();
308 });
309 GetGridWindow()->Bind(wxEVT_MOUSEWHEEL,
310 [&](const wxMouseEvent& ev) { OnWheel(ev); });
311 // wxGridEvent.GetCol() and GetRow() are not const until wxWidgets 3.2
312 Bind(wxEVT_GRID_LABEL_LEFT_CLICK,
313 [&](wxGridEvent& ev) { HandleSort(ev.GetCol()); });
314 Bind(wxEVT_GRID_CELL_LEFT_CLICK,
315 [&](wxGridEvent& ev) { OnClickCell(ev.GetRow(), ev.GetCol()); });
316 Bind(wxEVT_PAINT, [&](wxPaintEvent& ev) {
317 SetColAttributes(dynamic_cast<const wxWindow*>(ev.GetEventObject()));
318 ev.Skip();
319 });
320 Bind(wxEVT_SYS_COLOUR_CHANGED,
321 [&](wxSysColourChangedEvent& ev) { OnColorChange(ev); });
322 conn_change_lstnr.Init(
323 m_conn_states.evt_conn_status_change,
324 [&](ObservedEvt&) { OnConnectionChange(m_connections); });
325 }
326
327 void SetColorScheme(const ColorScheme cs) {
328 m_cs = cs;
329 ReloadGrid(m_connections);
330 }
331
332 void OnColorChange() {
333 auto foreground = wxSystemSettings::GetColour(wxSYS_COLOUR_MENUTEXT);
334 auto background = wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW);
335 SetLabelBackgroundColour(background);
336 SetLabelTextColour(foreground);
337 for (int row = 0; row <= GetNumberRows(); row++) {
338 for (unsigned col = 0; col < 7; col++) {
339 SetCellBackgroundColour(row, col, background);
340 SetCellTextColour(row, col, foreground);
341 }
342 }
343 }
344
345 void OnColorChange(wxSysColourChangedEvent& ev) {
346 OnColorChange();
347 ev.Skip();
348 }
349
350 wxSize GetEstimatedSize() const {
351 unsigned rs = 0;
352 for (auto s : m_header_column_widths) rs += s;
353 return {static_cast<int>(rs), -1};
354 }
355
357 static void OnWheel(const wxMouseEvent& ev) {
358 auto w =
359 dynamic_cast<wxScrolledWindow*>(FindWindowByName(TopScrollWindowName));
360 assert(w && "No TopScroll window found");
361 int xpos;
362 int ypos;
363 w->GetViewStart(&xpos, &ypos);
364 int x;
365 int y;
366 w->GetScrollPixelsPerUnit(&x, &y);
367 // Not sure where the factor "4" comes from...
368 const int dir = ev.GetWheelRotation();
369 w->Scroll(-1, ypos - dir / y / 4);
370 }
371
373 void ReloadGrid(const std::vector<ConnectionParams*>& connections) {
374 ClearGrid();
375
376 for (auto it = connections.begin(); it != connections.end(); ++it) {
377 const auto row = static_cast<int>(it - connections.begin());
378 EnsureRows(row);
379 SetCellValue(row, 0, (*it)->is_enabled ? "1" : "");
380 if ((*it)->is_enabled)
381 m_tooltips[row][0] = _("Enabled, click to disable");
382 else
383 m_tooltips[row][0] = _("Disabled, click to enable");
384 std::string protocol = NavAddr::BusToString((*it)->GetCommProtocol());
385 SetCellValue(row, 1, protocol);
386 SetCellValue(row, 2, (*it)->GetPortDirectionValueStr());
387 SetCellValue(row, 3, (*it)->GetStrippedDSPort());
388 m_tooltips[row][3] = (*it)->user_comment;
389 SetCellValue(row, 5, UtfGear());
390 m_tooltips[row][5] = _("Edit connection");
391 SetCellValue(row, 6, UtfWastebasket());
392 m_tooltips[row][6] = _("Delete connection");
393 SetCellValue(row, 7, (*it)->GetKey());
394
395 if (IsAndroid()) {
396 wxString sp(protocol);
397 unsigned size = sp.Length() * wxWindow::GetCharWidth();
398 m_header_column_widths[1] = std::max(m_header_column_widths[1], size);
399 size = (*it)->GetPortDirectionValueStr().Length() *
400 wxWindow::GetCharWidth();
401 m_header_column_widths[2] = std::max(m_header_column_widths[2], size);
402 sp = wxString((*it)->GetStrippedDSPort());
403 size = sp.Length() * wxWindow::GetCharWidth();
404 m_header_column_widths[3] = std::max(m_header_column_widths[3], size);
405 }
406 }
407 OnConnectionChange(m_connections);
408 OnColorChange();
409
410 if (IsAndroid()) {
411 int ic = 0;
412 for (auto val : m_header_column_widths) {
413 SetColSize(ic, static_cast<int>(val));
414 ic++;
415 }
416 } else
417 AutoSize();
418 }
419
422 public:
423 explicit ConnStateCompare(Connections* connections)
424 : m_conns(connections) {}
425 bool operator()(const ConnectionParams* p1,
426 const ConnectionParams* p2) const {
427 int row1 = m_conns->FindConnectionIndex(p1);
428 int row2 = m_conns->FindConnectionIndex(p2);
429 if (row1 == -1 && row2 == -1) return false;
430 if (row1 == -1) return false;
431 if (row2 == -1) return true;
432 const int v1 = m_conns->GetCellValue(row1, 4)[0];
433 const int v2 = m_conns->GetCellValue(row2, 4)[0];
434 return v1 < v2;
435 }
436 Connections* m_conns;
437 };
438
444 using namespace std;
445 auto key = cp->GetKey();
446 auto found = find_if(
447 m_connections.begin(), m_connections.end(),
448 [key](const ConnectionParams* cp) { return cp->GetKey() == key; });
449 if (found == m_connections.end()) return -1;
450 return static_cast<int>(found - m_connections.begin());
451 }
452
453private:
458 ConnectionParams* FindRowConnection(const int row) const {
459 auto found = find_if(m_connections.begin(), m_connections.end(),
460 [&](const ConnectionParams* p) {
461 return GetCellValue(row, 7) == p->GetKey();
462 });
463 return found != m_connections.end() ? *found : nullptr;
464 }
465
470 void EnsureRows(size_t rows) {
471 for (unsigned i = m_tooltips.size(); i <= rows; i++)
472 m_tooltips.emplace_back(std::vector<std::string>(7));
473 for (unsigned i = GetNumberRows(); i <= rows; i++) AppendRows(1, false);
474 }
475
477 void SetColAttributes(const wxWindow* parent) {
478 if (IsWindows()) {
479 // Set all cells to global color scheme
480 SetDefaultCellBackgroundColour(GetGlobalColor("DILG1"));
481 }
482 auto enable_attr = new wxGridCellAttr();
483 enable_attr->SetAlignment(wxALIGN_CENTRE, wxALIGN_CENTRE);
484
485 if (IsAndroid())
486 enable_attr->SetRenderer(
487 new BitmapEnableCellRenderer(wxWindow::GetCharWidth() * 3 / 2, m_cs));
488 else
489 enable_attr->SetRenderer(new wxGridCellBoolRenderer());
490
491 enable_attr->SetEditor(new wxGridCellBoolEditor());
492 if (IsWindows()) enable_attr->SetBackgroundColour(GetGlobalColor("DILG1"));
493 SetColAttr(0, enable_attr);
494
495 auto protocol_attr = new wxGridCellAttr();
496 protocol_attr->SetAlignment(wxALIGN_LEFT, wxALIGN_CENTRE);
497 protocol_attr->SetReadOnly(true);
498 if (IsWindows())
499 protocol_attr->SetBackgroundColour(GetGlobalColor("DILG1"));
500 SetColAttr(1, protocol_attr);
501
502 auto in_out_attr = new wxGridCellAttr();
503 in_out_attr->SetAlignment(wxALIGN_CENTRE, wxALIGN_CENTRE);
504 in_out_attr->SetReadOnly(true);
505 if (IsWindows()) in_out_attr->SetBackgroundColour(GetGlobalColor("DILG1"));
506 SetColAttr(2, in_out_attr);
507
508 auto port_attr = new wxGridCellAttr();
509 port_attr->SetAlignment(wxALIGN_LEFT, wxALIGN_CENTRE);
510 port_attr->SetReadOnly(true);
511 if (IsWindows()) port_attr->SetBackgroundColour(GetGlobalColor("DILG1"));
512 SetColAttr(3, port_attr);
513
514 auto status_attr = new wxGridCellAttr();
515 status_attr->SetAlignment(wxALIGN_CENTRE, wxALIGN_CENTRE);
516 status_attr->SetReadOnly(true);
517 status_attr->SetFont(parent->GetFont().Scale(1.3));
518 if (IsWindows()) status_attr->SetBackgroundColour(GetGlobalColor("DILG1"));
519 SetColAttr(4, status_attr);
520
521 auto edit_attr = new wxGridCellAttr();
522 edit_attr->SetAlignment(wxALIGN_CENTRE, wxALIGN_CENTRE);
523 edit_attr->SetFont(parent->GetFont().Scale(1.3));
524 edit_attr->SetReadOnly(true);
525 if (IsWindows()) edit_attr->SetBackgroundColour(GetGlobalColor("DILG1"));
526 SetColAttr(5, edit_attr);
527
528 auto delete_attr = new wxGridCellAttr();
529 delete_attr->SetAlignment(wxALIGN_LEFT, wxALIGN_CENTRE);
530 delete_attr->SetFont(parent->GetFont().Scale(1.3));
531 delete_attr->SetTextColour(*wxRED);
532 delete_attr->SetReadOnly(true);
533 if (IsWindows()) delete_attr->SetBackgroundColour(GetGlobalColor("DILG1"));
534 SetColAttr(6, delete_attr);
535 }
536
538 void OnClickCell(int row, int col) {
539 if (col == 0)
540 HandleEnable(row);
541 else if (col == 5) {
542 HandleEdit(row);
543 } else if (col == 6) {
544 HandleDelete(row);
545 }
546 }
547
549 void OnMouseMove(const wxMouseEvent& ev) {
550 const wxPoint pt = ev.GetPosition();
551 int row = YToRow(pt.y);
552 int col = XToCol(pt.x);
553 if (col < 0 || col >= 7 || row < 0 || row >= GetNumberRows()) return;
554 if (row * 7 + col == m_last_tooltip_cell) return;
555 m_last_tooltip_cell = row * 7 + col;
556 GetGridWindow()->SetToolTip(m_tooltips[row][col]);
557 }
558
560 void OnConnectionChange(const std::vector<ConnectionParams*>& connections) {
561 bool refresh_needed = false;
562 for (auto it = connections.begin(); it != connections.end(); ++it) {
563 ConnState state = m_conn_states.GetDriverState(
564 (*it)->GetCommProtocol(), (*it)->GetStrippedDSPort());
565 if (!(*it)->is_enabled) state = ConnState::Disabled;
566 auto row = static_cast<int>(it - connections.begin());
567 EnsureRows(row);
568 switch (state) {
569 case ConnState::Disabled:
570 SetCellValue(row, 4, UtfFilledCircle());
571 m_tooltips[row][4] = _("Disabled");
572 refresh_needed = true;
573 break;
574 case ConnState::NoStats:
575 SetCellValue(row, 4, UtfOpenCircle());
576 m_tooltips[row][4] = _("No driver statistics available");
577 refresh_needed = true;
578 break;
579 case ConnState::NoData:
580 SetCellValue(row, 4, "!");
581 m_tooltips[row][4] = _("No data flowing through connection");
582 refresh_needed = true;
583 break;
584 case ConnState::Unavailable:
585 SetCellValue(row, 4, "X");
586 m_tooltips[row][4] = _("The device is unavailable");
587 refresh_needed = true;
588 break;
589 case ConnState::Ok:
590 SetCellValue(row, 4, UtfCheckMark());
591 m_tooltips[row][4] = _("Data is flowing");
592 refresh_needed = true;
593 break;
594 }
595 }
596 if (refresh_needed) ForceRefresh();
597 }
598
600 void SetSortingColumn(int col) {
601 if (GetSortingColumn() != wxNOT_FOUND) {
602 int old_col = GetSortingColumn();
603 auto label = GetColLabelValue(old_col);
604 if (label[0] == UtfArrowDown()[0])
605 SetColLabelValue(old_col, label.substr(2));
606 }
607 auto label = GetColLabelValue(col);
608 if (label[0] != UtfArrowDown()[0])
609 SetColLabelValue(col, UtfArrowDown() + " " + label);
610 wxGrid::SetSortingColumn(col);
611 Fit();
612 }
613
615 void HandleSort(int col) {
616 if (col > 4) return;
617 auto& params = TheConnectionParams();
618 if (col < 4)
619 std::sort(params.begin(), params.end(), ConnCompare(col));
620 else // col == 4
621 std::sort(params.begin(), params.end(), ConnStateCompare(this));
623 SetSortingColumn(col);
624 }
625
627 void HandleEnable(int row) {
628 ConnectionParams* cp = FindRowConnection(row);
629 if (!cp) return;
630 cp->is_enabled = !cp->is_enabled;
631 cp->is_setup = FALSE; // trigger a rebuild/takedown of the connection
632 SetCellValue(row, 0, cp->is_enabled ? "1" : "");
633 if (cp->is_enabled)
634 m_tooltips[row][0] = _("Enabled, click to disable");
635 else
636 m_tooltips[row][0] = _("Disabled, click to enable");
637 DriverStats stats;
638 stats.driver_iface = cp->GetStrippedDSPort();
639 stats.driver_bus = cp->GetCommProtocol();
640 m_conn_states.HandleDriverStats(stats);
641 StopAndRemoveCommDriver(cp->GetStrippedDSPort(), cp->GetCommProtocol());
642 if (cp->is_enabled) MakeCommDriver(cp);
643 cp->is_setup = true;
644 if (!cp->is_enabled) {
645 SetCellValue(row, 4, UtfFilledCircle());
646 // ForceRefresh() apparently broken, see #4648
648 }
649 }
650
652 void HandleEdit(int row) {
653 if (ConnectionParams* cp = FindRowConnection(row)) {
654 Show(GetNumberRows() > 0);
655 m_on_edit_conn(cp, true);
656 }
657 }
658
660 void HandleDelete(int row) {
661 ConnectionParams* cp = FindRowConnection(row);
662 auto found = std::find(m_connections.begin(), m_connections.end(), cp);
663 if (found != m_connections.end()) {
664 std::stringstream ss;
665 ss << _("Ok to delete connection on ") << (*found)->GetStrippedDSPort();
666 int rcode = OCPNMessageBox(this, ss.str(), _("Delete connection?"),
667 wxOK | wxCANCEL);
668 if (rcode != wxID_OK && rcode != wxID_YES) return;
669 delete (*found)->options_panel;
670 StopAndRemoveCommDriver((*found)->GetStrippedDSPort(),
671 (*found)->GetCommProtocol());
672 TheConnectionParams().erase(found);
673 if (GetNumberRows() > static_cast<int>(m_connections.size()))
674 DeleteRows(GetNumberRows() - 1);
675 m_on_conn_delete.Notify();
676 }
677 }
678
679 obs::Listener conn_change_lstnr;
680 std::vector<std::vector<std::string>> m_tooltips;
681 ConnStates m_conn_states;
682 const std::vector<ConnectionParams*>& m_connections;
683 std::array<unsigned, 7> m_header_column_widths;
684 int m_last_tooltip_cell;
685 ColorScheme m_cs;
686 obs::EventVar& m_on_conn_delete;
687 std::function<void(ConnectionParams* p, bool editing)> m_on_edit_conn;
688};
689
691class GeneralPanel : public wxPanel {
692public:
693 explicit GeneralPanel(wxWindow* parent, wxSize max_size)
694 : wxPanel(parent, wxID_ANY) {
695 auto sizer = new wxStaticBoxSizer(wxVERTICAL, this, _("General"));
696 SetSizer(sizer);
697 auto flags = wxSizerFlags().Border();
698 m_upload_options = new UploadOptionsChoice(this);
699 sizer->Add(m_upload_options, flags);
700 sizer->Add(new PrioritiesBtn(this), flags);
701 wxWindow::SetMaxSize(max_size);
702 }
703 void SetColorScheme(ColorScheme) {
704 DimeControl(m_upload_options);
705 Refresh();
706 }
707
708private:
710 class PrioritiesBtn final : public wxButton {
711 public:
712 explicit PrioritiesBtn(wxWindow* parent)
713 : wxButton(parent, wxID_ANY, _("Adjust Nav data priorities...")) {
714 Bind(wxEVT_COMMAND_BUTTON_CLICKED, [&](wxCommandEvent&) {
715 PriorityDlg dialog(this->GetParent());
716 dialog.ShowModal();
717 });
718 }
719 };
720
722 class UploadOptionsChoice final : public wxRadioBox, public ApplyCancel {
723 public:
724 explicit UploadOptionsChoice(wxWindow* parent) : wxRadioBox() {
725 wxArrayString wx_choices;
726 for (auto& c : choices) wx_choices.Add(c);
727 Create(parent, wxID_ANY, _("Upload Format"), wxDefaultPosition,
728 wxDefaultSize, wx_choices, 0, wxRA_SPECIFY_ROWS);
729 DimeControl(this);
730 UploadOptionsChoice::Cancel();
731 }
732
733 void Cancel() override {
734 if (g_bGarminHostUpload)
735 SetSelection(1);
736 else if (g_GPS_Ident == "FurunoGP3X")
737 SetSelection(2);
738 else
739 SetSelection(0);
740 }
741
742 void Apply() override {
743 switch (GetSelection()) {
744 case 0:
745 g_bGarminHostUpload = false;
746 g_GPS_Ident = "Generic";
747 break;
748 case 1:
749 g_bGarminHostUpload = true;
750 g_GPS_Ident = "Generic";
751 break;
752 case 2:
753 g_bGarminHostUpload = false;
754 g_GPS_Ident = "FurunoGP3X";
755 break;
756 default:
757 assert(false && "Invalid upload case option");
758 }
759 }
760
761 const std::array<wxString, 3> choices = {
762 _("Generic NMEA 0183"), _("Garmin Host mode"), _("Furuno GP4X")};
763 };
764
765 UploadOptionsChoice* m_upload_options;
766};
767
769class ShowAdvanced : public wxStaticText {
770public:
771 ShowAdvanced(wxWindow* parent, std::function<void(bool)> on_toggle)
772 : wxStaticText(parent, wxID_ANY, ""),
773 m_on_toggle(std::move(on_toggle)),
774 m_show_advanced(true) {
775 Toggle();
776 Bind(wxEVT_LEFT_DOWN, [&](const wxMouseEvent& ev) { Toggle(); });
777 }
778
779private:
780 wxString m_label = _("Advanced ");
781 std::function<void(bool)> m_on_toggle;
782 bool m_show_advanced;
783
784 void Toggle() {
785 m_show_advanced = !m_show_advanced;
786 m_label = _("Advanced ");
787 m_label += m_show_advanced ? UtfArrowDown() : UtfArrowRight();
788 SetLabelText(m_label);
789 m_on_toggle(m_show_advanced);
790 }
791};
792
794class AdvancedPanel : public wxPanel {
795public:
796 explicit AdvancedPanel(wxWindow* parent, wxSize max_size)
797 : wxPanel(parent, wxID_ANY) {
798 auto sizer = new wxStaticBoxSizer(wxVERTICAL, this, "");
799 sizer->Add(new BearingsCheckbox(this), wxSizerFlags().Expand());
800 sizer->Add(new ExtraRmbRmcLine(this), wxSizerFlags().Expand());
801 sizer->Add(new NmeaFilterRow(this), wxSizerFlags().Expand());
802 sizer->Add(new TalkerIdRow(this), wxSizerFlags().Expand());
803 sizer->Add(new NetmaskRow(this), wxSizerFlags().Expand());
804 SetSizer(sizer);
805 wxWindow::SetMaxSize(max_size);
806 }
807
808private:
810 class BearingsCheckbox : public wxCheckBox, public ApplyCancel {
811 public:
812 explicit BearingsCheckbox(wxWindow* parent)
813 : wxCheckBox(parent, wxID_ANY,
814 _("Use magnetic bearing in output sentence APB")) {
815 wxCheckBox::SetValue(g_bMagneticAPB);
816 }
817
818 void Apply() override { g_bMagneticAPB = GetValue(); }
819 void Cancel() override { SetValue(g_bMagneticAPB); }
820 };
821
822 class ExtraRmbRmcLine : public wxPanel {
823 class RmbRmcCheckbox : public wxCheckBox, public ApplyCancel {
824 public:
825 explicit RmbRmcCheckbox(wxWindow* parent)
826 : wxCheckBox(parent, wxID_ANY,
827 _("Always send RMB and RMC NMEA0183 data")) {
828 wxCheckBox::SetValue(g_always_send_rmb_rmc);
829 }
830
831 void Apply() override { g_always_send_rmb_rmc = GetValue(); }
832 void Cancel() override { SetValue(g_always_send_rmb_rmc); }
833 };
834
835 public:
836 explicit ExtraRmbRmcLine(wxWindow* parent) : wxPanel(parent, wxID_ANY) {
837 auto hbox = new wxBoxSizer(wxHORIZONTAL);
838 hbox->Add(new RmbRmcCheckbox(this), wxSizerFlags().Expand());
839 hbox->Add(1, 1, 1, wxEXPAND);
840 hbox->Add(new InfoButton(this, g_btouch, kInfoHeader, kInfo),
841 wxSizerFlags().Border());
842 SetSizer(hbox);
843 wxWindow::Layout();
844 wxWindow::Show();
845 }
846 };
847 class ExtraRmbRmcCheckbox final : public wxCheckBox, public ApplyCancel {
848 public:
849 explicit ExtraRmbRmcCheckbox(wxWindow* parent)
850 : wxCheckBox(parent, wxID_ANY,
851 _("Always send RMB and RMC NMEA0183 data")) {
852 wxCheckBox::SetValue(g_always_send_rmb_rmc);
853 }
854
855 void Apply() override { g_always_send_rmb_rmc = GetValue(); }
856 void Cancel() override { SetValue(g_always_send_rmb_rmc); }
857 };
858
860 class NmeaFilterRow : public wxPanel, public ApplyCancel {
861 wxCheckBox* checkbox;
862 wxTextCtrl* filter_period;
863
864 public:
865 explicit NmeaFilterRow(wxWindow* parent) : wxPanel(parent) {
866 auto hbox = new wxBoxSizer(wxHORIZONTAL);
867 checkbox = new wxCheckBox(
868 this, wxID_ANY,
869 _("Filter NMEA course and speed data. Filter period: "));
870 checkbox->SetValue(g_bfilter_cogsog);
871 hbox->Add(checkbox, wxSizerFlags().Align(wxALIGN_CENTRE));
872 filter_period =
873 new wxTextCtrl(this, wxID_ANY, "", wxDefaultPosition,
874 wxSize(50, 3 * wxWindow::GetCharWidth()), 0);
875 filter_period->SetValue(std::to_string(g_COGFilterSec));
876 hbox->Add(filter_period, wxSizerFlags().Border());
877 SetSizer(hbox);
878 NmeaFilterRow::Cancel();
879 }
880
881 void Apply() override {
882 std::stringstream ss;
883 ss << filter_period->GetValue();
884 ss >> g_COGFilterSec;
885 g_bfilter_cogsog = checkbox->GetValue();
886 }
887
888 void Cancel() override {
889 std::stringstream ss;
890 ss << g_COGFilterSec;
891 filter_period->SetValue(ss.str());
892 checkbox->SetValue(g_bfilter_cogsog);
893 }
894 };
895
897 class TalkerIdRow : public wxPanel, public ApplyCancel {
898 wxTextCtrl* text_ctrl;
899
900 public:
901 explicit TalkerIdRow(wxWindow* parent) : wxPanel(parent) {
902 auto hbox = new wxBoxSizer(wxHORIZONTAL);
903 hbox->Add(new wxStaticText(this, wxID_ANY, _("NMEA 0183 Talker Id: ")),
904 wxSizerFlags().Align(wxALIGN_CENTRE_VERTICAL).Border());
905 text_ctrl = new wxTextCtrl(
906 this, wxID_ANY, "", wxDefaultPosition,
907 wxSize(4 * wxWindow::GetCharWidth(), 3 * wxWindow::GetCharWidth()));
908 text_ctrl->SetToolTip(
909 _("Enter a two-letter talker ID to override the default ID in NMEA "
910 "sentences generated by OpenCPN (e.g., GP, HC, EC). This affects "
911 "only sentences created by OpenCPN, not those forwarded from other "
912 "devices."));
913 text_ctrl->SetValue(g_TalkerIdText);
914 hbox->Add(text_ctrl, wxSizerFlags().Border());
915 SetSizer(hbox);
916 TalkerIdRow::Cancel();
917 }
918
919 void Apply() override { g_TalkerIdText = text_ctrl->GetValue(); }
920 void Cancel() override { text_ctrl->SetValue(g_TalkerIdText); }
921 };
922
924 class NetmaskRow : public wxPanel, public ApplyCancel {
925 public:
926 explicit NetmaskRow(wxWindow* parent)
927 : wxPanel(parent),
928 m_spin_ctrl(new wxSpinCtrl(this, wxID_ANY)),
929 m_text(new wxStaticText(this, wxID_ANY, "")) {
930 m_spin_ctrl->SetRange(8, 32);
931 auto hbox = new wxBoxSizer(wxHORIZONTAL);
932 auto flags = wxSizerFlags().Align(wxALIGN_CENTRE_VERTICAL).Border();
933 hbox->Add(new wxStaticText(this, wxID_ANY, _("Netmask: ")), flags);
934 hbox->Add(m_text, flags);
935 hbox->Add(new wxStaticText(this, wxID_ANY, _("length (bits): ")), flags);
936 hbox->Add(m_spin_ctrl, flags);
937 SetSizer(hbox);
938 NetmaskRow::Cancel();
939
940 Bind(wxEVT_SPINCTRL, [&](wxSpinEvent&) {
941 m_text->SetLabel(BitsToDottedMask(m_spin_ctrl->GetValue()));
942 Layout();
943 });
944 }
945
946 void Apply() override { g_netmask_bits = m_spin_ctrl->GetValue(); }
947
948 void Cancel() override {
949 m_spin_ctrl->SetValue(g_netmask_bits);
950 m_text->SetLabel(BitsToDottedMask(m_spin_ctrl->GetValue()));
951 Layout();
952 }
953
954 private:
955 wxSpinCtrl* m_spin_ctrl;
956 wxStaticText* m_text;
957 };
958};
959
961class TopPanel : public wxPanel {
962public:
963 TopPanel(wxWindow* parent, const std::vector<ConnectionParams*>& connections,
964 obs::EventVar& evt_add_connection,
965 const std::function<void(ConnectionParams* p, bool editing)>&
966 on_edit_conn)
967 : wxPanel(parent, wxID_ANY),
968 m_evt_add_connection(evt_add_connection),
969 m_connections(connections) {
970 auto vbox = new wxBoxSizer(wxVERTICAL);
971 auto conn_grid = new Connections(this, m_connections, m_evt_add_connection,
972 on_edit_conn);
973 wxSize panel_max_size(conn_grid->GetEstimatedSize());
974 vbox->AddSpacer(wxWindow::GetCharHeight());
975 auto conn_flags = wxSizerFlags().Border();
976 if (IsAndroid()) conn_flags = wxSizerFlags().Border().Expand();
977 vbox->Add(conn_grid, conn_flags);
978 vbox->Add(new AddConnectionButton(this, m_evt_add_connection, on_edit_conn),
979 wxSizerFlags().Border());
980 vbox->Add(0, wxWindow::GetCharHeight()); // Expanding spacer
981 auto panel_flags =
982 wxSizerFlags().Border(wxLEFT | wxDOWN | wxRIGHT).Expand();
983 m_general_panel = new GeneralPanel(this, panel_max_size);
984 vbox->Add(m_general_panel, panel_flags);
985
986 auto advanced_panel = new AdvancedPanel(this, panel_max_size);
987 m_advanced_panel = advanced_panel;
988 auto on_toggle = [&, advanced_panel, vbox](bool show) {
989 // FIXME advanced_panel->SetMaxSize({conn_grid->GetSize().x, -1});
990 advanced_panel->Show(show);
991 vbox->SetSizeHints(this);
992 vbox->Fit(this);
993 };
994 vbox->Add(new ShowAdvanced(this, on_toggle), panel_flags);
995 vbox->Add(advanced_panel, panel_flags.ReserveSpaceEvenIfHidden());
996
997 SetSizer(vbox);
998 vbox->SetSizeHints(this);
999 vbox->Fit(this);
1000 wxWindow::Fit();
1001 wxWindow::Show();
1002
1003 auto on_evt_update_connections = [&, conn_grid](ObservedEvt&) {
1004 conn_grid->ReloadGrid(TheConnectionParams());
1005 conn_grid->Show(conn_grid->GetNumberRows() > 0);
1006 Layout();
1007 };
1008 m_add_connection_lstnr.Init(m_evt_add_connection,
1009 on_evt_update_connections);
1010 m_conn_grid = conn_grid;
1011 }
1012
1013 void SetColorScheme(const ColorScheme cs) const {
1014 m_conn_grid->SetColorScheme(cs);
1015 m_general_panel->SetColorScheme(cs);
1016 }
1017 [[nodiscard]] Connections* GetConnectionsGrid() const { return m_conn_grid; }
1018
1019 obs::EventVar& m_evt_add_connection;
1020
1021private:
1022 const std::vector<ConnectionParams*>& m_connections;
1023 obs::Listener m_add_connection_lstnr;
1024 Connections* m_conn_grid;
1025 GeneralPanel* m_general_panel;
1026 AdvancedPanel* m_advanced_panel;
1027};
1028
1030class TopScroll : public wxScrolledWindow {
1031public:
1032 TopScroll(wxWindow* parent, const std::vector<ConnectionParams*>& connections,
1033 obs::EventVar& evt_add_connection)
1034 : wxScrolledWindow(parent, wxID_ANY, wxDefaultPosition, wxDefaultSize,
1035 wxVSCROLL | wxHSCROLL, TopScrollWindowName) {
1036 ShowScrollbars(wxSHOW_SB_NEVER, wxSHOW_SB_ALWAYS);
1037 auto vbox = new wxBoxSizer(wxVERTICAL);
1038 SetSizer(vbox);
1039
1040 auto on_edit_connection = [&](ConnectionParams* p, bool editing) {
1041 if (editing)
1042 HandleEdit(p);
1043 else
1044 HandleNew(p);
1045 };
1046
1047 top_panel =
1048 new TopPanel(this, connections, evt_add_connection, on_edit_connection);
1049 vbox->Add(top_panel, wxSizerFlags(1).Expand());
1050
1051 auto on_edit_click = [&](const ConnectionParams* p, bool new_mode,
1052 bool ok_cancel) {
1053 HandleEditFinish(p, new_mode, ok_cancel);
1054 };
1055
1056 m_edit_panel = new ConnectionEditDialog(this, on_edit_click);
1057 m_edit_panel->SetPropsLabel(_("Edit Selected Connection"));
1058 wxWindow* options = FindWindowByName("Options");
1059 assert(options && "Null Options window!");
1060 int fraction = 9;
1061#ifdef ANDROID
1062 fraction = 10;
1063#endif
1064 m_edit_panel->SetSize(
1065 wxSize(options->GetSize().x, options->GetSize().y * fraction / 10));
1066 vbox->Add(m_edit_panel, wxSizerFlags(0).Expand());
1067 m_edit_panel->Hide();
1068
1069 SetScrollRate(0, 10);
1070 if (IsAndroid()) SetScrollRate(1, 1);
1071
1072 SetScrollRate(0, 10);
1073 if (IsAndroid()) SetScrollRate(1, 1);
1074 }
1075
1076 void SetColorScheme(ColorScheme cs) const {
1077 if (top_panel) top_panel->SetColorScheme(cs);
1078 }
1079
1080 void HandleEdit(ConnectionParams* p) {
1081 m_edit_panel->SetPropsLabel(_("Edit Selected Connection"));
1082 m_edit_panel->SetNewMode(false);
1083 m_edit_panel->PreloadControls(p);
1084 m_edit_panel->AddOKCancelButtons();
1085 SwitchToEditor();
1086 }
1087
1088 void HandleNew(ConnectionParams*) {
1089 m_edit_panel->SetPropsLabel(_("Configure new connection"));
1090 m_edit_panel->SetDefaultConnectionParams();
1091 m_edit_panel->SetNewMode(true);
1092 m_edit_panel->AddOKCancelButtons();
1093 m_edit_panel->InitiateNewConnection();
1094 SwitchToEditor();
1095 }
1096
1097 void SwitchToEditor() {
1098 top_panel->Hide(); // TopPanel
1099 Scroll(0, 0);
1100 DimeControl(m_edit_panel);
1101 m_edit_panel->Show();
1102 g_options->ShowOKButtons(false);
1103 Layout();
1104 }
1105
1106 void SwitchToGrid() {
1107 g_options->ShowOKButtons(true);
1108 m_edit_panel->Hide();
1109 top_panel->Show();
1110 top_panel->GetConnectionsGrid()->ReloadGrid(TheConnectionParams());
1111 Layout();
1112 Scroll(0, 0);
1113 }
1114
1115 void HandleEditFinish(const ConnectionParams* cp_orig, bool new_mode,
1116 bool ok_cancel) {
1117 if (!ok_cancel) {
1118 SwitchToGrid();
1119 return;
1120 }
1121 // OK from EDIT mode
1122 if (!new_mode) {
1123 ConnectionParams* cp_edited = m_edit_panel->GetParamsFromControls();
1124 delete cp_orig->options_panel;
1125 StopAndRemoveCommDriver(cp_orig->GetStrippedDSPort(),
1126 cp_orig->GetCommProtocol());
1127 int index = top_panel->GetConnectionsGrid()->FindConnectionIndex(cp_orig);
1128 assert(index != -1 && "Cannot look up connection index");
1129 TheConnectionParams()[index] = cp_edited;
1130 cp_edited->is_setup = false; // Trigger new stream
1131 }
1132 // OK from NEW mode
1133 else {
1134 ConnectionParams* cp = m_edit_panel->GetParamsFromControls();
1135 if (cp) {
1136 if (FindConnectionByIface(cp)) {
1137 wxMessageDialog dialog(this, kInterfaceExistsMessage,
1138 _("Driver creation warning"),
1139 wxOK | wxCENTRE | wxICON_WARNING);
1140 dialog.ShowModal();
1141 }
1142 if (cp->IsPortValid()) {
1143 // Trigger new stream
1144 cp->is_setup = false;
1145 // transfer ownership FIXME use smart pointers
1146 TheConnectionParams().push_back(cp);
1147 } else {
1148 wxString msg =
1149 _("Unable to create a connection as configured. "
1150 "Connected port or address was missing.");
1151 auto& noteman = NotificationManager::GetInstance();
1152 noteman.AddNotification(NotificationSeverity::kWarning,
1153 msg.ToStdString(), 60);
1154 delete cp;
1155 }
1156 }
1157 UpdateDatastreams();
1158 top_panel->m_evt_add_connection.Notify();
1159 }
1160
1161 SwitchToGrid();
1162 UpdateDatastreams();
1163 }
1164
1165private:
1166 TopPanel* top_panel;
1167 ConnectionEditDialog* m_edit_panel;
1168};
1169
1172 wxWindow* parent, const std::vector<ConnectionParams*>& connections)
1173 : wxPanel(parent, wxID_ANY), m_connections(connections) {
1174 const auto vbox = new wxBoxSizer(wxVERTICAL);
1175 vbox->Add(new TopScroll(this, connections, m_evt_add_connection),
1176 wxSizerFlags(1).Expand());
1177 SetSizer(vbox);
1178 wxWindow::Fit();
1179 wxWindow::Show();
1180}
1181
1182void ConnectionsDlg::OnResize(const wxSize& size) {
1183 auto w = FindWindowByName(TopScrollWindowName);
1184 if (!w) return;
1185 w->SetMinSize(size);
1186 Fit();
1187}
1188
1189void ConnectionsDlg::DoApply(wxWindow* root) {
1190 for (wxWindow* child : root->GetChildren()) {
1191 if (auto widget = dynamic_cast<ApplyCancel*>(child)) widget->Apply();
1192 DoApply(child);
1193 }
1194}
1195
1196void ConnectionsDlg::DoCancel(wxWindow* root) {
1197 for (wxWindow* child : root->GetChildren()) {
1198 if (auto widget = dynamic_cast<ApplyCancel*>(child)) widget->Cancel();
1199 DoCancel(child);
1200 }
1201}
1202
1203void ConnectionsDlg::ApplySettings() { DoApply(this); }
1204
1205void ConnectionsDlg::CancelSettings() { DoCancel(this); }
1206
1207void ConnectionsDlg::SetColorScheme(ColorScheme cs) {
1208 auto w = dynamic_cast<TopScroll*>(FindWindowByName(TopScrollWindowName));
1209 if (w) w->SetColorScheme(cs);
1210}
Basic platform specific support utilities without GUI deps.
The "Add new connection" button.
Indeed: The "Advanced" panel.
Interface implemented by widgets supporting Apply and Cancel.
virtual ~ApplyCancel()=default
Destroy the Apply Cancel object.
virtual void Apply()=0
Make values set by user actually being used.
virtual void Cancel()=0
Restore values modified by user to their pristine state, often in a global.
Custom renderer class for rendering ENABLE in a grid cell.
std::sort support: Compare twSetInitialSettingso ConnectionParams w r t given column
Filter reading driver driver_stats status reports from CommDriverRegistry transforming these to a str...
Definition conn_states.h:64
obs::EventVar evt_conn_status_change
Notified with a shared_ptr<ConnData> when the state is changed.
Definition conn_states.h:70
Dialog for editing connection parameters.
ConnectionParams * GetParamsFromControls()
Return parameters instance populated from UI elements owned by caller.
Connection data container close to a POD struct.
Definition conn_params.h:76
bool IsPortValid() const
Return true if port data like com port or network address are sane.
std::string GetStrippedDSPort() const
Return port string with possible windows extra data removed, in some cases empty.
std::string GetKey() const
Return string unique for each instance.
wxString GetPortDirectionValueStr() const
Return translated name for direction, like _("Input").
NavAddr::Bus GetCommProtocol() const
Return NavAddr::Bus corresponding to network address/port or serial parameters.
void ApplySettings()
Make dialog's settings the active ones, usually by updating globals.
void OnResize(const wxSize &size)
Resize the connections tab.
ConnectionsDlg(wxWindow *parent, const std::vector< ConnectionParams * > &connections)
Create a new ConnectionsDlg instance.
void CancelSettings()
Restore dialog settings from currently used values, usually globals.
std::sort support: Compare two ConnectionParams w r t state.
Grid with existing connections: type, port, status, etc.
static void OnWheel(const wxMouseEvent &ev)
Mouse wheel: scroll the TopScroll window.
void ReloadGrid(const std::vector< ConnectionParams * > &connections)
Reload grid using data from given list of connections.
int FindConnectionIndex(const ConnectionParams *cp) const
Find index in m_connections for given pointer.
Indeed: the General panel.
Clickable, small info icon which displays a help text Typical usage:
Definition gui_lib.h:146
Custom event class for OpenCPN's notification system.
The "Show advanced" text + right/down triangle and handler.
Top panel: connections grid, "Add new connection", general options.
Top scroll window, adds scrollbars to TopPanel.
Generic event handling between for example MVC Model and Controller based on a shared EventVar variab...
Definition evtvar.h:78
void Notify() override
Notify all listeners, no data supplied.
Definition evtvar.h:83
Global color handling by name.
void MakeCommDriver(const ConnectionParams *params)
Create and register a driver for given connection.
Communication drivers factory and support.
Misc driver utilities.
bool g_always_send_rmb_rmc
Always send RMB and RMC n0183 messages even if there is no active route.
Global variables stored in configuration file.
std::vector< ConnectionParams * > & TheConnectionParams()
Return global list of connections.
Connection parameters.
Panel for editing a connection.
Runtime connection/driver state definitions.
Dialog and support code for editing a connection.
Options | Connections GUI tab managing connections
Platform independent GL includes.
Misc GUI event vars, a singleton.
General purpose GUI support.
Utility functions.
User notifications manager.
OpenCPN Platform specific support utilities.
options * g_options
Global instance.
Definition options.cpp:181
Options dialog.
Input priorities management dialog.
Driver statistics report.
SVG utilities.