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