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 constexpr 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(const 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->serial_port != new_cp->serial_port) continue;
122 return cp;
123 break;
124 case NETWORK:
125 if (cp->net_protocol != new_cp->net_protocol) continue;
126 if (cp->network_address != new_cp->network_address) continue;
127 if (cp->network_port != new_cp->network_port) 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->socket_can_port != new_cp->socket_can_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
317public:
318 explicit ConnCompare(int col) : m_col(col) {}
319
320 bool operator()(const ConnectionParams* p1,
321 const ConnectionParams* p2) const {
322 switch (m_col) {
323 case 0:
324 return static_cast<int>(p1->is_enabled) >
325 static_cast<int>(p2->is_enabled);
326 case 1:
327 return p1->GetCommProtocol() < p2->GetCommProtocol();
328 case 2:
330 case 3:
331 return p1->GetStrippedDSPort() < p2->GetStrippedDSPort();
332 default:
333 return false;
334 }
335 }
336
337private:
338 const int m_col;
339};
340
345public:
349 virtual ~ApplyCancel() = default;
350
352 virtual void Apply() = 0;
353
357 virtual void Cancel() = 0;
358};
359
361class AddConnectionButton final : public wxButton {
362public:
363 AddConnectionButton(wxWindow* parent, obs::EventVar& evt_add_connection,
364 const std::function<void(ConnectionParams* p,
365 bool editing)>& _start_edit_conn)
366 : wxButton(parent, wxID_ANY, _("Add new connection...")),
367 m_evt_add_connection(evt_add_connection),
368 m_start_edit_conn(_start_edit_conn) {
369 Bind(wxEVT_COMMAND_BUTTON_CLICKED,
370 [&](wxCommandEvent&) { OnAddConnection(); });
371 }
372
373private:
374 void OnAddConnection() const { m_start_edit_conn(nullptr, false); }
375
376 obs::EventVar& m_evt_add_connection;
377 std::function<void(ConnectionParams* p, bool editing)> m_start_edit_conn;
378};
379
381class Connections final : public wxGrid {
382public:
383 Connections(wxWindow* parent,
384 const std::vector<ConnectionParams*>& connections,
385 obs::EventVar& on_conn_update,
386 const std::function<void(ConnectionParams* p, bool editing)>&
387 on_edit_conn)
388 : wxGrid(parent, wxID_ANY),
389 m_connections(connections),
390 m_header_column_widths({0, 0, 0, 0, 0, 0, 0}),
391 m_last_tooltip_cell(100),
392 m_cs(GLOBAL_COLOR_SCHEME_DAY),
393 m_on_conn_delete(on_conn_update),
394 m_icons(parent),
395 m_on_edit_conn(on_edit_conn) {
396 ShowScrollbars(wxSHOW_SB_NEVER, wxSHOW_SB_NEVER);
397 SetTable(new wxGridStringTable(), true);
398 GetTable()->AppendCols(8);
399 HideCol(7);
400 if (IsAndroid()) {
401 SetDefaultRowSize(wxWindow::GetCharHeight() * 2);
402 SetColLabelSize(wxWindow::GetCharHeight() * 2);
403 }
404 static const std::array<wxString, 7> headers = {
405 "", _("Protocol") + " ", _("In/Out"), _("Data port"), _("Status"), "",
406 ""};
407 int ic = 0;
408 for (auto hdr = headers.begin(); hdr != headers.end(); hdr++, ic++) {
409 SetColLabelValue(static_cast<int>(hdr - headers.begin()), *hdr);
410 unsigned col_width = hdr->Length() * wxWindow::GetCharWidth();
411 col_width = wxMax(col_width, 6 * wxWindow::GetCharWidth());
412 m_header_column_widths[ic] = static_cast<int>(col_width);
413 SetColSize(ic, static_cast<int>(col_width));
414 }
415
416 if (IsWindows()) {
417 SetLabelBackgroundColour(GetGlobalColor("DILG1"));
418 SetLabelTextColour(GetGlobalColor("DILG3"));
419 }
420 HideRowLabels();
421 SetColAttributes(parent);
422
423 ReloadGrid(connections);
424 DisableDragColSize();
425 DisableDragRowSize();
426 wxWindow::Show(GetNumberRows() > 0);
427
428 GetGridWindow()->Bind(wxEVT_MOTION, [&](wxMouseEvent& ev) {
429 OnMouseMove(ev);
430 ev.Skip();
431 });
432 GetGridWindow()->Bind(wxEVT_MOUSEWHEEL,
433 [&](const wxMouseEvent& ev) { OnWheel(ev); });
434 // wxGridEvent.GetCol() and GetRow() are not const until wxWidgets 3.2
435 Bind(wxEVT_GRID_LABEL_LEFT_CLICK,
436 [&](wxGridEvent& ev) { HandleSort(ev.GetCol()); });
437 Bind(wxEVT_GRID_CELL_LEFT_CLICK,
438 [&](wxGridEvent& ev) { OnClickCell(ev.GetRow(), ev.GetCol()); });
439 Bind(wxEVT_PAINT, [&](wxPaintEvent& ev) {
440 SetColAttributes(dynamic_cast<const wxWindow*>(ev.GetEventObject()));
441 ev.Skip();
442 });
443 conn_change_lstnr.Init(
444 m_conn_states.evt_conn_status_change,
445 [&](ObservedEvt&) { OnConnectionChange(m_connections); });
446 }
447 void SetColorScheme(const ColorScheme cs) {
448 m_cs = cs;
449 m_icons.SetColorScheme(cs);
450 ReloadGrid(m_connections);
451 }
452
453 wxSize GetEstimatedSize() const {
454 unsigned rs = 0;
455 for (auto s : m_header_column_widths) rs += s;
456 return {static_cast<int>(rs), -1};
457 }
458
460 static void OnWheel(const wxMouseEvent& ev) {
461 auto w =
462 dynamic_cast<wxScrolledWindow*>(FindWindowByName(TopScrollWindowName));
463 assert(w && "No TopScroll window found");
464 int xpos;
465 int ypos;
466 w->GetViewStart(&xpos, &ypos);
467 int x;
468 int y;
469 w->GetScrollPixelsPerUnit(&x, &y);
470 // Not sure where the factor "4" comes from...
471 const int dir = ev.GetWheelRotation();
472 w->Scroll(-1, ypos - dir / y / 4);
473 }
474
476 void ReloadGrid(const std::vector<ConnectionParams*>& connections) {
477 ClearGrid();
478 m_renderer_status_vector.clear();
479
480 for (auto it = connections.begin(); it != connections.end(); ++it) {
481 const auto row = static_cast<int>(it - connections.begin());
482 EnsureRows(row);
483 SetCellValue(row, 0, (*it)->is_enabled ? "1" : "");
484 if ((*it)->is_enabled)
485 m_tooltips[row][0] = _("Enabled, click to disable");
486 else
487 m_tooltips[row][0] = _("Disabled, click to enable");
488 std::string protocol = NavAddr::BusToString((*it)->GetCommProtocol());
489 SetCellValue(row, 1, protocol);
490 SetCellValue(row, 2, (*it)->GetPortDirectionValueStr());
491 SetCellValue(row, 3, (*it)->GetStrippedDSPort());
492 m_tooltips[row][3] = (*it)->user_comment;
493 SetCellRenderer(row, 5, new BitmapCellRenderer(m_icons.settings, m_cs));
494 m_tooltips[row][5] = _("Edit connection");
495 SetCellRenderer(row, 6, new BitmapCellRenderer(m_icons.trash_bin, m_cs));
496 m_tooltips[row][6] = _("Delete connection");
497 SetCellValue(row, 7, (*it)->GetKey());
498
499 auto stat_renderer = new BitmapCellRenderer(m_icons.filled_circle, m_cs);
500 stat_renderer->status = ConnState::Disabled;
501 m_renderer_status_vector.push_back(stat_renderer);
502 SetCellRenderer(row, 4, stat_renderer);
503 if (IsAndroid()) {
504 wxString sp(protocol);
505 unsigned size = sp.Length() * wxWindow::GetCharWidth();
506 m_header_column_widths[1] = std::max(m_header_column_widths[1], size);
507 size = (*it)->GetPortDirectionValueStr().Length() *
508 wxWindow::GetCharWidth();
509 m_header_column_widths[2] = std::max(m_header_column_widths[2], size);
510 sp = wxString((*it)->GetStrippedDSPort());
511 size = sp.Length() * wxWindow::GetCharWidth();
512 m_header_column_widths[3] = std::max(m_header_column_widths[3], size);
513 }
514 }
515 OnConnectionChange(m_connections);
516
517 if (IsAndroid()) {
518 int ic = 0;
519 for (auto val : m_header_column_widths) {
520 SetColSize(ic, static_cast<int>(val));
521 ic++;
522 }
523 } else
524 AutoSize();
525 }
526
529 public:
530 explicit ConnStateCompare(Connections* connections)
531 : m_conns(connections) {}
532 bool operator()(const ConnectionParams* p1,
533 const ConnectionParams* p2) const {
534 int row1 = m_conns->FindConnectionIndex(p1);
535 int row2 = m_conns->FindConnectionIndex(p2);
536 if (row1 == -1 && row2 == -1) return false;
537 if (row1 == -1) return false;
538 if (row2 == -1) return true;
539 const int v1 = m_conns->GetCellValue(row1, 4)[0];
540 const int v2 = m_conns->GetCellValue(row2, 4)[0];
541 return v1 < v2;
542 }
543 Connections* m_conns;
544 };
545
551 using namespace std;
552 auto key = cp->GetKey();
553 auto found = find_if(
554 m_connections.begin(), m_connections.end(),
555 [key](const ConnectionParams* cp) { return cp->GetKey() == key; });
556 if (found == m_connections.end()) return -1;
557 return static_cast<int>(found - m_connections.begin());
558 }
559
560private:
565 ConnectionParams* FindRowConnection(const int row) const {
566 auto found = find_if(m_connections.begin(), m_connections.end(),
567 [&](const ConnectionParams* p) {
568 return GetCellValue(row, 7) == p->GetKey();
569 });
570 return found != m_connections.end() ? *found : nullptr;
571 }
572
577 void EnsureRows(size_t rows) {
578 for (unsigned i = m_tooltips.size(); i <= rows; i++)
579 m_tooltips.emplace_back(std::vector<std::string>(7));
580 for (unsigned i = GetNumberRows(); i <= rows; i++) AppendRows(1, false);
581 }
582
584 void SetColAttributes(const wxWindow* parent) {
585 if (IsWindows()) {
586 // Set all cells to global color scheme
587 SetDefaultCellBackgroundColour(GetGlobalColor("DILG1"));
588 }
589 auto enable_attr = new wxGridCellAttr();
590 enable_attr->SetAlignment(wxALIGN_CENTRE, wxALIGN_CENTRE);
591
592 if (IsAndroid())
593 enable_attr->SetRenderer(
594 new BitmapEnableCellRenderer(wxWindow::GetCharWidth() * 3 / 2, m_cs));
595 else
596 enable_attr->SetRenderer(new wxGridCellBoolRenderer());
597
598 enable_attr->SetEditor(new wxGridCellBoolEditor());
599 if (IsWindows()) enable_attr->SetBackgroundColour(GetGlobalColor("DILG1"));
600 SetColAttr(0, enable_attr);
601
602 auto protocol_attr = new wxGridCellAttr();
603 protocol_attr->SetAlignment(wxALIGN_LEFT, wxALIGN_CENTRE);
604 protocol_attr->SetReadOnly(true);
605 if (IsWindows())
606 protocol_attr->SetBackgroundColour(GetGlobalColor("DILG1"));
607 SetColAttr(1, protocol_attr);
608
609 auto in_out_attr = new wxGridCellAttr();
610 in_out_attr->SetAlignment(wxALIGN_CENTRE, wxALIGN_CENTRE);
611 in_out_attr->SetReadOnly(true);
612 if (IsWindows()) in_out_attr->SetBackgroundColour(GetGlobalColor("DILG1"));
613 SetColAttr(2, in_out_attr);
614
615 auto port_attr = new wxGridCellAttr();
616 port_attr->SetAlignment(wxALIGN_LEFT, wxALIGN_CENTRE);
617 port_attr->SetReadOnly(true);
618 if (IsWindows()) port_attr->SetBackgroundColour(GetGlobalColor("DILG1"));
619 SetColAttr(3, port_attr);
620
621 auto status_attr = new wxGridCellAttr();
622 status_attr->SetAlignment(wxALIGN_CENTRE, wxALIGN_CENTRE);
623 status_attr->SetReadOnly(true);
624 status_attr->SetFont(parent->GetFont().Scale(1.3));
625 if (IsWindows()) status_attr->SetBackgroundColour(GetGlobalColor("DILG1"));
626 SetColAttr(4, status_attr);
627
628 auto edit_attr = new wxGridCellAttr();
629 edit_attr->SetAlignment(wxALIGN_CENTRE, wxALIGN_CENTRE);
630 edit_attr->SetFont(parent->GetFont().Scale(1.3));
631 edit_attr->SetReadOnly(true);
632 if (IsWindows()) edit_attr->SetBackgroundColour(GetGlobalColor("DILG1"));
633 SetColAttr(5, edit_attr);
634
635 auto delete_attr = new wxGridCellAttr();
636 delete_attr->SetAlignment(wxALIGN_LEFT, wxALIGN_CENTRE);
637 delete_attr->SetFont(parent->GetFont().Scale(1.3));
638 delete_attr->SetTextColour(*wxRED);
639 delete_attr->SetReadOnly(true);
640 if (IsWindows()) delete_attr->SetBackgroundColour(GetGlobalColor("DILG1"));
641 SetColAttr(6, delete_attr);
642 }
643
645 void OnClickCell(int row, int col) {
646 if (col == 0)
647 HandleEnable(row);
648 else if (col == 5) {
649 HandleEdit(row);
650 } else if (col == 6) {
651 HandleDelete(row);
652 }
653 }
654
656 void OnMouseMove(const wxMouseEvent& ev) {
657 const wxPoint pt = ev.GetPosition();
658 int row = YToRow(pt.y);
659 int col = XToCol(pt.x);
660 if (col < 0 || col >= 7 || row < 0 || row >= GetNumberRows()) return;
661 if (row * 7 + col == m_last_tooltip_cell) return;
662 m_last_tooltip_cell = row * 7 + col;
663 GetGridWindow()->SetToolTip(m_tooltips[row][col]);
664 }
665
667 void OnConnectionChange(const std::vector<ConnectionParams*>& connections) {
668 bool refresh_needed = false;
669 for (auto it = connections.begin(); it != connections.end(); ++it) {
670 ConnState state = m_conn_states.GetDriverState(
671 (*it)->GetCommProtocol(), (*it)->GetStrippedDSPort());
672 if (!(*it)->is_enabled) state = ConnState::Disabled;
673 auto row = static_cast<int>(it - connections.begin());
674 EnsureRows(row);
675 if (static_cast<int>(m_renderer_status_vector.size()) < row + 1) continue;
676 switch (state) {
677 case ConnState::Disabled:
678 if (m_renderer_status_vector[row]->status != ConnState::Disabled) {
679 m_renderer_status_vector[row]->SetBitmap(m_icons.filled_circle);
680 m_renderer_status_vector[row]->status = ConnState::Disabled;
681 refresh_needed = true;
682 }
683 m_tooltips[row][4] = _("Disabled");
684 break;
685 case ConnState::NoStats:
686 if (m_renderer_status_vector[row]->status != ConnState::NoStats) {
687 m_renderer_status_vector[row]->SetBitmap(m_icons.open_circle);
688 m_renderer_status_vector[row]->status = ConnState::NoStats;
689 refresh_needed = true;
690 }
691 m_tooltips[row][4] = _("No driver statistics available");
692 break;
693 case ConnState::NoData:
694 if (m_renderer_status_vector[row]->status != ConnState::NoData) {
695 m_renderer_status_vector[row]->SetBitmap(m_icons.exclaim_mark);
696 m_renderer_status_vector[row]->status = ConnState::NoData;
697 refresh_needed = true;
698 }
699 m_tooltips[row][4] = _("No data flowing through connection");
700 break;
701 case ConnState::Unavailable:
702 if (m_renderer_status_vector[row]->status != ConnState::Unavailable) {
703 m_renderer_status_vector[row]->SetBitmap(m_icons.x_mult);
704 m_renderer_status_vector[row]->status = ConnState::Unavailable;
705 refresh_needed = true;
706 }
707 m_tooltips[row][4] = _("The device is unavailable");
708 break;
709 case ConnState::Ok:
710 if (m_renderer_status_vector[row]->status != ConnState::Ok) {
711 m_renderer_status_vector[row]->SetBitmap(m_icons.check_mark);
712 m_renderer_status_vector[row]->status = ConnState::Ok;
713 refresh_needed = true;
714 }
715 m_tooltips[row][4] = _("Data is flowing");
716 break;
717 }
718 }
719 if (refresh_needed) ForceRefresh();
720 }
721
723 void SetSortingColumn(int col) {
724 if (GetSortingColumn() != wxNOT_FOUND) {
725 int old_col = GetSortingColumn();
726 auto label = GetColLabelValue(old_col);
727 if (label[0] == UtfArrowDown()[0])
728 SetColLabelValue(old_col, label.substr(2));
729 }
730 auto label = GetColLabelValue(col);
731 if (label[0] != UtfArrowDown()[0])
732 SetColLabelValue(col, UtfArrowDown() + " " + label);
733 wxGrid::SetSortingColumn(col);
734 Fit();
735 }
736
738 void HandleSort(int col) {
739 if (col > 4) return;
740 auto& params = TheConnectionParams();
741 if (col < 4)
742 std::sort(params.begin(), params.end(), ConnCompare(col));
743 else // col == 4
744 std::sort(params.begin(), params.end(), ConnStateCompare(this));
746 SetSortingColumn(col);
747 }
748
750 void HandleEnable(int row) {
751 ConnectionParams* cp = FindRowConnection(row);
752 if (!cp) return;
753 cp->is_enabled = !cp->is_enabled;
754 cp->is_setup = FALSE; // trigger a rebuild/takedown of the connection
755 SetCellValue(row, 0, cp->is_enabled ? "1" : "");
756 if (cp->is_enabled)
757 m_tooltips[row][0] = _("Enabled, click to disable");
758 else
759 m_tooltips[row][0] = _("Disabled, click to enable");
760 DriverStats stats;
761 stats.driver_iface = cp->GetStrippedDSPort();
762 stats.driver_bus = cp->GetCommProtocol();
763 m_conn_states.HandleDriverStats(stats);
764 StopAndRemoveCommDriver(cp->GetStrippedDSPort(), cp->GetCommProtocol());
765 if (cp->is_enabled) MakeCommDriver(cp);
766 cp->is_setup = true;
767 if (!cp->is_enabled) {
768 SetCellValue(row, 4, UtfFilledCircle());
769 // ForceRefresh() apparently broken, see #4648
771 }
772 }
773
775 void HandleEdit(int row) {
776 if (ConnectionParams* cp = FindRowConnection(row)) {
777 Show(GetNumberRows() > 0);
778 m_on_edit_conn(cp, true);
779 }
780 }
781
783 void HandleDelete(int row) {
784 ConnectionParams* cp = FindRowConnection(row);
785 auto found = std::find(m_connections.begin(), m_connections.end(), cp);
786 if (found != m_connections.end()) {
787 std::stringstream ss;
788 ss << _("Ok to delete connection on ") << (*found)->GetStrippedDSPort();
789 int rcode = OCPNMessageBox(this, ss.str(), _("Delete connection?"),
790 wxOK | wxCANCEL);
791 if (rcode != wxID_OK && rcode != wxID_YES) return;
792 delete (*found)->options_panel;
793 StopAndRemoveCommDriver((*found)->GetStrippedDSPort(),
794 (*found)->GetCommProtocol());
795 TheConnectionParams().erase(found);
796 if (GetNumberRows() > static_cast<int>(m_connections.size()))
797 DeleteRows(GetNumberRows() - 1);
798 m_on_conn_delete.Notify();
799 }
800 }
801
802 obs::Listener conn_change_lstnr;
803 std::vector<std::vector<std::string>> m_tooltips;
804 ConnStates m_conn_states;
805 const std::vector<ConnectionParams*>& m_connections;
806 std::array<unsigned, 7> m_header_column_widths;
807 int m_last_tooltip_cell;
808 ColorScheme m_cs;
809 obs::EventVar& m_on_conn_delete;
810 StdIcons m_icons;
811 std::vector<BitmapCellRenderer*> m_renderer_status_vector;
812 std::function<void(ConnectionParams* p, bool editing)> m_on_edit_conn;
813};
814
816class GeneralPanel : public wxPanel {
817public:
818 explicit GeneralPanel(wxWindow* parent, wxSize max_size)
819 : wxPanel(parent, wxID_ANY) {
820 auto sizer = new wxStaticBoxSizer(wxVERTICAL, this, _("General"));
821 SetSizer(sizer);
822 auto flags = wxSizerFlags().Border();
823 m_upload_options = new UploadOptionsChoice(this);
824 sizer->Add(m_upload_options, flags);
825 sizer->Add(new PrioritiesBtn(this), flags);
826 wxWindow::SetMaxSize(max_size);
827 }
828 void SetColorScheme(ColorScheme) {
829 DimeControl(m_upload_options);
830 Refresh();
831 }
832
833private:
835 class PrioritiesBtn final : public wxButton {
836 public:
837 explicit PrioritiesBtn(wxWindow* parent)
838 : wxButton(parent, wxID_ANY, _("Adjust Nav data priorities...")) {
839 Bind(wxEVT_COMMAND_BUTTON_CLICKED, [&](wxCommandEvent&) {
840 PriorityDlg dialog(this->GetParent());
841 dialog.ShowModal();
842 });
843 }
844 };
845
847 class UploadOptionsChoice final : public wxRadioBox, public ApplyCancel {
848 public:
849 explicit UploadOptionsChoice(wxWindow* parent) : wxRadioBox() {
850 wxArrayString wx_choices;
851 for (auto& c : choices) wx_choices.Add(c);
852 Create(parent, wxID_ANY, _("Upload Format"), wxDefaultPosition,
853 wxDefaultSize, wx_choices, 0, wxRA_SPECIFY_ROWS);
854 DimeControl(this);
855 UploadOptionsChoice::Cancel();
856 }
857
858 void Cancel() override {
859 if (g_bGarminHostUpload)
860 SetSelection(1);
861 else if (g_GPS_Ident == "FurunoGP3X")
862 SetSelection(2);
863 else
864 SetSelection(0);
865 }
866
867 void Apply() override {
868 switch (GetSelection()) {
869 case 0:
870 g_bGarminHostUpload = false;
871 g_GPS_Ident = "Generic";
872 break;
873 case 1:
874 g_bGarminHostUpload = true;
875 g_GPS_Ident = "Generic";
876 break;
877 case 2:
878 g_bGarminHostUpload = false;
879 g_GPS_Ident = "FurunoGP3X";
880 break;
881 default:
882 assert(false && "Invalid upload case option");
883 }
884 }
885
886 const std::array<wxString, 3> choices = {
887 _("Generic NMEA 0183"), _("Garmin Host mode"), _("Furuno GP4X")};
888 };
889
890 UploadOptionsChoice* m_upload_options;
891};
892
894class ShowAdvanced : public wxStaticText {
895public:
896 ShowAdvanced(wxWindow* parent, std::function<void(bool)> on_toggle)
897 : wxStaticText(parent, wxID_ANY, ""),
898 m_on_toggle(std::move(on_toggle)),
899 m_show_advanced(true) {
900 Toggle();
901 Bind(wxEVT_LEFT_DOWN, [&](const wxMouseEvent& ev) { Toggle(); });
902 }
903
904private:
905 wxString m_label = _("Advanced ");
906 std::function<void(bool)> m_on_toggle;
907 bool m_show_advanced;
908
909 void Toggle() {
910 m_show_advanced = !m_show_advanced;
911 m_label = _("Advanced ");
912 m_label += m_show_advanced ? UtfArrowDown() : UtfArrowRight();
913 SetLabelText(m_label);
914 m_on_toggle(m_show_advanced);
915 }
916};
917
919class AdvancedPanel : public wxPanel {
920public:
921 explicit AdvancedPanel(wxWindow* parent, wxSize max_size)
922 : wxPanel(parent, wxID_ANY) {
923 auto sizer = new wxStaticBoxSizer(wxVERTICAL, this, "");
924 sizer->Add(new BearingsCheckbox(this), wxSizerFlags().Expand());
925 sizer->Add(new ExtraRmbRmcLine(this), wxSizerFlags().Expand());
926 sizer->Add(new NmeaFilterRow(this), wxSizerFlags().Expand());
927 sizer->Add(new TalkerIdRow(this), wxSizerFlags().Expand());
928 sizer->Add(new NetmaskRow(this), wxSizerFlags().Expand());
929 SetSizer(sizer);
930 wxWindow::SetMaxSize(max_size);
931 }
932
933private:
935 class BearingsCheckbox : public wxCheckBox, public ApplyCancel {
936 public:
937 explicit BearingsCheckbox(wxWindow* parent)
938 : wxCheckBox(parent, wxID_ANY,
939 _("Use magnetic bearing in output sentence APB")) {
940 wxCheckBox::SetValue(g_bMagneticAPB);
941 }
942
943 void Apply() override { g_bMagneticAPB = GetValue(); }
944 void Cancel() override { SetValue(g_bMagneticAPB); }
945 };
946
947 class ExtraRmbRmcLine : public wxPanel {
948 class RmbRmcCheckbox : public wxCheckBox, public ApplyCancel {
949 public:
950 explicit RmbRmcCheckbox(wxWindow* parent)
951 : wxCheckBox(parent, wxID_ANY,
952 _("Always send RMB and RMC NMEA0183 data")) {
953 wxCheckBox::SetValue(g_always_send_rmb_rmc);
954 }
955
956 void Apply() override { g_always_send_rmb_rmc = GetValue(); }
957 void Cancel() override { SetValue(g_always_send_rmb_rmc); }
958 };
959
960 public:
961 explicit ExtraRmbRmcLine(wxWindow* parent) : wxPanel(parent, wxID_ANY) {
962 auto hbox = new wxBoxSizer(wxHORIZONTAL);
963 hbox->Add(new RmbRmcCheckbox(this), wxSizerFlags().Expand());
964 hbox->Add(1, 1, 1, wxEXPAND);
965 hbox->Add(new InfoButton(this, g_btouch, kInfoHeader, kInfo),
966 wxSizerFlags().Border());
967 SetSizer(hbox);
968 wxWindow::Layout();
969 wxWindow::Show();
970 }
971 };
972 class ExtraRmbRmcCheckbox final : public wxCheckBox, public ApplyCancel {
973 public:
974 explicit ExtraRmbRmcCheckbox(wxWindow* parent)
975 : wxCheckBox(parent, wxID_ANY,
976 _("Always send RMB and RMC NMEA0183 data")) {
977 wxCheckBox::SetValue(g_always_send_rmb_rmc);
978 }
979
980 void Apply() override { g_always_send_rmb_rmc = GetValue(); }
981 void Cancel() override { SetValue(g_always_send_rmb_rmc); }
982 };
983
985 class NmeaFilterRow : public wxPanel, public ApplyCancel {
986 wxCheckBox* checkbox;
987 wxTextCtrl* filter_period;
988
989 public:
990 explicit NmeaFilterRow(wxWindow* parent) : wxPanel(parent) {
991 auto hbox = new wxBoxSizer(wxHORIZONTAL);
992 checkbox = new wxCheckBox(
993 this, wxID_ANY,
994 _("Filter NMEA course and speed data. Filter period: "));
995 checkbox->SetValue(g_bfilter_cogsog);
996 hbox->Add(checkbox, wxSizerFlags().Align(wxALIGN_CENTRE));
997 filter_period =
998 new wxTextCtrl(this, wxID_ANY, "", wxDefaultPosition,
999 wxSize(50, 3 * wxWindow::GetCharWidth()), 0);
1000 filter_period->SetValue(std::to_string(g_COGFilterSec));
1001 hbox->Add(filter_period, wxSizerFlags().Border());
1002 SetSizer(hbox);
1003 NmeaFilterRow::Cancel();
1004 }
1005
1006 void Apply() override {
1007 std::stringstream ss;
1008 ss << filter_period->GetValue();
1009 ss >> g_COGFilterSec;
1010 g_bfilter_cogsog = checkbox->GetValue();
1011 }
1012
1013 void Cancel() override {
1014 std::stringstream ss;
1015 ss << g_COGFilterSec;
1016 filter_period->SetValue(ss.str());
1017 checkbox->SetValue(g_bfilter_cogsog);
1018 }
1019 };
1020
1022 class TalkerIdRow : public wxPanel, public ApplyCancel {
1023 wxTextCtrl* text_ctrl;
1024
1025 public:
1026 explicit TalkerIdRow(wxWindow* parent) : wxPanel(parent) {
1027 auto hbox = new wxBoxSizer(wxHORIZONTAL);
1028 hbox->Add(new wxStaticText(this, wxID_ANY, _("NMEA 0183 Talker Id: ")),
1029 wxSizerFlags().Align(wxALIGN_CENTRE_VERTICAL).Border());
1030 text_ctrl = new wxTextCtrl(
1031 this, wxID_ANY, "", wxDefaultPosition,
1032 wxSize(4 * wxWindow::GetCharWidth(), 3 * wxWindow::GetCharWidth()));
1033 text_ctrl->SetToolTip(
1034 _("Enter a two-letter talker ID to override the default ID in NMEA "
1035 "sentences generated by OpenCPN (e.g., GP, HC, EC). This affects "
1036 "only sentences created by OpenCPN, not those forwarded from other "
1037 "devices."));
1038 text_ctrl->SetValue(g_TalkerIdText);
1039 hbox->Add(text_ctrl, wxSizerFlags().Border());
1040 SetSizer(hbox);
1041 TalkerIdRow::Cancel();
1042 }
1043
1044 void Apply() override { g_TalkerIdText = text_ctrl->GetValue(); }
1045 void Cancel() override { text_ctrl->SetValue(g_TalkerIdText); }
1046 };
1047
1049 class NetmaskRow : public wxPanel, public ApplyCancel {
1050 public:
1051 explicit NetmaskRow(wxWindow* parent)
1052 : wxPanel(parent),
1053 m_spin_ctrl(new wxSpinCtrl(this, wxID_ANY)),
1054 m_text(new wxStaticText(this, wxID_ANY, "")) {
1055 m_spin_ctrl->SetRange(8, 32);
1056 auto hbox = new wxBoxSizer(wxHORIZONTAL);
1057 auto flags = wxSizerFlags().Align(wxALIGN_CENTRE_VERTICAL).Border();
1058 hbox->Add(new wxStaticText(this, wxID_ANY, _("Netmask: ")), flags);
1059 hbox->Add(m_text, flags);
1060 hbox->Add(new wxStaticText(this, wxID_ANY, _("length (bits): ")), flags);
1061 hbox->Add(m_spin_ctrl, flags);
1062 SetSizer(hbox);
1063 NetmaskRow::Cancel();
1064
1065 Bind(wxEVT_SPINCTRL, [&](wxSpinEvent&) {
1066 m_text->SetLabel(BitsToDottedMask(m_spin_ctrl->GetValue()));
1067 Layout();
1068 });
1069 }
1070
1071 void Apply() override { g_netmask_bits = m_spin_ctrl->GetValue(); }
1072
1073 void Cancel() override {
1074 m_spin_ctrl->SetValue(g_netmask_bits);
1075 m_text->SetLabel(BitsToDottedMask(m_spin_ctrl->GetValue()));
1076 Layout();
1077 }
1078
1079 private:
1080 wxSpinCtrl* m_spin_ctrl;
1081 wxStaticText* m_text;
1082 };
1083};
1084
1086class TopPanel : public wxPanel {
1087public:
1088 TopPanel(wxWindow* parent, const std::vector<ConnectionParams*>& connections,
1089 obs::EventVar& evt_add_connection,
1090 const std::function<void(ConnectionParams* p, bool editing)>&
1091 on_edit_conn)
1092 : wxPanel(parent, wxID_ANY),
1093 m_evt_add_connection(evt_add_connection),
1094 m_connections(connections) {
1095 auto vbox = new wxBoxSizer(wxVERTICAL);
1096 auto conn_grid = new Connections(this, m_connections, m_evt_add_connection,
1097 on_edit_conn);
1098 wxSize panel_max_size(conn_grid->GetEstimatedSize());
1099 vbox->AddSpacer(wxWindow::GetCharHeight());
1100 auto conn_flags = wxSizerFlags().Border();
1101 if (IsAndroid()) conn_flags = wxSizerFlags().Border().Expand();
1102 vbox->Add(conn_grid, conn_flags);
1103 vbox->Add(new AddConnectionButton(this, m_evt_add_connection, on_edit_conn),
1104 wxSizerFlags().Border());
1105 vbox->Add(0, wxWindow::GetCharHeight()); // Expanding spacer
1106 auto panel_flags =
1107 wxSizerFlags().Border(wxLEFT | wxDOWN | wxRIGHT).Expand();
1108 m_general_panel = new GeneralPanel(this, panel_max_size);
1109 vbox->Add(m_general_panel, panel_flags);
1110
1111 auto advanced_panel = new AdvancedPanel(this, panel_max_size);
1112 m_advanced_panel = advanced_panel;
1113 auto on_toggle = [&, advanced_panel, vbox](bool show) {
1114 // FIXME advanced_panel->SetMaxSize({conn_grid->GetSize().x, -1});
1115 advanced_panel->Show(show);
1116 vbox->SetSizeHints(this);
1117 vbox->Fit(this);
1118 };
1119 vbox->Add(new ShowAdvanced(this, on_toggle), panel_flags);
1120 vbox->Add(advanced_panel, panel_flags.ReserveSpaceEvenIfHidden());
1121
1122 SetSizer(vbox);
1123 vbox->SetSizeHints(this);
1124 vbox->Fit(this);
1125 wxWindow::Fit();
1126 wxWindow::Show();
1127
1128 auto on_evt_update_connections = [&, conn_grid](ObservedEvt&) {
1129 conn_grid->ReloadGrid(TheConnectionParams());
1130 conn_grid->Show(conn_grid->GetNumberRows() > 0);
1131 Layout();
1132 };
1133 m_add_connection_lstnr.Init(m_evt_add_connection,
1134 on_evt_update_connections);
1135 m_conn_grid = conn_grid;
1136 }
1137
1138 void SetColorScheme(const ColorScheme cs) const {
1139 m_conn_grid->SetColorScheme(cs);
1140 m_general_panel->SetColorScheme(cs);
1141 }
1142 [[nodiscard]] Connections* GetConnectionsGrid() const { return m_conn_grid; }
1143
1144 obs::EventVar& m_evt_add_connection;
1145
1146private:
1147 const std::vector<ConnectionParams*>& m_connections;
1148 obs::Listener m_add_connection_lstnr;
1149 Connections* m_conn_grid;
1150 GeneralPanel* m_general_panel;
1151 AdvancedPanel* m_advanced_panel;
1152};
1153
1155class TopScroll : public wxScrolledWindow {
1156public:
1157 TopScroll(wxWindow* parent, const std::vector<ConnectionParams*>& connections,
1158 obs::EventVar& evt_add_connection)
1159 : wxScrolledWindow(parent, wxID_ANY, wxDefaultPosition, wxDefaultSize,
1160 wxVSCROLL | wxHSCROLL, TopScrollWindowName) {
1161 ShowScrollbars(wxSHOW_SB_NEVER, wxSHOW_SB_ALWAYS);
1162 auto vbox = new wxBoxSizer(wxVERTICAL);
1163 SetSizer(vbox);
1164
1165 auto on_edit_connection = [&](ConnectionParams* p, bool editing) {
1166 if (editing)
1167 HandleEdit(p);
1168 else
1169 HandleNew(p);
1170 };
1171
1172 top_panel =
1173 new TopPanel(this, connections, evt_add_connection, on_edit_connection);
1174 vbox->Add(top_panel, wxSizerFlags(1).Expand());
1175
1176 auto on_edit_click = [&](const ConnectionParams* p, bool new_mode,
1177 bool ok_cancel) {
1178 HandleEditFinish(p, new_mode, ok_cancel);
1179 };
1180
1181 m_edit_panel = new ConnectionEditDialog(this, on_edit_click);
1182 m_edit_panel->SetPropsLabel(_("Edit Selected Connection"));
1183 wxWindow* options = FindWindowByName("Options");
1184 assert(options && "Null Options window!");
1185 int fraction = 9;
1186#ifdef ANDROID
1187 fraction = 10;
1188#endif
1189 m_edit_panel->SetSize(
1190 wxSize(options->GetSize().x, options->GetSize().y * fraction / 10));
1191 vbox->Add(m_edit_panel, wxSizerFlags(0).Expand());
1192 m_edit_panel->Hide();
1193
1194 SetScrollRate(0, 10);
1195 if (IsAndroid()) SetScrollRate(1, 1);
1196
1197 SetScrollRate(0, 10);
1198 if (IsAndroid()) SetScrollRate(1, 1);
1199 }
1200
1201 void SetColorScheme(ColorScheme cs) const {
1202 if (top_panel) top_panel->SetColorScheme(cs);
1203 }
1204
1205 void HandleEdit(ConnectionParams* p) {
1206 m_edit_panel->SetPropsLabel(_("Edit Selected Connection"));
1207 m_edit_panel->SetNewMode(false);
1208 m_edit_panel->PreloadControls(p);
1209 m_edit_panel->AddOKCancelButtons();
1210 SwitchToEditor();
1211 }
1212
1213 void HandleNew(ConnectionParams*) {
1214 m_edit_panel->SetPropsLabel(_("Configure new connection"));
1215 m_edit_panel->SetDefaultConnectionParams();
1216 m_edit_panel->SetNewMode(true);
1217 m_edit_panel->AddOKCancelButtons();
1218 m_edit_panel->InitiateNewConnection();
1219 SwitchToEditor();
1220 }
1221
1222 void SwitchToEditor() {
1223 top_panel->Hide(); // TopPanel
1224 Scroll(0, 0);
1225 DimeControl(m_edit_panel);
1226 m_edit_panel->Show();
1227 g_options->ShowOKButtons(false);
1228 Layout();
1229 }
1230
1231 void SwitchToGrid() {
1232 g_options->ShowOKButtons(true);
1233 m_edit_panel->Hide();
1234 top_panel->Show();
1235 top_panel->GetConnectionsGrid()->ReloadGrid(TheConnectionParams());
1236 Layout();
1237 Scroll(0, 0);
1238 }
1239
1240 void HandleEditFinish(const ConnectionParams* cp_orig, bool new_mode,
1241 bool ok_cancel) {
1242 if (!ok_cancel) {
1243 SwitchToGrid();
1244 return;
1245 }
1246 // OK from EDIT mode
1247 if (!new_mode) {
1248 ConnectionParams* cp_edited = m_edit_panel->GetParamsFromControls();
1249 delete cp_orig->options_panel;
1250 StopAndRemoveCommDriver(cp_orig->GetStrippedDSPort(),
1251 cp_orig->GetCommProtocol());
1252 int index = top_panel->GetConnectionsGrid()->FindConnectionIndex(cp_orig);
1253 assert(index != -1 && "Cannot look up connection index");
1254 TheConnectionParams()[index] = cp_edited;
1255 cp_edited->is_setup = false; // Trigger new stream
1256 }
1257 // OK from NEW mode
1258 else {
1259 ConnectionParams* cp = m_edit_panel->GetParamsFromControls();
1260 if (cp) {
1261 if (FindConnectionByIface(cp)) {
1262 wxMessageDialog dialog(this, kInterfaceExistsMessage,
1263 _("Driver creation warning"),
1264 wxOK | wxCENTRE | wxICON_WARNING);
1265 dialog.ShowModal();
1266 }
1267 if (cp->IsPortValid()) {
1268 // Trigger new stream
1269 cp->is_setup = false;
1270 // transfer ownership FIXME use smart pointers
1271 TheConnectionParams().push_back(cp);
1272 } else {
1273 wxString msg =
1274 _("Unable to create a connection as configured. "
1275 "Connected port or address was missing.");
1276 auto& noteman = NotificationManager::GetInstance();
1277 noteman.AddNotification(NotificationSeverity::kWarning,
1278 msg.ToStdString(), 60);
1279 delete cp;
1280 }
1281 }
1282 UpdateDatastreams();
1283 top_panel->m_evt_add_connection.Notify();
1284 }
1285
1286 SwitchToGrid();
1287 UpdateDatastreams();
1288 }
1289
1290private:
1291 TopPanel* top_panel;
1292 ConnectionEditDialog* m_edit_panel;
1293};
1294
1297 wxWindow* parent, const std::vector<ConnectionParams*>& connections)
1298 : wxPanel(parent, wxID_ANY), m_connections(connections) {
1299 const auto vbox = new wxBoxSizer(wxVERTICAL);
1300 vbox->Add(new TopScroll(this, connections, m_evt_add_connection),
1301 wxSizerFlags(1).Expand());
1302 SetSizer(vbox);
1303 wxWindow::Fit();
1304 wxWindow::Show();
1305}
1306
1307void ConnectionsDlg::OnResize(const wxSize& size) {
1308 auto w = FindWindowByName(TopScrollWindowName);
1309 if (!w) return;
1310 w->SetMinSize(size);
1311 Fit();
1312}
1313
1314void ConnectionsDlg::DoApply(wxWindow* root) {
1315 for (wxWindow* child : root->GetChildren()) {
1316 if (auto widget = dynamic_cast<ApplyCancel*>(child)) widget->Apply();
1317 DoApply(child);
1318 }
1319}
1320
1321void ConnectionsDlg::DoCancel(wxWindow* root) {
1322 for (wxWindow* child : root->GetChildren()) {
1323 if (auto widget = dynamic_cast<ApplyCancel*>(child)) widget->Cancel();
1324 DoCancel(child);
1325 }
1326}
1327
1328void ConnectionsDlg::ApplySettings() { DoApply(this); }
1329
1330void ConnectionsDlg::CancelSettings() { DoCancel(this); }
1331
1332void ConnectionsDlg::SetColorScheme(ColorScheme cs) {
1333 auto w = dynamic_cast<TopScroll*>(FindWindowByName(TopScrollWindowName));
1334 if (w) w->SetColorScheme(cs);
1335}
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 twSetInitialSettingso ConnectionParams w r t given column
Filter reading driver driver_stats status reports from CommDriverRegistry transforming these to a str...
Definition conn_states.h:64
obs::EventVar evt_conn_status_change
Notified with a shared_ptr<ConnData> when the state is changed.
Definition conn_states.h:70
Dialog for editing connection parameters.
ConnectionParams * GetParamsFromControls()
Return parameters instance populated from UI elements owned by caller.
Connection data container close to a POD struct.
Definition conn_params.h:75
bool IsPortValid() const
Return true if port data like com port or network address are sane.
std::string GetStrippedDSPort() const
Return port string with possible windows extra data removed, in some cases empty.
std::string GetKey() const
Return string unique for each instance.
wxString GetPortDirectionValueStr() const
Return translated name for direction, like _("Input").
NavAddr::Bus GetCommProtocol() const
Return NavAddr::Bus corresponding to network address/port or serial parameters.
void ApplySettings()
Make dialog's settings the active ones, usually by updating globals.
void OnResize(const wxSize &size)
Resize the connections tab.
ConnectionsDlg(wxWindow *parent, const std::vector< ConnectionParams * > &connections)
Create a new ConnectionsDlg instance.
void CancelSettings()
Restore dialog settings from currently used values, usually globals.
std::sort support: Compare two ConnectionParams w r t state.
Grid with existing connections: type, port, status, etc.
static void OnWheel(const wxMouseEvent &ev)
Mouse wheel: scroll the TopScroll window.
void ReloadGrid(const std::vector< ConnectionParams * > &connections)
Reload grid using data from given list of connections.
int FindConnectionIndex(const ConnectionParams *cp) const
Find index in m_connections for given pointer.
Indeed: the General panel.
Clickable, small info icon which displays a help text Typical usage:
Definition gui_lib.h:146
double GetDisplaySizeMM()
Get the width of the screen in millimeters.
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.
Generic event handling between for example MVC Model and Controller based on a shared EventVar variab...
Definition evtvar.h:78
void Notify() override
Notify all listeners, no data supplied.
Definition evtvar.h:83
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.
std::vector< ConnectionParams * > & TheConnectionParams()
Return global list of connections.
Connection parameters.
Panel for editing a connection.
Runtime connection/driver state definitions.
Dialog and support code for editing a connection.
Options | Connections GUI tab managing connections
Platform independent GL includes.
Misc GUI event vars, a singleton.
General purpose GUI support.
Utility functions.
User notifications manager.
OpenCPN Platform specific support utilities.
options * g_options
Global instance.
Definition options.cpp:181
Options dialog.
Input priorities management dialog.
Driver statistics report.
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.