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 wxString UtfArrowDown() { return wxString::FromUTF8(u8"\u25bc"); }
67static wxString UtfArrowRight() { return wxString::FromUTF8(u8"\u25ba"); }
68static wxString UtfFilledCircle() { return wxString::FromUTF8(u8"\u2b24"); }
69
70static const auto TopScrollWindowName = "TopScroll";
71
72static const char* kInfoHeader = _("OpenCPN help").c_str();
73static const char* kInfo = _(R"---(
74Normally OpenCPN sends the RMB (distance and heading to waypoint
75etc) and RMC (position, heading etc.) sentences when there is an
76active waypoint. The talker ID is by default EC.
77
78In certain cases these messages (ECRMB and ECRMC) are required
79also when there is no active waypoint. This option enables this.
80
81Obviously, this option carries risks for vessels which have an
82autopilot which is not engaged manually -- the "fake" RMB messages
83could possibly initiate the autopilot in dangerous ways.
84
85Devices needing this includes the NASA Clipper GPS Repeater. In this
86case the output must also be filtered so that ECRMB and ECRMC are
87the only transmitted messages.
88)---")
89 .c_str();
90
91static bool IsWindows() {
92 return wxPlatformInfo::Get().GetOperatingSystemId() & wxOS_WINDOWS;
93}
94
95static bool IsAndroid() {
96#ifdef ANDROID
97 return true;
98#else
99 return false;
100#endif
101}
102
103static std::string BitsToDottedMask(const unsigned bits) {
104 const uint32_t mask = 0xffffffff << (32 - bits);
105 std::stringstream ss;
106 ss << ((mask & 0xff000000) >> 24) << ".";
107 ss << ((mask & 0x00ff0000) >> 16) << ".";
108 ss << ((mask & 0x0000ff00) >> 8) << ".";
109 ss << (mask & 0x000000ff);
110 return ss.str();
111}
112
114class StdIcons {
115private:
116 const unsigned m_size;
117 const fs::path m_svg_dir;
118 ColorScheme m_cs;
119
121 static unsigned GetSize(const wxWindow* parent) {
122 double size = parent->GetCharHeight() * (IsWindows() ? 1.3 : 1.0);
123#if wxCHECK_VERSION(3, 1, 2)
124 // Apply scale factor, mostly for Windows. Other platforms
125 // does this in the toolkits, ToDIP() is aware of this.
126 size *= static_cast<double>(parent->ToDIP(100)) / 100.;
127#endif
128 // Force minimum physical size for touch screens
129 if (g_btouch) {
130 double pixel_per_mm =
131 wxGetDisplaySize().x / g_Platform->GetDisplaySizeMM();
132 size = std::max(size, 7.0 * pixel_per_mm);
133 }
134 return static_cast<unsigned>(size);
135 }
136
137 [[nodiscard]] wxBitmap LoadIcon(const std::string& filename) const {
138 fs::path path = m_svg_dir / filename;
139 return LoadSVG(path.string(), m_size, m_size);
140 }
141
142 [[nodiscard]] wxBitmap IconApplyColorScheme(const wxBitmap& proto) const {
143 if (!proto.IsOk()) return wxNullBitmap;
144 if ((m_cs != GLOBAL_COLOR_SCHEME_DAY) &&
145 (m_cs != GLOBAL_COLOR_SCHEME_RGB)) {
146 // Assume the bitmap is monochrome, so simply invert the colors.
147 const wxImage image = proto.ConvertToImage();
148 unsigned char* data = image.GetData();
149 unsigned char* p_idata = data;
150 for (int i = 0; i < image.GetSize().y; i++) {
151 for (int j = 0; j < image.GetSize().x; j++) {
152 unsigned char v = *p_idata;
153 v = 255 - v;
154 *p_idata++ = v;
155 v = *p_idata;
156 v = 255 - v;
157 *p_idata++ = v;
158 v = *p_idata;
159 v = 255 - v;
160 *p_idata++ = v;
161 }
162 }
163 return {image};
164 }
165 return proto;
166 }
167
168public:
169 explicit StdIcons(const wxWindow* parent)
170 : m_size(GetSize(parent)),
171 m_svg_dir(fs::path(g_Platform->GetSharedDataDir().ToStdString()) /
172 "uidata" / "MUI_flat"),
173 m_cs(GLOBAL_COLOR_SCHEME_RGB),
174 trash_bin_proto(LoadIcon("trash_bin.svg")),
175 settings_proto(LoadIcon("setting_gear.svg")),
176 filled_circle_proto(LoadIcon("circle-on.svg")),
177 open_circle_proto(LoadIcon("circle-off.svg")),
178 exclaim_mark_proto(LoadIcon("exclaim_mark.svg")),
179 x_mult_proto(LoadIcon("X_mult.svg")),
180 check_mark_proto(LoadIcon("check_mark.svg")) {
181 trash_bin = trash_bin_proto;
182 settings = settings_proto;
183 filled_circle = filled_circle_proto;
184 open_circle = open_circle_proto;
185 exclaim_mark = exclaim_mark_proto;
186 x_mult = x_mult_proto;
187 check_mark = check_mark_proto;
188 }
189
190 void SetColorScheme(const ColorScheme cs) {
191 if (m_cs != cs) {
192 m_cs = cs;
193 trash_bin = IconApplyColorScheme(trash_bin_proto);
194 settings = IconApplyColorScheme(settings_proto);
195 filled_circle = IconApplyColorScheme(filled_circle_proto);
196 open_circle = IconApplyColorScheme(open_circle_proto);
197 exclaim_mark = IconApplyColorScheme(exclaim_mark_proto);
198 x_mult = IconApplyColorScheme(x_mult_proto);
199 check_mark = IconApplyColorScheme(check_mark_proto);
200 }
201 }
202
203 const wxBitmap trash_bin_proto;
204 const wxBitmap settings_proto;
205 const wxBitmap filled_circle_proto;
206 const wxBitmap open_circle_proto;
207 const wxBitmap exclaim_mark_proto;
208 const wxBitmap x_mult_proto;
209 const wxBitmap check_mark_proto;
210
211 wxBitmap trash_bin;
212 wxBitmap settings;
213 wxBitmap filled_circle;
214 wxBitmap open_circle;
215 wxBitmap exclaim_mark;
216 wxBitmap x_mult;
217 wxBitmap check_mark;
218};
219
221class BitmapCellRenderer : public wxGridCellRenderer {
222public:
223 BitmapCellRenderer(const wxBitmap& bitmap, ColorScheme cs)
224 : status(ConnState::Disabled), m_bitmap(bitmap), m_cs(cs) {}
225
226 // Update the bitmap dynamically
227 void SetBitmap(const wxBitmap& bitmap) { m_bitmap = bitmap; }
228
229 void Draw(wxGrid& grid, wxGridCellAttr& attr, wxDC& dc, const wxRect& rect,
230 int row, int col, bool isSelected) override {
231 dc.SetBrush(wxBrush(GetGlobalColor("DILG2")));
232 if ((m_cs != GLOBAL_COLOR_SCHEME_DAY) && m_cs != GLOBAL_COLOR_SCHEME_RGB)
233 dc.SetBrush(wxBrush(GetDialogColor(DLG_BACKGROUND)));
234 if (IsWindows()) dc.SetBrush(wxBrush(GetGlobalColor("DILG1")));
235 dc.DrawRectangle(rect);
236
237 // Draw the bitmap centered in the cell
238 dc.DrawBitmap(m_bitmap, rect.x + (rect.width - m_bitmap.GetWidth()) / 2,
239 rect.y + (rect.height - m_bitmap.GetHeight()) / 2, true);
240 }
241
242 wxSize GetBestSize(wxGrid& grid, wxGridCellAttr& attr, wxDC& dc, int row,
243 int col) override {
244 // Return the size of the bitmap as the best size for the cell
245 return {m_bitmap.GetWidth(), m_bitmap.GetHeight()};
246 }
247
248 [[nodiscard]] BitmapCellRenderer* Clone() const override {
249 return new BitmapCellRenderer(m_bitmap, m_cs);
250 }
251 ConnState status;
252
253private:
254 wxBitmap m_bitmap;
255 ColorScheme m_cs;
256};
257
262class BitmapEnableCellRenderer : public wxGridCellBoolRenderer {
263public:
264 BitmapEnableCellRenderer(int _size, ColorScheme cs) : size(_size), m_cs(cs) {}
265
266 [[nodiscard]] BitmapEnableCellRenderer* Clone() const override {
267 return new BitmapEnableCellRenderer(size, m_cs);
268 }
269
270 wxSize GetBestSize(wxGrid& grid, wxGridCellAttr& attr, wxDC& dc, int row,
271 int col) override {
272 return {size, size};
273 }
274
275private:
276 int size;
277 ColorScheme m_cs;
278};
279
282public:
283 explicit ConnCompare(int col) : m_col(col) {}
284
285 bool operator()(const ConnectionParams* p1,
286 const ConnectionParams* p2) const {
287 switch (m_col) {
288 case 0:
289 return static_cast<int>(p1->bEnabled) > static_cast<int>(p2->bEnabled);
290 case 1:
291 return p1->GetCommProtocol() < p2->GetCommProtocol();
292 case 2:
293 return p1->GetIOTypeValueStr() < p2->GetIOTypeValueStr();
294 case 3:
295 return p1->GetStrippedDSPort() < p2->GetStrippedDSPort();
296 default:
297 return false;
298 }
299 }
300
301private:
302 const int m_col;
303};
304
309public:
313 virtual ~ApplyCancel() = default;
314
316 virtual void Apply() = 0;
317
321 virtual void Cancel() = 0;
322};
323
325class AddConnectionButton final : public wxButton {
326public:
327 AddConnectionButton(wxWindow* parent, EventVar& evt_add_connection,
328 const std::function<void(ConnectionParams* p,
329 bool editing)>& _start_edit_conn)
330 : wxButton(parent, wxID_ANY, _("Add new connection...")),
331 m_evt_add_connection(evt_add_connection),
332 m_start_edit_conn(_start_edit_conn) {
333 Bind(wxEVT_COMMAND_BUTTON_CLICKED,
334 [&](wxCommandEvent&) { OnAddConnection(); });
335 }
336
337private:
338 void OnAddConnection() const { m_start_edit_conn(nullptr, false); }
339
340 EventVar& m_evt_add_connection;
341 std::function<void(ConnectionParams* p, bool editing)> m_start_edit_conn;
342};
343
345class Connections final : public wxGrid {
346public:
347 Connections(wxWindow* parent,
348 const std::vector<ConnectionParams*>& connections,
349 EventVar& on_conn_update,
350 const std::function<void(ConnectionParams* p, bool editing)>&
351 on_edit_conn)
352 : wxGrid(parent, wxID_ANY),
353 m_connections(connections),
354 m_header_column_widths({0, 0, 0, 0, 0, 0, 0}),
355 m_last_tooltip_cell(100),
356 m_cs(GLOBAL_COLOR_SCHEME_DAY),
357 m_on_conn_delete(on_conn_update),
358 m_icons(parent),
359 m_on_edit_conn(on_edit_conn) {
360 ShowScrollbars(wxSHOW_SB_NEVER, wxSHOW_SB_NEVER);
361 SetTable(new wxGridStringTable(), false);
362 GetTable()->AppendCols(8);
363 HideCol(7);
364 if (IsAndroid()) {
365 SetDefaultRowSize(wxWindow::GetCharHeight() * 2);
366 SetColLabelSize(wxWindow::GetCharHeight() * 2);
367 }
368 static const std::array<wxString, 7> headers = {
369 "", _("Protocol") + " ", _("In/Out"), _("Data port"), _("Status"), "",
370 ""};
371 int ic = 0;
372 for (auto hdr = headers.begin(); hdr != headers.end(); hdr++, ic++) {
373 SetColLabelValue(static_cast<int>(hdr - headers.begin()), *hdr);
374 unsigned col_width = hdr->Length() * wxWindow::GetCharWidth();
375 col_width = wxMax(col_width, 6 * wxWindow::GetCharWidth());
376 m_header_column_widths[ic] = static_cast<int>(col_width);
377 SetColSize(ic, static_cast<int>(col_width));
378 }
379
380 if (IsWindows()) {
381 SetLabelBackgroundColour(GetGlobalColor("DILG1"));
382 SetLabelTextColour(GetGlobalColor("DILG3"));
383 }
384 HideRowLabels();
385 SetColAttributes(parent);
386
387 ReloadGrid(connections);
388 DisableDragColSize();
389 DisableDragRowSize();
390 wxWindow::Show(GetNumberRows() > 0);
391
392 GetGridWindow()->Bind(wxEVT_MOTION, [&](wxMouseEvent& ev) {
393 OnMouseMove(ev);
394 ev.Skip();
395 });
396 GetGridWindow()->Bind(wxEVT_MOUSEWHEEL,
397 [&](const wxMouseEvent& ev) { OnWheel(ev); });
398 // wxGridEvent.GetCol() and GetRow() are not const until wxWidgets 3.2
399 Bind(wxEVT_GRID_LABEL_LEFT_CLICK,
400 [&](wxGridEvent& ev) { HandleSort(ev.GetCol()); });
401 Bind(wxEVT_GRID_CELL_LEFT_CLICK,
402 [&](wxGridEvent& ev) { OnClickCell(ev.GetRow(), ev.GetCol()); });
403 Bind(wxEVT_PAINT, [&](wxPaintEvent& ev) {
404 SetColAttributes(dynamic_cast<const wxWindow*>(ev.GetEventObject()));
405 ev.Skip();
406 });
407 conn_change_lstnr.Init(
408 m_conn_states.evt_conn_status_change,
409 [&](ObservedEvt&) { OnConnectionChange(m_connections); });
410 }
411 void SetColorScheme(const ColorScheme cs) {
412 m_cs = cs;
413 m_icons.SetColorScheme(cs);
414 ReloadGrid(m_connections);
415 }
416
417 wxSize GetEstimatedSize() const {
418 unsigned rs = 0;
419 for (auto s : m_header_column_widths) rs += s;
420 return {static_cast<int>(rs), -1};
421 }
422
424 static void OnWheel(const wxMouseEvent& ev) {
425 auto w =
426 dynamic_cast<wxScrolledWindow*>(FindWindowByName(TopScrollWindowName));
427 assert(w && "No TopScroll window found");
428 int xpos;
429 int ypos;
430 w->GetViewStart(&xpos, &ypos);
431 int x;
432 int y;
433 w->GetScrollPixelsPerUnit(&x, &y);
434 // Not sure where the factor "4" comes from...
435 const int dir = ev.GetWheelRotation();
436 w->Scroll(-1, ypos - dir / y / 4);
437 }
438
440 void ReloadGrid(const std::vector<ConnectionParams*>& connections) {
441 ClearGrid();
442 m_renderer_status_vector.clear();
443
444 for (auto it = connections.begin(); it != connections.end(); ++it) {
445 const auto row = static_cast<int>(it - connections.begin());
446 EnsureRows(row);
447 SetCellValue(row, 0, (*it)->bEnabled ? "1" : "");
448 if ((*it)->bEnabled)
449 m_tooltips[row][0] = _("Enabled, click to disable");
450 else
451 m_tooltips[row][0] = _("Disabled, click to enable");
452 std::string protocol = NavAddr::BusToString((*it)->GetCommProtocol());
453 SetCellValue(row, 1, protocol);
454 SetCellValue(row, 2, (*it)->GetIOTypeValueStr());
455 SetCellValue(row, 3, (*it)->GetStrippedDSPort());
456 m_tooltips[row][3] = (*it)->UserComment;
457 SetCellRenderer(row, 5, new BitmapCellRenderer(m_icons.settings, m_cs));
458 m_tooltips[row][5] = _("Edit connection");
459 SetCellRenderer(row, 6, new BitmapCellRenderer(m_icons.trash_bin, m_cs));
460 m_tooltips[row][6] = _("Delete connection");
461 SetCellValue(row, 7, (*it)->GetKey());
462
463 auto stat_renderer = new BitmapCellRenderer(m_icons.filled_circle, m_cs);
464 stat_renderer->status = ConnState::Disabled;
465 m_renderer_status_vector.push_back(stat_renderer);
466 SetCellRenderer(row, 4, stat_renderer);
467 if (IsAndroid()) {
468 wxString sp(protocol);
469 unsigned size = sp.Length() * wxWindow::GetCharWidth();
470 m_header_column_widths[1] = std::max(m_header_column_widths[1], size);
471 size = (*it)->GetIOTypeValueStr().Length() * wxWindow::GetCharWidth();
472 m_header_column_widths[2] = std::max(m_header_column_widths[2], size);
473 sp = wxString((*it)->GetStrippedDSPort());
474 size = sp.Length() * wxWindow::GetCharWidth();
475 m_header_column_widths[3] = std::max(m_header_column_widths[3], size);
476 }
477 }
478 OnConnectionChange(m_connections);
479
480 if (IsAndroid()) {
481 int ic = 0;
482 for (auto val : m_header_column_widths) {
483 SetColSize(ic, static_cast<int>(val));
484 ic++;
485 }
486 } else
487 AutoSize();
488 }
489
492 public:
493 explicit ConnStateCompare(Connections* connections)
494 : m_conns(connections) {}
495 bool operator()(const ConnectionParams* p1,
496 const ConnectionParams* p2) const {
497 int row1 = m_conns->FindConnectionIndex(p1);
498 int row2 = m_conns->FindConnectionIndex(p2);
499 if (row1 == -1 && row2 == -1) return false;
500 if (row1 == -1) return false;
501 if (row2 == -1) return true;
502 const int v1 = m_conns->GetCellValue(row1, 4)[0];
503 const int v2 = m_conns->GetCellValue(row2, 4)[0];
504 return v1 < v2;
505 }
506 Connections* m_conns;
507 };
508
514 using namespace std;
515 auto key = cp->GetKey();
516 auto found = find_if(
517 m_connections.begin(), m_connections.end(),
518 [key](const ConnectionParams* cp) { return cp->GetKey() == key; });
519 if (found == m_connections.end()) return -1;
520 return static_cast<int>(found - m_connections.begin());
521 }
522
523private:
528 ConnectionParams* FindRowConnection(const int row) const {
529 auto found = find_if(m_connections.begin(), m_connections.end(),
530 [&](const ConnectionParams* p) {
531 return GetCellValue(row, 7) == p->GetKey();
532 });
533 return found != m_connections.end() ? *found : nullptr;
534 }
535
540 void EnsureRows(size_t rows) {
541 for (unsigned i = m_tooltips.size(); i <= rows; i++)
542 m_tooltips.emplace_back(std::vector<std::string>(7));
543 for (unsigned i = GetNumberRows(); i <= rows; i++) AppendRows(1, false);
544 }
545
547 void SetColAttributes(const wxWindow* parent) {
548 if (IsWindows()) {
549 // Set all cells to global color scheme
550 SetDefaultCellBackgroundColour(GetGlobalColor("DILG1"));
551 }
552 auto enable_attr = new wxGridCellAttr();
553 enable_attr->SetAlignment(wxALIGN_CENTRE, wxALIGN_CENTRE);
554
555 if (IsAndroid())
556 enable_attr->SetRenderer(
557 new BitmapEnableCellRenderer(wxWindow::GetCharWidth() * 3 / 2, m_cs));
558 else
559 enable_attr->SetRenderer(new wxGridCellBoolRenderer());
560
561 enable_attr->SetEditor(new wxGridCellBoolEditor());
562 if (IsWindows()) enable_attr->SetBackgroundColour(GetGlobalColor("DILG1"));
563 SetColAttr(0, enable_attr);
564
565 auto protocol_attr = new wxGridCellAttr();
566 protocol_attr->SetAlignment(wxALIGN_LEFT, wxALIGN_CENTRE);
567 protocol_attr->SetReadOnly(true);
568 if (IsWindows())
569 protocol_attr->SetBackgroundColour(GetGlobalColor("DILG1"));
570 SetColAttr(1, protocol_attr);
571
572 auto in_out_attr = new wxGridCellAttr();
573 in_out_attr->SetAlignment(wxALIGN_CENTRE, wxALIGN_CENTRE);
574 in_out_attr->SetReadOnly(true);
575 if (IsWindows()) in_out_attr->SetBackgroundColour(GetGlobalColor("DILG1"));
576 SetColAttr(2, in_out_attr);
577
578 auto port_attr = new wxGridCellAttr();
579 port_attr->SetAlignment(wxALIGN_LEFT, wxALIGN_CENTRE);
580 port_attr->SetReadOnly(true);
581 if (IsWindows()) port_attr->SetBackgroundColour(GetGlobalColor("DILG1"));
582 SetColAttr(3, port_attr);
583
584 auto status_attr = new wxGridCellAttr();
585 status_attr->SetAlignment(wxALIGN_CENTRE, wxALIGN_CENTRE);
586 status_attr->SetReadOnly(true);
587 status_attr->SetFont(parent->GetFont().Scale(1.3));
588 if (IsWindows()) status_attr->SetBackgroundColour(GetGlobalColor("DILG1"));
589 SetColAttr(4, status_attr);
590
591 auto edit_attr = new wxGridCellAttr();
592 edit_attr->SetAlignment(wxALIGN_CENTRE, wxALIGN_CENTRE);
593 edit_attr->SetFont(parent->GetFont().Scale(1.3));
594 edit_attr->SetReadOnly(true);
595 if (IsWindows()) edit_attr->SetBackgroundColour(GetGlobalColor("DILG1"));
596 SetColAttr(5, edit_attr);
597
598 auto delete_attr = new wxGridCellAttr();
599 delete_attr->SetAlignment(wxALIGN_LEFT, wxALIGN_CENTRE);
600 delete_attr->SetFont(parent->GetFont().Scale(1.3));
601 delete_attr->SetTextColour(*wxRED);
602 delete_attr->SetReadOnly(true);
603 if (IsWindows()) delete_attr->SetBackgroundColour(GetGlobalColor("DILG1"));
604 SetColAttr(6, delete_attr);
605 }
606
608 void OnClickCell(int row, int col) {
609 if (col == 0)
610 HandleEnable(row);
611 else if (col == 5) {
612 HandleEdit(row);
613 } else if (col == 6) {
614 HandleDelete(row);
615 }
616 }
617
619 void OnMouseMove(const wxMouseEvent& ev) {
620 const wxPoint pt = ev.GetPosition();
621 int row = YToRow(pt.y);
622 int col = XToCol(pt.x);
623 if (col < 0 || col >= 7 || row < 0 || row >= GetNumberRows()) return;
624 if (row * 7 + col == m_last_tooltip_cell) return;
625 m_last_tooltip_cell = row * 7 + col;
626 GetGridWindow()->SetToolTip(m_tooltips[row][col]);
627 }
628
630 void OnConnectionChange(const std::vector<ConnectionParams*>& connections) {
631 bool refresh_needed = false;
632 for (auto it = connections.begin(); it != connections.end(); ++it) {
633 ConnState state = m_conn_states.GetDriverState(
634 (*it)->GetCommProtocol(), (*it)->GetStrippedDSPort());
635 if (!(*it)->bEnabled) state = ConnState::Disabled;
636 auto row = static_cast<int>(it - connections.begin());
637 EnsureRows(row);
638 if (static_cast<int>(m_renderer_status_vector.size()) < row + 1) continue;
639 switch (state) {
640 case ConnState::Disabled:
641 if (m_renderer_status_vector[row]->status != ConnState::Disabled) {
642 m_renderer_status_vector[row]->SetBitmap(m_icons.filled_circle);
643 m_renderer_status_vector[row]->status = ConnState::Disabled;
644 refresh_needed = true;
645 }
646 m_tooltips[row][4] = _("Disabled");
647 break;
648 case ConnState::NoStats:
649 if (m_renderer_status_vector[row]->status != ConnState::NoStats) {
650 m_renderer_status_vector[row]->SetBitmap(m_icons.open_circle);
651 m_renderer_status_vector[row]->status = ConnState::NoStats;
652 refresh_needed = true;
653 }
654 m_tooltips[row][4] = _("No driver statistics available");
655 break;
656 case ConnState::NoData:
657 if (m_renderer_status_vector[row]->status != ConnState::NoData) {
658 m_renderer_status_vector[row]->SetBitmap(m_icons.exclaim_mark);
659 m_renderer_status_vector[row]->status = ConnState::NoData;
660 refresh_needed = true;
661 }
662 m_tooltips[row][4] = _("No data flowing through connection");
663 break;
664 case ConnState::Unavailable:
665 if (m_renderer_status_vector[row]->status != ConnState::Unavailable) {
666 m_renderer_status_vector[row]->SetBitmap(m_icons.x_mult);
667 m_renderer_status_vector[row]->status = ConnState::Unavailable;
668 refresh_needed = true;
669 }
670 m_tooltips[row][4] = _("The device is unavailable");
671 break;
672 case ConnState::Ok:
673 if (m_renderer_status_vector[row]->status != ConnState::Ok) {
674 m_renderer_status_vector[row]->SetBitmap(m_icons.check_mark);
675 m_renderer_status_vector[row]->status = ConnState::Ok;
676 refresh_needed = true;
677 }
678 m_tooltips[row][4] = _("Data is flowing");
679 break;
680 }
681 }
682 if (refresh_needed) ForceRefresh();
683 }
684
686 void SetSortingColumn(int col) {
687 if (GetSortingColumn() != wxNOT_FOUND) {
688 int old_col = GetSortingColumn();
689 auto label = GetColLabelValue(old_col);
690 if (label[0] == UtfArrowDown()[0])
691 SetColLabelValue(old_col, label.substr(2));
692 }
693 auto label = GetColLabelValue(col);
694 if (label[0] != UtfArrowDown()[0])
695 SetColLabelValue(col, UtfArrowDown() + " " + label);
696 wxGrid::SetSortingColumn(col);
697 Fit();
698 }
699
701 void HandleSort(int col) {
702 if (col > 4) return;
703 auto& params = TheConnectionParams();
704 if (col < 4)
705 std::sort(params.begin(), params.end(), ConnCompare(col));
706 else // col == 4
707 std::sort(params.begin(), params.end(), ConnStateCompare(this));
708 ReloadGrid(TheConnectionParams());
709 SetSortingColumn(col);
710 }
711
713 void HandleEnable(int row) {
714 ConnectionParams* cp = FindRowConnection(row);
715 if (!cp) return;
716 cp->bEnabled = !cp->bEnabled;
717 cp->b_IsSetup = FALSE; // trigger a rebuild/takedown of the connection
718 SetCellValue(row, 0, cp->bEnabled ? "1" : "");
719 if (cp->bEnabled)
720 m_tooltips[row][0] = _("Enabled, click to disable");
721 else
722 m_tooltips[row][0] = _("Disabled, click to enable");
723 DriverStats stats;
724 stats.driver_iface = cp->GetStrippedDSPort();
725 stats.driver_bus = cp->GetCommProtocol();
726 m_conn_states.HandleDriverStats(stats);
727 StopAndRemoveCommDriver(cp->GetStrippedDSPort(), cp->GetCommProtocol());
728 if (cp->bEnabled) MakeCommDriver(cp);
729 cp->b_IsSetup = true;
730 if (!cp->bEnabled) {
731 SetCellValue(row, 4, UtfFilledCircle());
732 // ForceRefresh() apparently broken, see #4648
733 ReloadGrid(TheConnectionParams());
734 }
735 }
736
738 void HandleEdit(int row) {
739 if (ConnectionParams* cp = FindRowConnection(row)) {
740 Show(GetNumberRows() > 0);
741 m_on_edit_conn(cp, true);
742 }
743 }
744
746 void HandleDelete(int row) {
747 ConnectionParams* cp = FindRowConnection(row);
748 auto found = std::find(m_connections.begin(), m_connections.end(), cp);
749 if (found != m_connections.end()) {
750 std::stringstream ss;
751 ss << _("Ok to delete connection on ") << (*found)->GetStrippedDSPort();
752 int rcode = OCPNMessageBox(this, ss.str(), _("Delete connection?"),
753 wxOK | wxCANCEL);
754 if (rcode != wxID_OK && rcode != wxID_YES) return;
755 delete (*found)->m_optionsPanel;
756 StopAndRemoveCommDriver((*found)->GetStrippedDSPort(),
757 (*found)->GetCommProtocol());
758 TheConnectionParams().erase(found);
759 if (GetNumberRows() > static_cast<int>(m_connections.size()))
760 DeleteRows(GetNumberRows() - 1);
761 m_on_conn_delete.Notify();
762 }
763 }
764
765 ObsListener conn_change_lstnr;
766 std::vector<std::vector<std::string>> m_tooltips;
767 ConnStates m_conn_states;
768 const std::vector<ConnectionParams*>& m_connections;
769 std::array<unsigned, 7> m_header_column_widths;
770 int m_last_tooltip_cell;
771 ColorScheme m_cs;
772 EventVar& m_on_conn_delete;
773 StdIcons m_icons;
774 std::vector<BitmapCellRenderer*> m_renderer_status_vector;
775 std::function<void(ConnectionParams* p, bool editing)> m_on_edit_conn;
776};
777
779class GeneralPanel : public wxPanel {
780public:
781 explicit GeneralPanel(wxWindow* parent, wxSize max_size)
782 : wxPanel(parent, wxID_ANY) {
783 auto sizer = new wxStaticBoxSizer(wxVERTICAL, this, _("General"));
784 SetSizer(sizer);
785 auto flags = wxSizerFlags().Border();
786 m_upload_options = new UploadOptionsChoice(this);
787 sizer->Add(m_upload_options, flags);
788 sizer->Add(new PrioritiesBtn(this), flags);
789 wxWindow::SetMaxSize(max_size);
790 }
791 void SetColorScheme(ColorScheme) {
792 DimeControl(m_upload_options);
793 Refresh();
794 }
795
796private:
798 class PrioritiesBtn final : public wxButton {
799 public:
800 explicit PrioritiesBtn(wxWindow* parent)
801 : wxButton(parent, wxID_ANY, _("Adjust Nav data priorities...")) {
802 Bind(wxEVT_COMMAND_BUTTON_CLICKED, [&](wxCommandEvent&) {
803 PriorityDlg dialog(this->GetParent());
804 dialog.ShowModal();
805 });
806 }
807 };
808
810 class UploadOptionsChoice final : public wxRadioBox, public ApplyCancel {
811 public:
812 explicit UploadOptionsChoice(wxWindow* parent) : wxRadioBox() {
813 wxArrayString wx_choices;
814 for (auto& c : choices) wx_choices.Add(c);
815 Create(parent, wxID_ANY, _("Upload Format"), wxDefaultPosition,
816 wxDefaultSize, wx_choices, 0, wxRA_SPECIFY_ROWS);
817 DimeControl(this);
818 UploadOptionsChoice::Cancel();
819 }
820
821 void Cancel() override {
822 if (g_bGarminHostUpload)
823 SetSelection(1);
824 else if (g_GPS_Ident == "FurunoGP3X")
825 SetSelection(2);
826 else
827 SetSelection(0);
828 }
829
830 void Apply() override {
831 switch (GetSelection()) {
832 case 0:
833 g_bGarminHostUpload = false;
834 g_GPS_Ident = "Generic";
835 break;
836 case 1:
837 g_bGarminHostUpload = true;
838 g_GPS_Ident = "Generic";
839 break;
840 case 2:
841 g_bGarminHostUpload = false;
842 g_GPS_Ident = "FurunoGP3X";
843 break;
844 default:
845 assert(false && "Invalid upload case option");
846 }
847 }
848
849 const std::array<wxString, 3> choices = {
850 _("Generic NMEA 0183"), _("Garmin Host mode"), _("Furuno GP4X")};
851 };
852
853 UploadOptionsChoice* m_upload_options;
854};
855
857class ShowAdvanced : public wxStaticText {
858public:
859 ShowAdvanced(wxWindow* parent, std::function<void(bool)> on_toggle)
860 : wxStaticText(parent, wxID_ANY, ""),
861 m_on_toggle(std::move(on_toggle)),
862 m_show_advanced(true) {
863 Toggle();
864 Bind(wxEVT_LEFT_DOWN, [&](const wxMouseEvent& ev) { Toggle(); });
865 }
866
867private:
868 wxString m_label = _("Advanced ");
869 std::function<void(bool)> m_on_toggle;
870 bool m_show_advanced;
871
872 void Toggle() {
873 m_show_advanced = !m_show_advanced;
874 m_label = _("Advanced ");
875 m_label += m_show_advanced ? UtfArrowDown() : UtfArrowRight();
876 SetLabelText(m_label);
877 m_on_toggle(m_show_advanced);
878 }
879};
880
882class AdvancedPanel : public wxPanel {
883public:
884 explicit AdvancedPanel(wxWindow* parent, wxSize max_size)
885 : wxPanel(parent, wxID_ANY) {
886 auto sizer = new wxStaticBoxSizer(wxVERTICAL, this, "");
887 sizer->Add(new BearingsCheckbox(this), wxSizerFlags().Expand());
888 sizer->Add(new ExtraRmbRmcLine(this), wxSizerFlags().Expand());
889 sizer->Add(new NmeaFilterRow(this), wxSizerFlags().Expand());
890 sizer->Add(new TalkerIdRow(this), wxSizerFlags().Expand());
891 sizer->Add(new NetmaskRow(this), wxSizerFlags().Expand());
892 SetSizer(sizer);
893 wxWindow::SetMaxSize(max_size);
894 }
895
896private:
898 class BearingsCheckbox : public wxCheckBox, public ApplyCancel {
899 public:
900 explicit BearingsCheckbox(wxWindow* parent)
901 : wxCheckBox(parent, wxID_ANY,
902 _("Use magnetic bearing in output sentence APB")) {
903 wxCheckBox::SetValue(g_bMagneticAPB);
904 }
905
906 void Apply() override { g_bMagneticAPB = GetValue(); }
907 void Cancel() override { SetValue(g_bMagneticAPB); }
908 };
909
910 class ExtraRmbRmcLine : public wxPanel {
911 class RmbRmcCheckbox : public wxCheckBox, public ApplyCancel {
912 public:
913 explicit RmbRmcCheckbox(wxWindow* parent)
914 : wxCheckBox(parent, wxID_ANY,
915 _("Always send RMB and RMC NMEA0183 data")) {
916 wxCheckBox::SetValue(g_always_send_rmb_rmc);
917 }
918
919 void Apply() override { g_always_send_rmb_rmc = GetValue(); }
920 void Cancel() override { SetValue(g_always_send_rmb_rmc); }
921 };
922
923 public:
924 explicit ExtraRmbRmcLine(wxWindow* parent) : wxPanel(parent, wxID_ANY) {
925 auto hbox = new wxBoxSizer(wxHORIZONTAL);
926 hbox->Add(new RmbRmcCheckbox(this), wxSizerFlags().Expand());
927 hbox->Add(1, 1, 1, wxEXPAND);
928 hbox->Add(new InfoButton(this, g_btouch, kInfoHeader, kInfo),
929 wxSizerFlags().Border());
930 SetSizer(hbox);
931 wxWindow::Layout();
932 wxWindow::Show();
933 }
934 };
935 class ExtraRmbRmcCheckbox final : public wxCheckBox, public ApplyCancel {
936 public:
937 explicit ExtraRmbRmcCheckbox(wxWindow* parent)
938 : wxCheckBox(parent, wxID_ANY,
939 _("Always send RMB and RMC NMEA0183 data")) {
940 wxCheckBox::SetValue(g_always_send_rmb_rmc);
941 }
942
943 void Apply() override { g_always_send_rmb_rmc = GetValue(); }
944 void Cancel() override { SetValue(g_always_send_rmb_rmc); }
945 };
946
948 class NmeaFilterRow : public wxPanel, public ApplyCancel {
949 wxCheckBox* checkbox;
950 wxTextCtrl* filter_period;
951
952 public:
953 explicit NmeaFilterRow(wxWindow* parent) : wxPanel(parent) {
954 auto hbox = new wxBoxSizer(wxHORIZONTAL);
955 checkbox = new wxCheckBox(
956 this, wxID_ANY,
957 _("Filter NMEA course and speed data. Filter period: "));
958 checkbox->SetValue(g_bfilter_cogsog);
959 hbox->Add(checkbox, wxSizerFlags().Align(wxALIGN_CENTRE));
960 filter_period =
961 new wxTextCtrl(this, wxID_ANY, "", wxDefaultPosition,
962 wxSize(50, 3 * wxWindow::GetCharWidth()), 0);
963 filter_period->SetValue(std::to_string(g_COGFilterSec));
964 hbox->Add(filter_period, wxSizerFlags().Border());
965 SetSizer(hbox);
966 NmeaFilterRow::Cancel();
967 }
968
969 void Apply() override {
970 std::stringstream ss;
971 ss << filter_period->GetValue();
972 ss >> g_COGFilterSec;
973 g_bfilter_cogsog = checkbox->GetValue();
974 }
975
976 void Cancel() override {
977 std::stringstream ss;
978 ss << g_COGFilterSec;
979 filter_period->SetValue(ss.str());
980 checkbox->SetValue(g_bfilter_cogsog);
981 }
982 };
983
985 class TalkerIdRow : public wxPanel, public ApplyCancel {
986 wxTextCtrl* text_ctrl;
987
988 public:
989 explicit TalkerIdRow(wxWindow* parent) : wxPanel(parent) {
990 auto hbox = new wxBoxSizer(wxHORIZONTAL);
991 hbox->Add(new wxStaticText(this, wxID_ANY, _("NMEA 0183 Talker Id: ")),
992 wxSizerFlags().Align(wxALIGN_CENTRE_VERTICAL).Border());
993 text_ctrl = new wxTextCtrl(
994 this, wxID_ANY, "", wxDefaultPosition,
995 wxSize(4 * wxWindow::GetCharWidth(), 3 * wxWindow::GetCharWidth()));
996 text_ctrl->SetToolTip(
997 _("Enter a two-letter talker ID to override the default ID in NMEA "
998 "sentences generated by OpenCPN (e.g., GP, HC, EC). This affects "
999 "only sentences created by OpenCPN, not those forwarded from other "
1000 "devices."));
1001 text_ctrl->SetValue(g_TalkerIdText);
1002 hbox->Add(text_ctrl, wxSizerFlags().Border());
1003 SetSizer(hbox);
1004 TalkerIdRow::Cancel();
1005 }
1006
1007 void Apply() override { g_TalkerIdText = text_ctrl->GetValue(); }
1008 void Cancel() override { text_ctrl->SetValue(g_TalkerIdText); }
1009 };
1010
1012 class NetmaskRow : public wxPanel, public ApplyCancel {
1013 public:
1014 explicit NetmaskRow(wxWindow* parent)
1015 : wxPanel(parent),
1016 m_spin_ctrl(new wxSpinCtrl(this, wxID_ANY)),
1017 m_text(new wxStaticText(this, wxID_ANY, "")) {
1018 m_spin_ctrl->SetRange(8, 32);
1019 auto hbox = new wxBoxSizer(wxHORIZONTAL);
1020 auto flags = wxSizerFlags().Align(wxALIGN_CENTRE_VERTICAL).Border();
1021 hbox->Add(new wxStaticText(this, wxID_ANY, _("Netmask: ")), flags);
1022 hbox->Add(m_text, flags);
1023 hbox->Add(new wxStaticText(this, wxID_ANY, _("length (bits): ")), flags);
1024 hbox->Add(m_spin_ctrl, flags);
1025 SetSizer(hbox);
1026 NetmaskRow::Cancel();
1027
1028 Bind(wxEVT_SPINCTRL, [&](wxSpinEvent&) {
1029 m_text->SetLabel(BitsToDottedMask(m_spin_ctrl->GetValue()));
1030 Layout();
1031 });
1032 }
1033
1034 void Apply() override { g_netmask_bits = m_spin_ctrl->GetValue(); }
1035
1036 void Cancel() override {
1037 m_spin_ctrl->SetValue(g_netmask_bits);
1038 m_text->SetLabel(BitsToDottedMask(m_spin_ctrl->GetValue()));
1039 Layout();
1040 }
1041
1042 private:
1043 wxSpinCtrl* m_spin_ctrl;
1044 wxStaticText* m_text;
1045 };
1046};
1047
1049class TopPanel : public wxPanel {
1050public:
1051 TopPanel(wxWindow* parent, const std::vector<ConnectionParams*>& connections,
1052 EventVar& evt_add_connection,
1053 const std::function<void(ConnectionParams* p, bool editing)>&
1054 on_edit_conn)
1055 : wxPanel(parent, wxID_ANY),
1056 m_evt_add_connection(evt_add_connection),
1057 m_connections(connections) {
1058 auto vbox = new wxBoxSizer(wxVERTICAL);
1059 auto conn_grid = new Connections(this, m_connections, m_evt_add_connection,
1060 on_edit_conn);
1061 wxSize panel_max_size(conn_grid->GetEstimatedSize());
1062 vbox->AddSpacer(wxWindow::GetCharHeight());
1063 auto conn_flags = wxSizerFlags().Border();
1064 if (IsAndroid()) conn_flags = wxSizerFlags().Border().Expand();
1065 vbox->Add(conn_grid, conn_flags);
1066 vbox->Add(new AddConnectionButton(this, m_evt_add_connection, on_edit_conn),
1067 wxSizerFlags().Border());
1068 vbox->Add(0, wxWindow::GetCharHeight()); // Expanding spacer
1069 auto panel_flags =
1070 wxSizerFlags().Border(wxLEFT | wxDOWN | wxRIGHT).Expand();
1071 m_general_panel = new GeneralPanel(this, panel_max_size);
1072 vbox->Add(m_general_panel, panel_flags);
1073
1074 auto advanced_panel = new AdvancedPanel(this, panel_max_size);
1075 m_advanced_panel = advanced_panel;
1076 auto on_toggle = [&, advanced_panel, vbox](bool show) {
1077 // FIXME advanced_panel->SetMaxSize({conn_grid->GetSize().x, -1});
1078 advanced_panel->Show(show);
1079 vbox->SetSizeHints(this);
1080 vbox->Fit(this);
1081 };
1082 vbox->Add(new ShowAdvanced(this, on_toggle), panel_flags);
1083 vbox->Add(advanced_panel, panel_flags.ReserveSpaceEvenIfHidden());
1084
1085 SetSizer(vbox);
1086 vbox->SetSizeHints(this);
1087 vbox->Fit(this);
1088 wxWindow::Fit();
1089 wxWindow::Show();
1090
1091 auto on_evt_update_connections = [&, conn_grid](ObservedEvt&) {
1092 conn_grid->ReloadGrid(TheConnectionParams());
1093 conn_grid->Show(conn_grid->GetNumberRows() > 0);
1094 Layout();
1095 };
1096 m_add_connection_lstnr.Init(m_evt_add_connection,
1097 on_evt_update_connections);
1098 m_conn_grid = conn_grid;
1099 }
1100
1101 void SetColorScheme(const ColorScheme cs) const {
1102 m_conn_grid->SetColorScheme(cs);
1103 m_general_panel->SetColorScheme(cs);
1104 }
1105 [[nodiscard]] Connections* GetConnectionsGrid() const { return m_conn_grid; }
1106
1107 EventVar& m_evt_add_connection;
1108
1109private:
1110 const std::vector<ConnectionParams*>& m_connections;
1111 ObsListener m_add_connection_lstnr;
1112 Connections* m_conn_grid;
1113 GeneralPanel* m_general_panel;
1114 AdvancedPanel* m_advanced_panel;
1115};
1116
1118class TopScroll : public wxScrolledWindow {
1119public:
1120 TopScroll(wxWindow* parent, const std::vector<ConnectionParams*>& connections,
1121 EventVar& evt_add_connection)
1122 : wxScrolledWindow(parent, wxID_ANY, wxDefaultPosition, wxDefaultSize,
1123 wxVSCROLL | wxHSCROLL, TopScrollWindowName) {
1124 ShowScrollbars(wxSHOW_SB_NEVER, wxSHOW_SB_ALWAYS);
1125 auto vbox = new wxBoxSizer(wxVERTICAL);
1126 SetSizer(vbox);
1127
1128 auto on_edit_connection = [&](ConnectionParams* p, bool editing) {
1129 if (editing)
1130 HandleEdit(p);
1131 else
1132 HandleNew(p);
1133 };
1134
1135 top_panel =
1136 new TopPanel(this, connections, evt_add_connection, on_edit_connection);
1137 vbox->Add(top_panel, wxSizerFlags(1).Expand());
1138
1139 auto on_edit_click = [&](const ConnectionParams* p, bool new_mode,
1140 bool ok_cancel) {
1141 HandleEditFinish(p, new_mode, ok_cancel);
1142 };
1143
1144 m_edit_panel = new ConnectionEditDialog(this, on_edit_click);
1145 m_edit_panel->SetPropsLabel(_("Edit Selected Connection"));
1146 wxWindow* options = FindWindowByName("Options");
1147 assert(options && "Null Options window!");
1148 int fraction = 9;
1149#ifdef ANDROID
1150 fraction = 10;
1151#endif
1152 m_edit_panel->SetSize(
1153 wxSize(options->GetSize().x, options->GetSize().y * fraction / 10));
1154 vbox->Add(m_edit_panel, wxSizerFlags(0).Expand());
1155 m_edit_panel->Hide();
1156
1157 SetScrollRate(0, 10);
1158 if (IsAndroid()) SetScrollRate(1, 1);
1159
1160 SetScrollRate(0, 10);
1161 if (IsAndroid()) SetScrollRate(1, 1);
1162 }
1163
1164 void SetColorScheme(ColorScheme cs) const {
1165 if (top_panel) top_panel->SetColorScheme(cs);
1166 }
1167
1168 void HandleEdit(ConnectionParams* p) {
1169 m_edit_panel->SetPropsLabel(_("Edit Selected Connection"));
1170 m_edit_panel->SetNewMode(false);
1171 m_edit_panel->PreloadControls(p);
1172 m_edit_panel->AddOKCancelButtons();
1173 SwitchToEditor();
1174 }
1175
1176 void HandleNew(ConnectionParams*) {
1177 m_edit_panel->SetPropsLabel(_("Configure new connection"));
1178 m_edit_panel->SetDefaultConnectionParams();
1179 m_edit_panel->SetNewMode(true);
1180 m_edit_panel->AddOKCancelButtons();
1181 SwitchToEditor();
1182 }
1183
1184 void SwitchToEditor() {
1185 top_panel->Hide(); // TopPanel
1186 Scroll(0, 0);
1187 DimeControl(m_edit_panel);
1188 m_edit_panel->Show();
1189 g_options->ShowOKButtons(false);
1190
1191 Layout();
1192 }
1193
1194 void SwitchToGrid() {
1195 g_options->ShowOKButtons(true);
1196 m_edit_panel->Hide();
1197 top_panel->Show();
1198 top_panel->GetConnectionsGrid()->ReloadGrid(TheConnectionParams());
1199 Layout();
1200 Scroll(0, 0);
1201 }
1202
1203 void HandleEditFinish(const ConnectionParams* cp_orig, bool new_mode,
1204 bool ok_cancel) {
1205 if (!ok_cancel) {
1206 SwitchToGrid();
1207 return;
1208 }
1209 // OK from EDIT mode
1210 if (!new_mode) {
1211 ConnectionParams* cp_edited = m_edit_panel->GetParamsFromControls();
1212 delete cp_orig->m_optionsPanel;
1213 StopAndRemoveCommDriver(cp_orig->GetStrippedDSPort(),
1214 cp_orig->GetCommProtocol());
1215 int index = top_panel->GetConnectionsGrid()->FindConnectionIndex(cp_orig);
1216 assert(index != -1 && "Cannot look up connection index");
1217 TheConnectionParams()[index] = cp_edited;
1218 cp_edited->b_IsSetup = false; // Trigger new stream
1219 }
1220 // OK from NEW mode
1221 else {
1222 if (ConnectionParams* cp = m_edit_panel->GetParamsFromControls()) {
1223 if (cp->GetValidPort()) {
1224 cp->b_IsSetup = false; // Trigger new stream
1225 TheConnectionParams().push_back(cp);
1226 } else {
1227 wxString msg =
1228 _("Unable to create a connection as configured. "
1229 "Connected port or address was missing.");
1230 auto& noteman = NotificationManager::GetInstance();
1231 noteman.AddNotification(NotificationSeverity::kWarning,
1232 msg.ToStdString(), 60);
1233 }
1234 }
1235 UpdateDatastreams();
1236 top_panel->m_evt_add_connection.Notify();
1237 }
1238
1239 SwitchToGrid();
1240 UpdateDatastreams();
1241 }
1242
1243private:
1244 TopPanel* top_panel;
1245 ConnectionEditDialog* m_edit_panel;
1246};
1247
1250 wxWindow* parent, const std::vector<ConnectionParams*>& connections)
1251 : wxPanel(parent, wxID_ANY), m_connections(connections) {
1252 const auto vbox = new wxBoxSizer(wxVERTICAL);
1253 vbox->Add(new TopScroll(this, connections, m_evt_add_connection),
1254 wxSizerFlags(1).Expand());
1255 SetSizer(vbox);
1256 wxWindow::Fit();
1257 wxWindow::Show();
1258}
1259
1260void ConnectionsDlg::OnResize(const wxSize& size) {
1261 auto w = FindWindowByName(TopScrollWindowName);
1262 if (!w) return;
1263 w->SetMinSize(size);
1264 Fit();
1265}
1266
1267void ConnectionsDlg::DoApply(wxWindow* root) {
1268 for (wxWindow* child : root->GetChildren()) {
1269 if (auto widget = dynamic_cast<ApplyCancel*>(child)) widget->Apply();
1270 DoApply(child);
1271 }
1272}
1273
1274void ConnectionsDlg::DoCancel(wxWindow* root) {
1275 for (wxWindow* child : root->GetChildren()) {
1276 if (auto widget = dynamic_cast<ApplyCancel*>(child)) widget->Cancel();
1277 DoCancel(child);
1278 }
1279}
1280
1281void ConnectionsDlg::ApplySettings() { DoApply(this); }
1282
1283void ConnectionsDlg::CancelSettings() { DoCancel(this); }
1284
1285void ConnectionsDlg::SetColorScheme(ColorScheme cs) {
1286 auto w = dynamic_cast<TopScroll*>(FindWindowByName(TopScrollWindowName));
1287 if (w) w->SetColorScheme(cs);
1288}
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 bitmap in a grid cell.
Custom renderer class for rendering ENABLE in a grid cell.
std::sort support: Compare two 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
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.
std::string GetKey() const
Return string unique for each instance.
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.
Generic event handling between MVC Model and Controller based on a shared EventVar variable.
void Notify() override
Notify all listeners, no data supplied.
Indeed: the General panel.
Clickable, small info icon which displays a help text Typical usage:
Definition gui_lib.h:146
double GetDisplaySizeMM()
Get the width of the screen in millimeters.
Define an action to be performed when a KeyProvider is notified.
Definition observable.h:257
void Init(const KeyProvider &kp, const std::function< void(ObservedEvt &ev)> &action)
Initiate an object yet not listening.
Definition observable.h:295
Custom event class for OpenCPN's notification system.
The "Show advanced" text + right/down triangle and handler.
Standard icons bitmaps: settings gear, trash bin, etc.
Top panel: connections grid, "Add new connection", general options.
Top scroll window, adds scrollbars to TopPanel.
wxColour GetDialogColor(DialogColor color)
Retrieves a dialog color based on its role in the application's dialogs.
Global color handling by name.
@ DLG_BACKGROUND
Background color of the dialog.
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.
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:180
Options dialog.
Input priorities management dialog.
Driver statistics report.
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...
Definition svg_utils.cpp:59
SVG utilities.