OpenCPN Partial API docs
Loading...
Searching...
No Matches
connections_dlg.cpp
Go to the documentation of this file.
1
2/***************************************************************************
3 * Copyright (C) 2025 Alec Leamas *
4 * *
5 * This program is free software; you can redistribute it and/or modify *
6 * it under the terms of the GNU General Public License as published by *
7 * the Free Software Foundation; either version 2 of the License, or *
8 * (at your option) any later version. *
9 * *
10 * This program is distributed in the hope that it will be useful, *
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of *
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
13 * GNU General Public License for more details. *
14 * *
15 * You should have received a copy of the GNU General Public License *
16 * along with this program; if not, see <https://www.gnu.org/licenses/>. *
17 **************************************************************************/
18
23#include <array>
24#include <algorithm>
25#include <sstream>
26#include <string>
27#include <vector>
28
29#include <wx/arrstr.h>
30#include <wx/bitmap.h>
31#include <wx/grid.h>
32#include <wx/panel.h>
33#include <wx/sizer.h>
34#include <wx/spinctrl.h>
35#include <wx/textctrl.h>
36#include <wx/window.h>
37
38#include "model/base_platform.h"
39#include "model/comm_drv_factory.h"
40#include "model/comm_util.h"
41#include "model/config_vars.h"
42#include "model/conn_params.h"
43#include "model/conn_states.h"
45
46#include "connections_dlg.h"
47
48#include "color_handler.h"
49#include "connection_edit.h"
50#include "conn_params_panel.h"
51#include "gui_lib.h"
52#include "navutil.h"
53#include "OCPNPlatform.h"
54#include "priority_gui.h"
55#include "std_filesystem.h"
56#include "svg_utils.h"
57
58extern OCPNPlatform* g_Platform;
59
60static const auto kUtfArrowDown = wxString::FromUTF8(u8"\u25bc");
61static const auto kUtfArrowRight = wxString::FromUTF8(u8"\u25ba");
62static const auto kUtfFilledCircle = wxString::FromUTF8(u8"\u2b24");
63
64static const char* const TopScrollWindowName = "TopScroll";
65
66static inline bool IsWindows() {
67 return wxPlatformInfo::Get().GetOperatingSystemId() & wxOS_WINDOWS;
68}
69
70static inline bool IsAndroid() {
71#ifdef ANDROID
72 return true;
73#else
74 return false;
75#endif
76}
77
78static std::string BitsToDottedMask(unsigned bits) {
79 uint32_t mask = 0xffffffff << (32 - bits);
80 std::stringstream ss;
81 ss << ((mask & 0xff000000) >> 24) << ".";
82 ss << ((mask & 0x00ff0000) >> 16) << ".";
83 ss << ((mask & 0x0000ff00) >> 8) << ".";
84 ss << (mask & 0x000000ff);
85 return ss.str();
86}
87
89class StdIcons {
90private:
91 const double m_size;
92 const fs::path m_svg_dir;
93
95 double GetSize(const wxWindow* parent) {
96 double size = parent->GetCharHeight() * (IsWindows() ? 1.3 : 1.0);
97#if wxCHECK_VERSION(3, 1, 2)
98 // Apply scale factor, mostly for Windows. Other platforms
99 // does this in the toolkits, ToDIP() is aware of this.
100 size *= static_cast<double>(parent->ToDIP(100)) / 100.;
101#endif
102 // Force minimum physical size for touch screens
103 if (g_btouch) {
104 double pixel_per_mm =
105 wxGetDisplaySize().x / g_Platform->GetDisplaySizeMM();
106 size = std::max(size, 7.0 * pixel_per_mm);
107 }
108 return size;
109 }
110
111 wxBitmap LoadIcon(const std::string& filename) const {
112 fs::path path = m_svg_dir / filename;
113 return LoadSVG(path.string(), m_size, m_size);
114 }
115
116public:
117 StdIcons(const wxWindow* parent)
118 : m_size(GetSize(parent)),
119 m_svg_dir(fs::path(g_Platform->GetSharedDataDir().ToStdString()) /
120 "uidata" / "MUI_flat"),
121 trashbin(LoadIcon("trash_bin.svg")),
122 settings(LoadIcon("setting_gear.svg")),
123 filled_circle(LoadIcon("circle-on.svg")),
124 open_circle(LoadIcon("circle-off.svg")),
125 exclaim_mark(LoadIcon("exclaim_mark.svg")),
126 x_mult(LoadIcon("X_mult.svg")),
127 check_mark(LoadIcon("check_mark.svg")) {}
128
129 const wxBitmap trashbin;
130 const wxBitmap settings;
131 const wxBitmap filled_circle;
132 const wxBitmap open_circle;
133 const wxBitmap exclaim_mark;
134 const wxBitmap x_mult;
135 const wxBitmap check_mark;
136};
137
139class BitmapCellRenderer : public wxGridCellRenderer {
140public:
141 BitmapCellRenderer(const wxBitmap& bitmap)
142 : status(ConnState::Disabled), m_bitmap(bitmap) {}
143
144 // Update the bitmap dynamically
145 void SetBitmap(const wxBitmap& bitmap) { m_bitmap = bitmap; }
146
147 void Draw(wxGrid& grid, wxGridCellAttr& attr, wxDC& dc, const wxRect& rect,
148 int row, int col, bool isSelected) override {
149 dc.SetBrush(wxBrush(GetGlobalColor("DILG2")));
150 if (IsWindows()) dc.SetBrush(wxBrush(GetGlobalColor("DILG1")));
151 dc.DrawRectangle(rect);
152
153 // Draw the bitmap centered in the cell
154 dc.DrawBitmap(m_bitmap, rect.x + (rect.width - m_bitmap.GetWidth()) / 2,
155 rect.y + (rect.height - m_bitmap.GetHeight()) / 2, true);
156 }
157
158 wxSize GetBestSize(wxGrid& grid, wxGridCellAttr& attr, wxDC& dc, int row,
159 int col) override {
160 // Return the size of the bitmap as the best size for the cell
161 return wxSize(m_bitmap.GetWidth(), m_bitmap.GetHeight());
162 }
163
164 BitmapCellRenderer* Clone() const override {
165 return new BitmapCellRenderer(m_bitmap);
166 }
167 ConnState status;
168
169private:
170 wxBitmap m_bitmap;
171};
172
175public:
176 ConnCompare(int col) : m_col(col) {}
177
178 bool operator()(const ConnectionParams* p1, const ConnectionParams* p2) {
179 switch (m_col) {
180 case 0:
181 return static_cast<int>(p1->bEnabled) > static_cast<int>(p2->bEnabled);
182 case 1:
183 return p1->GetCommProtocol() < p2->GetCommProtocol();
184 case 2:
185 return p1->GetIOTypeValueStr() < p2->GetIOTypeValueStr();
186 case 3:
187 return p1->GetStrippedDSPort() < p2->GetStrippedDSPort();
188 default:
189 return false;
190 }
191 }
192
193private:
194 const int m_col;
195};
196
201public:
202 virtual ~ApplyCancel() = default;
203
205 virtual void Apply() = 0;
206
210 virtual void Cancel() = 0;
211};
212
214class AddConnectionButton : public wxButton {
215public:
216 AddConnectionButton(wxWindow* parent, EventVar& evt_add_connection)
217 : wxButton(parent, wxID_ANY, _("Add new connection...")),
218 m_evt_add_connection(evt_add_connection) {
219 Bind(wxEVT_COMMAND_BUTTON_CLICKED,
220 [&](wxCommandEvent& ev) { OnAddConnection(); });
221 }
222
223private:
224 void OnAddConnection() {
225 ConnectionEditDialog dialog(this);
226 dialog.SetPropsLabel(_("Configure new connection"));
227 dialog.SetDefaultConnectionParams();
228 wxWindow* options = wxWindow::FindWindowByName("Options");
229 assert(options && "Null Options window!");
230 dialog.SetSize(wxSize(options->GetSize().x, options->GetSize().y * 8 / 10));
231 auto rv = dialog.ShowModal();
232 if (rv == wxID_OK) {
233 if (ConnectionParams* cp = dialog.GetParamsFromControls()) {
234 if (cp->GetValidPort()) {
235 cp->b_IsSetup = false; // Trigger new stream
236 TheConnectionParams().push_back(cp);
237 } else {
238 wxString msg =
239 _("Unable to create a connection as configured. "
240 "Connected port or address was missing.");
241 auto& noteman = NotificationManager::GetInstance();
242 noteman.AddNotification(NotificationSeverity::kWarning,
243 msg.ToStdString(), 60);
244 }
245 }
246 UpdateDatastreams();
247 m_evt_add_connection.Notify();
248 }
249 }
250
251 EventVar& m_evt_add_connection;
252};
253
255class Connections : public wxGrid {
256public:
257 Connections(wxWindow* parent,
258 const std::vector<ConnectionParams*>& connections,
259 EventVar& on_conn_update)
260 : wxGrid(parent, wxID_ANY),
261 m_connections(connections),
262 m_on_conn_delete(on_conn_update),
263 m_last_tooltip_cell(100),
264 m_icons(parent) {
265 ShowScrollbars(wxSHOW_SB_NEVER, wxSHOW_SB_NEVER);
266 SetTable(new wxGridStringTable(), false);
267 GetTable()->AppendCols(8);
268 HideCol(7);
269 if (IsAndroid()) {
270 SetDefaultRowSize(wxWindow::GetCharHeight() * 2);
271 SetColLabelSize(wxWindow::GetCharHeight() * 2);
272 }
273 static const std::array<wxString, 7> headers = {
274 "", _("Protocol"), _("In/Out"), _("Data port"), _("Status"), "", ""};
275 int ic = 0;
276 for (auto hdr = headers.begin(); hdr != headers.end(); hdr++, ic++) {
277 SetColLabelValue(static_cast<int>(hdr - headers.begin()), *hdr);
278 int col_width = (*hdr).Length() * GetCharWidth();
279 col_width = wxMax(col_width, 6 * GetCharWidth());
280 header_column_widths[ic] = col_width;
281 SetColSize(ic, col_width);
282 }
283
284 if (IsWindows()) {
285 SetLabelBackgroundColour(GetGlobalColor("DILG1"));
286 SetLabelTextColour(GetGlobalColor("DILG3"));
287 }
288 HideRowLabels();
289 SetColAttributes(parent);
290
291 ReloadGrid(connections);
292 DisableDragColSize();
293 DisableDragRowSize();
294 wxWindow::Show(GetNumberRows() > 0);
295
296 GetGridWindow()->Bind(wxEVT_MOTION, [&](wxMouseEvent& ev) {
297 OnMouseMove(ev);
298 ev.Skip();
299 });
300 GetGridWindow()->Bind(wxEVT_MOUSEWHEEL,
301 [&](const wxMouseEvent& ev) { OnWheel(ev); });
302 // wxGridEvent.GetCol() and GetRow() are not const until wxWidgets 3.2
303 Bind(wxEVT_GRID_LABEL_LEFT_CLICK,
304 [&](wxGridEvent& ev) { HandleSort(ev.GetCol()); });
305 Bind(wxEVT_GRID_CELL_LEFT_CLICK,
306 [&](wxGridEvent& ev) { OnClickCell(ev.GetRow(), ev.GetCol()); });
307 Bind(wxEVT_PAINT, [&](wxPaintEvent& ev) {
308 SetColAttributes(static_cast<wxWindow*>(ev.GetEventObject()));
309 ev.Skip();
310 });
311 conn_change_lstnr.Init(
312 m_conn_states.evt_conn_status_change,
313 [&](ObservedEvt&) { OnConnectionChange(m_connections); });
314 }
315
316 wxSize GetEstimatedSize() {
317 int rs = 0;
318 for (auto s : header_column_widths) rs += s;
319 return wxSize(rs, -1);
320 }
321
323 void OnWheel(const wxMouseEvent& ev) {
324 auto w = static_cast<wxScrolledWindow*>(
325 wxWindow::FindWindowByName(TopScrollWindowName));
326 assert(w && "No TopScroll window found");
327 int xpos;
328 int ypos;
329 w->GetViewStart(&xpos, &ypos);
330 int x;
331 int y;
332 w->GetScrollPixelsPerUnit(&x, &y);
333 // Not sure where the factor "4" comes from...
334 int dir = ev.GetWheelRotation();
335 w->Scroll(-1, ypos - dir / y / 4);
336 }
337
339 void ReloadGrid(const std::vector<ConnectionParams*>& connections) {
340 ClearGrid();
341 m_renderer_status_vector.clear();
342
343 for (auto it = connections.begin(); it != connections.end(); it++) {
344 auto row = static_cast<int>(it - connections.begin());
345 EnsureRows(row);
346 SetCellValue(row, 0, (*it)->bEnabled ? "1" : "");
347 if ((*it)->bEnabled)
348 m_tooltips[row][0] = _("Enabled, click to disable");
349 else
350 m_tooltips[row][0] = _("Disabled, click to enable");
351 std::string protocol = NavAddr::BusToString((*it)->GetCommProtocol());
352 SetCellValue(row, 1, protocol);
353 SetCellValue(row, 2, (*it)->GetIOTypeValueStr());
354 SetCellValue(row, 3, (*it)->GetStrippedDSPort());
355 m_tooltips[row][3] = (*it)->UserComment;
356 SetCellRenderer(row, 5, new BitmapCellRenderer(m_icons.settings));
357 m_tooltips[row][5] = _("Edit connection");
358 SetCellRenderer(row, 6, new BitmapCellRenderer(m_icons.trashbin));
359 m_tooltips[row][6] = _("Delete connection");
360 SetCellValue(row, 7, (*it)->GetKey());
361
362 auto stat_renderer = new BitmapCellRenderer(m_icons.filled_circle);
363 stat_renderer->status = ConnState::Disabled;
364 m_renderer_status_vector.push_back(stat_renderer);
365 SetCellRenderer(row, 4, stat_renderer);
366 if (IsAndroid()) {
367 wxString sp(protocol);
368 int ssize = sp.Length() * wxWindow::GetCharWidth();
369 header_column_widths[1] = wxMax(header_column_widths[1], ssize);
370 ssize = (*it)->GetIOTypeValueStr().Length() * wxWindow::GetCharWidth();
371 header_column_widths[2] = wxMax(header_column_widths[2], ssize);
372 sp = wxString((*it)->GetStrippedDSPort());
373 ssize = sp.Length() * wxWindow::GetCharWidth();
374 header_column_widths[3] = wxMax(header_column_widths[3], ssize);
375 }
376 }
377 OnConnectionChange(m_connections);
378
379 if (IsAndroid()) {
380 int ic = 0;
381 for (auto val : header_column_widths) {
382 SetColSize(ic, val);
383 ic++;
384 }
385 } else
386 AutoSize();
387 }
388
391 public:
392 ConnStateCompare(Connections* connections) : m_conns(connections) {}
393 bool operator()(const ConnectionParams* p1, const ConnectionParams* p2) {
394 int row1 = m_conns->FindConnectionIndex(p1);
395 int row2 = m_conns->FindConnectionIndex(p2);
396 if (row1 == -1 && row2 == -1) return false;
397 if (row1 == -1) return false;
398 if (row2 == -1) return true;
399 int v1 = static_cast<int>(m_conns->GetCellValue(row1, 4)[0]);
400 int v2 = static_cast<int>(m_conns->GetCellValue(row2, 4)[0]);
401 return v1 < v2;
402 }
403 Connections* m_conns;
404 };
405
406private:
411 ConnectionParams* FindRowConnection(int row) const {
412 auto found = find_if(m_connections.begin(), m_connections.end(),
413 [&](const ConnectionParams* p) {
414 return GetCellValue(row, 7) == p->GetKey();
415 });
416 return found != m_connections.end() ? *found : nullptr;
417 }
418
423 int FindConnectionIndex(const ConnectionParams* cp) const {
424 using namespace std;
425 auto key = cp->GetKey();
426 auto found = find_if(
427 m_connections.begin(), m_connections.end(),
428 [key](const ConnectionParams* cp) { return cp->GetKey() == key; });
429 if (found == m_connections.end()) return -1;
430 return static_cast<int>(found - m_connections.begin());
431 }
432
437 void EnsureRows(size_t rows) {
438 while (m_tooltips.size() <= rows)
439 m_tooltips.push_back(std::vector<std::string>(7));
440 while (GetNumberRows() <= static_cast<int>(rows)) AppendRows(1, false);
441 }
442
444 void SetColAttributes(const wxWindow* parent) {
445 if (IsWindows()) {
446 // Set all cells to global color scheme
447 SetDefaultCellBackgroundColour(GetGlobalColor("DILG1"));
448 }
449 auto enable_attr = new wxGridCellAttr();
450 enable_attr->SetAlignment(wxALIGN_CENTRE, wxALIGN_CENTRE);
451 enable_attr->SetRenderer(new wxGridCellBoolRenderer());
452 enable_attr->SetEditor(new wxGridCellBoolEditor());
453 if (IsWindows()) enable_attr->SetBackgroundColour(GetGlobalColor("DILG1"));
454 SetColAttr(0, enable_attr);
455
456 auto protocol_attr = new wxGridCellAttr();
457 protocol_attr->SetAlignment(wxALIGN_LEFT, wxALIGN_CENTRE);
458 protocol_attr->SetReadOnly(true);
459 if (IsWindows())
460 protocol_attr->SetBackgroundColour(GetGlobalColor("DILG1"));
461 SetColAttr(1, protocol_attr);
462
463 auto in_out_attr = new wxGridCellAttr();
464 in_out_attr->SetAlignment(wxALIGN_CENTRE, wxALIGN_CENTRE);
465 in_out_attr->SetReadOnly(true);
466 if (IsWindows()) in_out_attr->SetBackgroundColour(GetGlobalColor("DILG1"));
467 SetColAttr(2, in_out_attr);
468
469 auto port_attr = new wxGridCellAttr();
470 port_attr->SetAlignment(wxALIGN_LEFT, wxALIGN_CENTRE);
471 port_attr->SetReadOnly(true);
472 if (IsWindows()) port_attr->SetBackgroundColour(GetGlobalColor("DILG1"));
473 SetColAttr(3, port_attr);
474
475 auto status_attr = new wxGridCellAttr();
476 status_attr->SetAlignment(wxALIGN_CENTRE, wxALIGN_CENTRE);
477 status_attr->SetReadOnly(true);
478 status_attr->SetFont(parent->GetFont().Scale(1.3));
479 if (IsWindows()) status_attr->SetBackgroundColour(GetGlobalColor("DILG1"));
480 SetColAttr(4, status_attr);
481
482 auto edit_attr = new wxGridCellAttr();
483 edit_attr->SetAlignment(wxALIGN_CENTRE, wxALIGN_CENTRE);
484 edit_attr->SetFont(parent->GetFont().Scale(1.3));
485 edit_attr->SetReadOnly(true);
486 if (IsWindows()) edit_attr->SetBackgroundColour(GetGlobalColor("DILG1"));
487 SetColAttr(5, edit_attr);
488
489 auto delete_attr = new wxGridCellAttr();
490 delete_attr->SetAlignment(wxALIGN_LEFT, wxALIGN_CENTRE);
491 delete_attr->SetFont(parent->GetFont().Scale(1.3));
492 delete_attr->SetTextColour(*wxRED);
493 delete_attr->SetReadOnly(true);
494 if (IsWindows()) delete_attr->SetBackgroundColour(GetGlobalColor("DILG1"));
495 SetColAttr(6, delete_attr);
496 }
497
499 void OnClickCell(int row, int col) {
500 if (col == 0)
501 HandleEnable(row);
502 else if (col == 5) {
503 HandleEdit(row);
504 } else if (col == 6) {
505 HandleDelete(row);
506 }
507 }
508
510 void OnMouseMove(const wxMouseEvent& ev) {
511 wxPoint pt = ev.GetPosition();
512 int row = YToRow(pt.y);
513 int col = XToCol(pt.x);
514 if (col < 0 || col >= 7 || row < 0 || row >= GetNumberRows()) return;
515 if (row * 7 + col == m_last_tooltip_cell) return;
516 m_last_tooltip_cell = row * 7 + col;
517 GetGridWindow()->SetToolTip(m_tooltips[row][col]);
518 }
519
521 void OnConnectionChange(const std::vector<ConnectionParams*>& connections) {
522 bool refresh_needed = false;
523 for (auto it = connections.begin(); it != connections.end(); it++) {
524 ConnState state = m_conn_states.GetDriverState(
525 (*it)->GetCommProtocol(), (*it)->GetStrippedDSPort());
526 if (!(*it)->bEnabled) state = ConnState::Disabled;
527 auto row = static_cast<int>(it - connections.begin());
528 EnsureRows(row);
529 if (m_renderer_status_vector.size() < static_cast<size_t>(row + 1))
530 continue;
531 switch (state) {
532 case ConnState::Disabled:
533 if (m_renderer_status_vector[row]->status != ConnState::Disabled) {
534 m_renderer_status_vector[row]->SetBitmap(m_icons.filled_circle);
535 m_renderer_status_vector[row]->status = ConnState::Disabled;
536 refresh_needed = true;
537 }
538 m_tooltips[row][4] = _("Disabled");
539 break;
540 case ConnState::NoStats:
541 if (m_renderer_status_vector[row]->status != ConnState::NoStats) {
542 m_renderer_status_vector[row]->SetBitmap(m_icons.open_circle);
543 m_renderer_status_vector[row]->status = ConnState::NoStats;
544 refresh_needed = true;
545 }
546 m_tooltips[row][4] = _("No driver statistics available");
547 break;
548 case ConnState::NoData:
549 if (m_renderer_status_vector[row]->status != ConnState::NoData) {
550 m_renderer_status_vector[row]->SetBitmap(m_icons.exclaim_mark);
551 m_renderer_status_vector[row]->status = ConnState::NoData;
552 refresh_needed = true;
553 }
554 m_tooltips[row][4] = _("No data flowing through connection");
555 break;
556 case ConnState::Unavailable:
557 if (m_renderer_status_vector[row]->status != ConnState::Unavailable) {
558 m_renderer_status_vector[row]->SetBitmap(m_icons.x_mult);
559 m_renderer_status_vector[row]->status = ConnState::Unavailable;
560 refresh_needed = true;
561 }
562 m_tooltips[row][4] = _("The device is unavailable");
563 break;
564 case ConnState::Ok:
565 if (m_renderer_status_vector[row]->status != ConnState::Ok) {
566 m_renderer_status_vector[row]->SetBitmap(m_icons.check_mark);
567 m_renderer_status_vector[row]->status = ConnState::Ok;
568 refresh_needed = true;
569 }
570 m_tooltips[row][4] = _("Data is flowing");
571 break;
572 }
573 }
574 if (refresh_needed) ForceRefresh();
575 }
576
578 void SetSortingColumn(int col) {
579 if (GetSortingColumn() != wxNOT_FOUND) {
580 int old_col = GetSortingColumn();
581 auto label = GetColLabelValue(old_col);
582 if (label[0] == kUtfArrowDown[0])
583 SetColLabelValue(old_col, label.substr(2));
584 }
585 auto label = GetColLabelValue(col);
586 if (label[0] != kUtfArrowDown[0])
587 SetColLabelValue(col, kUtfArrowDown + " " + label);
588 wxGrid::SetSortingColumn(col);
589 Fit();
590 }
591
593 void HandleSort(int col) {
594 if (col > 4) return;
595 auto& params = TheConnectionParams();
596 if (col < 4)
597 std::sort(params.begin(), params.end(), ConnCompare(col));
598 else // col == 4
599 std::sort(params.begin(), params.end(), ConnStateCompare(this));
600 ReloadGrid(TheConnectionParams());
601 SetSortingColumn(col);
602 }
603
605 void HandleEnable(int row) {
606 ConnectionParams* cp = FindRowConnection(row);
607 if (!cp) return;
608 cp->bEnabled = !cp->bEnabled;
609 cp->b_IsSetup = FALSE; // trigger a rebuild/takedown of the connection
610 SetCellValue(row, 0, cp->bEnabled ? "1" : "");
611 if (cp->bEnabled)
612 m_tooltips[row][0] = _("Enabled, click to disable");
613 else
614 m_tooltips[row][0] = _("Disabled, click to enable");
615 DriverStats stats;
616 stats.driver_iface = cp->GetStrippedDSPort();
617 stats.driver_bus = cp->GetCommProtocol();
618 m_conn_states.HandleDriverStats(stats);
619 StopAndRemoveCommDriver(cp->GetStrippedDSPort(), cp->GetCommProtocol());
620 if (cp->bEnabled) MakeCommDriver(cp);
621 cp->b_IsSetup = true;
622 if (!cp->bEnabled) {
623 SetCellValue(row, 4, kUtfFilledCircle);
624 ForceRefresh();
625 }
626 }
627
629 void HandleEdit(int row) {
630 if (ConnectionParams* cp = FindRowConnection(row)) {
631 ConnectionEditDialog dialog(this);
632 DimeControl(&dialog);
633 dialog.SetPropsLabel(_("Edit Selected Connection"));
634 dialog.PreloadControls(cp);
635 wxWindow* options = wxWindow::FindWindowByName("Options");
636 assert(options && "Null Options window!");
637 dialog.SetSize(
638 wxSize(options->GetSize().x, options->GetSize().y * 8 / 10));
639 Show(GetNumberRows() > 0);
640
641 auto rv = dialog.ShowModal();
642 if (rv == wxID_OK) {
643 ConnectionParams* cp_edited = dialog.GetParamsFromControls();
644 delete cp->m_optionsPanel;
645 StopAndRemoveCommDriver(cp->GetStrippedDSPort(), cp->GetCommProtocol());
646 int index = FindConnectionIndex(cp);
647 assert(index != -1 && "Cannot look up connection index");
648 TheConnectionParams()[index] = cp_edited;
649 cp_edited->b_IsSetup = false; // Trigger new stream
650 ReloadGrid(m_connections);
651 UpdateDatastreams();
652 }
653 }
654 }
655
657 void HandleDelete(int row) {
658 ConnectionParams* cp = FindRowConnection(row);
659 auto found = std::find(m_connections.begin(), m_connections.end(), cp);
660 if (found != m_connections.end()) {
661 std::stringstream ss;
662 ss << _("Ok to delete connection on ") << (*found)->GetStrippedDSPort();
663 int rcode = OCPNMessageBox(this, ss.str(), _("Delete connection?"),
664 wxOK | wxCANCEL);
665 if (rcode != wxID_OK && rcode != wxID_YES) return;
666 delete (*found)->m_optionsPanel;
667 StopAndRemoveCommDriver((*found)->GetStrippedDSPort(),
668 (*found)->GetCommProtocol());
669 TheConnectionParams().erase(found);
670 if (GetNumberRows() > static_cast<int>(m_connections.size()))
671 DeleteRows(GetNumberRows() - 1);
672 m_on_conn_delete.Notify();
673 }
674 }
675
676 ObsListener conn_change_lstnr;
677 std::vector<std::vector<std::string>> m_tooltips;
678 ConnStates m_conn_states;
679 const std::vector<ConnectionParams*>& m_connections;
680 EventVar& m_on_conn_delete;
681 int m_last_tooltip_cell;
682 StdIcons m_icons;
683 std::vector<BitmapCellRenderer*> m_renderer_status_vector;
684 std::array<int, 7> header_column_widths;
685};
686
688class GeneralPanel : public wxPanel {
689public:
690 explicit GeneralPanel(wxWindow* parent, wxSize max_size)
691 : wxPanel(parent, wxID_ANY) {
692 auto sizer = new wxStaticBoxSizer(wxVERTICAL, this, _("General"));
693 SetSizer(sizer);
694 auto flags = wxSizerFlags().Border();
695 sizer->Add(new UploadOptionsChoice(this), flags);
696 sizer->Add(new PrioritiesBtn(this), flags);
697 wxWindow::SetMaxSize(max_size);
698 }
699
700private:
702 class PrioritiesBtn : public wxButton {
703 public:
704 PrioritiesBtn(wxWindow* parent)
705 : wxButton(parent, wxID_ANY, _("Adjust Nav data priorities...")) {
706 Bind(wxEVT_COMMAND_BUTTON_CLICKED, [&](wxCommandEvent&) {
707 PriorityDlg dialog(this);
708 dialog.ShowModal();
709 });
710 }
711 };
712
714 class UploadOptionsChoice : public wxChoice, public ApplyCancel {
715 public:
716 explicit UploadOptionsChoice(wxWindow* parent) : wxChoice() {
717 wxArrayString wx_choices;
718 for (auto& c : choices) wx_choices.Add(c);
719 Create(parent, wxID_ANY, wxDefaultPosition, wxDefaultSize, wx_choices);
720 UploadOptionsChoice::Cancel();
721 }
722
723 void Cancel() override {
724 if (g_bGarminHostUpload)
725 SetSelection(1);
726 else if (g_GPS_Ident == "FurunoGP3X")
727 SetSelection(2);
728 else
729 SetSelection(0);
730 }
731
732 void Apply() override {
733 switch (GetSelection()) {
734 case 0:
735 g_bGarminHostUpload = false;
736 g_GPS_Ident = "Generic";
737 break;
738 case 1:
739 g_bGarminHostUpload = true;
740 g_GPS_Ident = "Generic";
741 break;
742 case 2:
743 g_bGarminHostUpload = false;
744 g_GPS_Ident = "FurunoGP3X";
745 break;
746 default:
747 assert(false && "Invalid upload case option");
748 }
749 }
750
751 const std::array<wxString, 3> choices = {
752 _("Use generic Nmea 0183 format for uploads"),
753 _("Use Garmin GRMN (Host) mode for uploads"),
754 _("Format uploads for Furuno GP4X")};
755 };
756};
757
759class ShowAdvanced : public wxStaticText {
760public:
761 ShowAdvanced(wxWindow* parent, std::function<void(bool)> on_toggle)
762 : wxStaticText(parent, wxID_ANY, ""),
763 m_on_toggle(on_toggle),
764 m_show_advanced(true) {
765 Toggle();
766 Bind(wxEVT_LEFT_DOWN, [&](wxMouseEvent& ev) { Toggle(); });
767 }
768
769private:
770 wxString m_label = _("Advanced ");
771 std::function<void(bool)> m_on_toggle;
772 bool m_show_advanced;
773
774 void Toggle() {
775 m_show_advanced = !m_show_advanced;
776 m_label = _("Advanced ");
777 m_label += (m_show_advanced ? kUtfArrowDown : kUtfArrowRight);
778 SetLabelText(m_label);
779 m_on_toggle(m_show_advanced);
780 }
781};
782
784class AdvancedPanel : public wxPanel {
785public:
786 explicit AdvancedPanel(wxWindow* parent, wxSize max_size)
787 : wxPanel(parent, wxID_ANY) {
788 auto sizer = new wxStaticBoxSizer(wxVERTICAL, this, "");
789 sizer->Add(new BearingsCheckbox(this), wxSizerFlags().Expand());
790 sizer->Add(new NmeaFilterRow(this), wxSizerFlags().Expand());
791 sizer->Add(new TalkerIdRow(this), wxSizerFlags().Expand());
792 sizer->Add(new NetmaskRow(this), wxSizerFlags().Expand());
793 SetSizer(sizer);
794 wxWindow::SetMaxSize(max_size);
795 }
796
797private:
799 class BearingsCheckbox : public wxCheckBox, public ApplyCancel {
800 public:
801 BearingsCheckbox(wxWindow* parent)
802 : wxCheckBox(parent, wxID_ANY,
803 _("Use magnetic bearing in output sentence APB")) {
804 wxCheckBox::SetValue(g_bMagneticAPB);
805 }
806
807 void Apply() override { g_bMagneticAPB = GetValue(); }
808 void Cancel() override { SetValue(g_bMagneticAPB); }
809 };
810
812 class NmeaFilterRow : public wxPanel, public ApplyCancel {
813 wxCheckBox* checkbox;
814 wxTextCtrl* filter_period;
815
816 public:
817 NmeaFilterRow(wxWindow* parent) : wxPanel(parent) {
818 auto hbox = new wxBoxSizer(wxHORIZONTAL);
819 checkbox = new wxCheckBox(
820 this, wxID_ANY,
821 _("Filter NMEA course and speed data. Filter period: "));
822 checkbox->SetValue(g_bfilter_cogsog);
823 hbox->Add(checkbox, wxSizerFlags().Align(wxALIGN_CENTRE));
824 filter_period =
825 new wxTextCtrl(this, wxID_ANY, "", wxDefaultPosition,
826 wxSize(50, 3 * wxWindow::GetCharWidth()), 0);
827 filter_period->SetValue(std::to_string(g_COGFilterSec));
828 hbox->Add(filter_period, wxSizerFlags().Border());
829 SetSizer(hbox);
830 NmeaFilterRow::Cancel();
831 }
832
833 void Apply() override {
834 std::stringstream ss;
835 ss << filter_period->GetValue();
836 ss >> g_COGFilterSec;
837 g_bfilter_cogsog = checkbox->GetValue();
838 }
839
840 void Cancel() override {
841 std::stringstream ss;
842 ss << g_COGFilterSec;
843 filter_period->SetValue(ss.str());
844 checkbox->SetValue(g_bfilter_cogsog);
845 }
846 };
847
849 class TalkerIdRow : public wxPanel, public ApplyCancel {
850 wxTextCtrl* text_ctrl;
851
852 public:
853 TalkerIdRow(wxWindow* parent) : wxPanel(parent) {
854 auto hbox = new wxBoxSizer(wxHORIZONTAL);
855 hbox->Add(new wxStaticText(this, wxID_ANY, _("NMEA 0183 Talker Id: ")),
856 wxSizerFlags().Align(wxALIGN_CENTRE_VERTICAL).Border());
857 text_ctrl = new wxTextCtrl(this, wxID_ANY, "", wxDefaultPosition,
858 wxSize(50, 3 * wxWindow::GetCharWidth()));
859 text_ctrl->SetToolTip(
860 _("Enter a two-letter talker ID to override the default ID in NMEA "
861 "sentences generated by OpenCPN (e.g., GP, HC, EC). This affects "
862 "only sentences created by OpenCPN, not those forwarded from other "
863 "devices."));
864 text_ctrl->SetValue(g_TalkerIdText);
865 hbox->Add(text_ctrl, wxSizerFlags().Border());
866 SetSizer(hbox);
867 TalkerIdRow::Cancel();
868 }
869
870 void Apply() override { g_TalkerIdText = text_ctrl->GetValue(); }
871 void Cancel() override { text_ctrl->SetValue(g_TalkerIdText); }
872 };
873
875 class NetmaskRow : public wxPanel, public ApplyCancel {
876 public:
877 NetmaskRow(wxWindow* parent)
878 : wxPanel(parent),
879 m_spin_ctrl(new wxSpinCtrl(this, wxID_ANY)),
880 m_text(new wxStaticText(this, wxID_ANY, "")) {
881 m_spin_ctrl->SetRange(8, 32);
882 auto hbox = new wxBoxSizer(wxHORIZONTAL);
883 auto flags = wxSizerFlags().Align(wxALIGN_CENTRE_VERTICAL).Border();
884 hbox->Add(new wxStaticText(this, wxID_ANY, _("Netmask: ")), flags);
885 hbox->Add(m_text, flags);
886 hbox->Add(new wxStaticText(this, wxID_ANY, _("length (bits): ")), flags);
887 hbox->Add(m_spin_ctrl, flags);
888 SetSizer(hbox);
889 NetmaskRow::Cancel();
890
891 Bind(wxEVT_SPINCTRL, [&](wxSpinEvent& ev) {
892 m_text->SetLabel(BitsToDottedMask(m_spin_ctrl->GetValue()));
893 Layout();
894 });
895 }
896
897 void Apply() override { g_netmask_bits = m_spin_ctrl->GetValue(); }
898
899 void Cancel() override {
900 m_spin_ctrl->SetValue(g_netmask_bits);
901 m_text->SetLabel(BitsToDottedMask(m_spin_ctrl->GetValue()));
902 Layout();
903 }
904
905 private:
906 wxSpinCtrl* m_spin_ctrl;
907 wxStaticText* m_text;
908 };
909};
910
912class TopPanel : public wxPanel {
913public:
914 TopPanel(wxWindow* parent, const std::vector<ConnectionParams*>& connections,
915 EventVar& evt_add_connection)
916 : wxPanel(parent, wxID_ANY),
917 m_connections(connections),
918 m_evt_add_connection(evt_add_connection) {
919 auto vbox = new wxBoxSizer(wxVERTICAL);
920 auto conn_grid = new Connections(this, m_connections, m_evt_add_connection);
921 wxSize panel_max_size(conn_grid->GetEstimatedSize());
922 vbox->AddSpacer(wxWindow::GetCharHeight());
923 auto conn_flags = wxSizerFlags().Border();
924 if (IsAndroid()) conn_flags = wxSizerFlags().Border().Expand();
925 vbox->Add(conn_grid, conn_flags);
926 vbox->Add(new AddConnectionButton(this, m_evt_add_connection),
927 wxSizerFlags().Border());
928 vbox->Add(0, wxWindow::GetCharHeight()); // Expanding spacer
929 auto panel_flags =
930 wxSizerFlags().Border(wxLEFT | wxDOWN | wxRIGHT).Expand();
931 vbox->Add(new GeneralPanel(this, panel_max_size), panel_flags);
932
933 auto advanced_panel = new AdvancedPanel(this, panel_max_size);
934 auto on_toggle = [&, advanced_panel, vbox](bool show) {
935 advanced_panel->Show(show);
936 vbox->SetSizeHints(this);
937 vbox->Fit(this);
938 };
939 vbox->Add(new ShowAdvanced(this, on_toggle), panel_flags);
940 vbox->Add(advanced_panel, panel_flags.ReserveSpaceEvenIfHidden());
941
942 SetSizer(vbox);
943 vbox->SetSizeHints(this);
944 vbox->Fit(this);
945 wxWindow::Fit();
946 wxWindow::Show();
947
948 auto on_evt_update_connections = [&, conn_grid](ObservedEvt&) {
949 conn_grid->ReloadGrid(TheConnectionParams());
950 conn_grid->Show(conn_grid->GetNumberRows() > 0);
951 Layout();
952 };
953 m_add_connection_lstnr.Init(m_evt_add_connection,
954 on_evt_update_connections);
955 }
956
957private:
958 const std::vector<ConnectionParams*>& m_connections;
959 EventVar& m_evt_add_connection;
960 ObsListener m_add_connection_lstnr;
961};
962
964class TopScroll : public wxScrolledWindow {
965public:
966 TopScroll(wxWindow* parent, const std::vector<ConnectionParams*>& connections,
967 EventVar& evt_add_connection)
968 : wxScrolledWindow(parent, wxID_ANY, wxDefaultPosition, wxDefaultSize,
969 wxVSCROLL | wxHSCROLL, TopScrollWindowName) {
970 ShowScrollbars(wxSHOW_SB_NEVER, wxSHOW_SB_ALWAYS);
971 auto vbox = new wxBoxSizer(wxVERTICAL);
972 vbox->Add(new TopPanel(this, connections, evt_add_connection),
973 wxSizerFlags(1).Expand());
974 SetSizer(vbox);
975 SetScrollRate(0, 10);
976 if (IsAndroid()) SetScrollRate(1, 1);
977 }
978};
979
982 wxWindow* parent, const std::vector<ConnectionParams*>& connections)
983 : wxPanel(parent, wxID_ANY), m_connections(connections) {
984 auto vbox = new wxBoxSizer(wxVERTICAL);
985 vbox->Add(new TopScroll(this, connections, m_evt_add_connection),
986 wxSizerFlags(1).Expand());
987 SetSizer(vbox);
988 wxWindow::Fit();
989 wxWindow::Show();
990};
991
992void ConnectionsDlg::OnResize(const wxSize& size) {
993 auto w = wxWindow::FindWindowByName(TopScrollWindowName);
994 if (!w) return;
995 w->SetMinSize(size);
996 Fit();
997}
998
999void ConnectionsDlg::DoApply(wxWindow* root) {
1000 for (wxWindow* child : root->GetChildren()) {
1001 if (auto widget = dynamic_cast<ApplyCancel*>(child)) widget->Apply();
1002 DoApply(child);
1003 }
1004}
1005
1006void ConnectionsDlg::DoCancel(wxWindow* root) {
1007 for (wxWindow* child : root->GetChildren()) {
1008 if (auto widget = dynamic_cast<ApplyCancel*>(child)) widget->Cancel();
1009 DoCancel(child);
1010 }
1011}
1012
1013void ConnectionsDlg::ApplySettings() { DoApply(this); }
1014
1015void ConnectionsDlg::CancelSettings() { DoCancel(this); }
The "Add new connection" button.
Indeed: The "Advanced" panel.
Interface implemented by widgets supporting Apply and Cancel.
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.
std::sort support: Compare two ConnectionParams w r t given column
Filter reading driver driver_stats status reports from CommDriverRegistry transforming these to a str...
Definition conn_states.h:65
EventVar evt_conn_status_change
Notified with a shared_ptr<ConnData> when the state is changed.
Definition conn_states.h:71
Dialog for editing connection parameters.
std::string GetKey() const
Return string unique for each instance.
void ApplySettings()
Make dialog's settings the active ones, usually by updating globals.
void OnResize(const wxSize &size)
Resize the connections tab.
ConnectionsDlg(wxWindow *parent, const std::vector< ConnectionParams * > &connections)
Create a new ConnectionsDlg instance.
void CancelSettings()
Restore dialog settings from currently used values, usually globals.
std::sort support: Compare two ConnectionParams w r t state.
Grid with existing connections: type, port, status, etc.
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.
Generic event handling between MVC Model and Controller based on a shared EventVar variable.
const void Notify()
Notify all listeners, no data supplied.
Indeed: the General panel.
Provides platform-specific support utilities for OpenCPN.
double GetDisplaySizeMM()
Get the width of the screen in millimeters.
Define an action to be performed when a KeyProvider is notified.
Definition observable.h:228
void Init(const KeyProvider &kp, std::function< void(ObservedEvt &ev)> action)
Initiate an object yet not listening.
Definition observable.h:255
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.
Runtime connection/driver state definitions.
Dialog and support code for editing a connection.
The ConnectionsDlg class.
General purpose GUI support.
Class NotificationManager.
Driver statistics report.