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