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