OpenCPN Partial API docs
Loading...
Searching...
No Matches
priority_gui.cpp
Go to the documentation of this file.
1/***************************************************************************
2 * Copyright (C) 2022 by David S. Register *
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#ifdef __ANDROID__
25#include "qdebug.h"
26#include <QtWidgets/QScroller>
27#include "androidUTIL.h"
28#endif
29
30#include <wx/wxprec.h>
31
32#ifndef WX_PRECOMP
33#include <wx/wx.h>
34#endif
35
36#include <wx/app.h>
37#include <wx/arrstr.h>
38#include <wx/dcscreen.h>
39#include <wx/sizer.h>
40#include <wx/tokenzr.h>
41
42#include "priority_gui.h"
43
44#include "model/comm_bridge.h"
45#include "ocpn_app.h"
46#include "ocpn_frame.h"
47#include "ocpn_plugin.h"
48
49class PriorityEntry : public wxTreeItemData {
50public:
51 PriorityEntry(int category, int index) {
52 m_category = category, m_index = index;
53 }
54 ~PriorityEntry() {};
55
56 int m_category, m_index;
57};
58
59PriorityDlg::PriorityDlg(wxWindow* parent)
60 : wxDialog(parent, wxID_ANY, _("Adjust Comm Priorities"), wxDefaultPosition,
61 wxSize(480, 420), wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER) {
62 m_selIndex = 0;
63 m_selmap_index = 0;
64
65 wxBoxSizer* mainSizer = new wxBoxSizer(wxVERTICAL);
66 SetSizer(mainSizer);
67
68 wxBoxSizer* secondSizer = new wxBoxSizer(wxHORIZONTAL);
69 mainSizer->Add(secondSizer, 1, wxEXPAND, 5);
70
71 wxStaticBox* pclbBox = new wxStaticBox(this, wxID_ANY, _("Priority List"));
72 wxStaticBoxSizer* stcSizer = new wxStaticBoxSizer(pclbBox, wxVERTICAL);
73 secondSizer->Add(stcSizer, 1, wxALL | wxEXPAND, 5);
74
75 m_prioTree = new wxTreeCtrl(this, wxID_ANY, wxDefaultPosition, wxDefaultSize);
76 stcSizer->Add(m_prioTree, 1, wxALL | wxEXPAND, 5);
77 wxFont* pF = OCPNGetFont(_("Dialog"));
78 m_pF = pF;
79 m_prioTree->SetFont(*pF);
80#ifdef __ANDROID__
81 m_prioTree->GetHandle()->setStyleSheet(
82 getWideScrollBarsStyleSheet() /*getScrollBarsStyleSheet()*/);
83 QScroller::ungrabGesture(m_prioTree->GetHandle());
84#endif
85
86 wxBoxSizer* btnEntrySizer = new wxBoxSizer(wxVERTICAL);
87 secondSizer->Add(btnEntrySizer, 0, wxALL | wxEXPAND, 5);
88 btnMoveUp = new wxButton(this, wxID_ANY, _("Move Up"));
89 btnMoveDown = new wxButton(this, wxID_ANY, _("Move Down"));
90 btnMoveUp->Disable();
91 btnMoveDown->Disable();
92
93 btnEntrySizer->Add(btnMoveUp, 0, wxALL, 5);
94 btnEntrySizer->Add(btnMoveDown, 0, wxALL, 5);
95
96 btnEntrySizer->AddSpacer(15);
97
98 btnRefresh = new wxButton(this, wxID_ANY, _("Refresh"));
99 btnClear = new wxButton(this, wxID_ANY, _("Clear All"));
100
101 btnEntrySizer->Add(btnRefresh, 0, wxALL, 5);
102 btnEntrySizer->Add(btnClear, 0, wxALL, 5);
103
104 wxStdDialogButtonSizer* btnSizer = new wxStdDialogButtonSizer();
105 wxButton* btnOK = new wxButton(this, wxID_OK);
106 wxButton* btnCancel = new wxButton(this, wxID_CANCEL, _("Cancel"));
107 btnSizer->AddButton(btnOK);
108 btnSizer->AddButton(btnCancel);
109 btnSizer->Realize();
110 mainSizer->Add(btnSizer, 0, wxALL | wxEXPAND, 5);
111
112 // Connect Events
113 btnMoveUp->Connect(wxEVT_COMMAND_BUTTON_CLICKED,
114 wxCommandEventHandler(PriorityDlg::OnMoveUpClick), NULL,
115 this);
116 btnMoveDown->Connect(wxEVT_COMMAND_BUTTON_CLICKED,
117 wxCommandEventHandler(PriorityDlg::OnMoveDownClick),
118 NULL, this);
119
120 btnRefresh->Connect(wxEVT_COMMAND_BUTTON_CLICKED,
121 wxCommandEventHandler(PriorityDlg::OnRefreshClick), NULL,
122 this);
123
124 btnClear->Connect(wxEVT_COMMAND_BUTTON_CLICKED,
125 wxCommandEventHandler(PriorityDlg::OnClearClick), NULL,
126 this);
127
128 m_prioTree->Connect(wxEVT_TREE_SEL_CHANGED,
129 wxCommandEventHandler(PriorityDlg::OnItemSelected), NULL,
130 this);
131
132 // Get the current status
133 MyApp& app = wxGetApp();
134 m_map = app.m_comm_bridge.GetPriorityMaps();
135
136 Populate();
137
138 int n_lines = wxMax(m_prioTree->GetCount(), 15);
139
140 wxScreenDC dc;
141 int char_width, char_height;
142 dc.GetTextExtent("W", &char_width, &char_height, NULL, NULL, m_pF);
143
144 int stcw = wxMax(m_maxStringLength * 15 / 10, 15 * char_width);
145 wxWindow* top_frame = wxTheApp->GetTopWindow();
146 wxSize min_size = wxSize(
147 stcw, wxMin(top_frame->GetSize().y * 2 / 4, n_lines * GetCharHeight()));
148
149 stcSizer->SetMinSize(min_size);
150
151 Layout();
152#ifdef __ANDROID__
153 wxSize max_size =
154 wxSize(top_frame->GetSize().x, top_frame->GetSize().y * 7 / 10);
155 SetSize(max_size);
156 Centre();
157#else
158 Fit();
159 Centre();
160#endif
161
162#ifdef __ANDROID__
163 androidDisableRotation();
164#endif
165}
166
167PriorityDlg::~PriorityDlg() {
168#ifdef __ANDROID__
169 androidEnableRotation();
170#endif
171}
172
173void PriorityDlg::AddLeaves(const std::vector<std::string>& map_list,
174 size_t map_index, std::string map_name,
175 wxTreeItemId leaf_parent) {
176 if (map_list.size() < (size_t)map_index) return;
177
178 // Get the current Priority container for this branch
179 MyApp& app = wxGetApp();
180 PriorityContainer pc = app.m_comm_bridge.GetPriorityContainer(map_name);
181
182 wxString priority_string(map_list[map_index].c_str());
183 wxStringTokenizer tk(priority_string, "|");
184 size_t index = 0;
185 while (tk.HasMoreTokens()) {
186 wxString item_string = tk.GetNextToken();
187
188 wxScreenDC dc;
189 int char_width, char_height;
190 dc.GetTextExtent(item_string, &char_width, &char_height, NULL, NULL, m_pF);
191
192 // Record the maximum display string length, for use in dialog sizing.
193 if (char_width > m_maxStringLength) {
194 m_maxStringLength = char_width;
195 m_max_string = item_string;
196 }
197
198 PriorityEntry* pe = new PriorityEntry(map_index, index);
199 wxTreeItemId id_tk =
200 m_prioTree->AppendItem(leaf_parent, item_string, -1, -1, pe);
201
202 // Set bold text on item currently active (usually 0)
203 if ((size_t)(pc.active_priority) == index) m_prioTree->SetItemBold(id_tk);
204
205 index++;
206 }
207}
208
209void PriorityDlg::Populate() {
210 m_prioTree->Unselect();
211 m_prioTree->DeleteAllItems();
212 m_maxStringLength = 15; // default width calculation
213
214 // wxTreeItemId* rootData = new wxDirItemData("Dummy", "Dummy",
215 // TRUE);
216 wxTreeItemId m_rootId = m_prioTree->AddRoot(_("Priorities"), -1, -1, NULL);
217 m_prioTree->SetItemHasChildren(m_rootId);
218
219 wxTreeItemId id_position =
220 m_prioTree->AppendItem(m_rootId, _("Position"), -1, -1, NULL);
221 m_prioTree->SetItemHasChildren(id_position);
222 AddLeaves(m_map, 0, "position", id_position);
223
224 wxTreeItemId id_velocity =
225 m_prioTree->AppendItem(m_rootId, _("Speed/Course"), -1, -1, NULL);
226 m_prioTree->SetItemHasChildren(id_velocity);
227 AddLeaves(m_map, 1, "velocity", id_velocity);
228
229 wxTreeItemId id_heading =
230 m_prioTree->AppendItem(m_rootId, _("Heading"), -1, -1, NULL);
231 m_prioTree->SetItemHasChildren(id_heading);
232 AddLeaves(m_map, 2, "heading", id_heading);
233
234 wxTreeItemId id_magvar =
235 m_prioTree->AppendItem(m_rootId, _("Mag Variation"), -1, -1, NULL);
236 m_prioTree->SetItemHasChildren(id_magvar);
237 AddLeaves(m_map, 3, "variation", id_magvar);
238
239 wxTreeItemId id_sats =
240 m_prioTree->AppendItem(m_rootId, _("Satellites"), -1, -1, NULL);
241 m_prioTree->SetItemHasChildren(id_sats);
242 AddLeaves(m_map, 4, "satellites", id_sats);
243
244 m_prioTree->ExpandAll();
245
246 // Restore selection
247 wxTreeItemId rootID = m_prioTree->GetRootItem();
248 wxTreeItemIdValue cookie;
249 int i = m_selmap_index;
250 wxTreeItemId cid = m_prioTree->GetFirstChild(rootID, cookie);
251
252 while ((i > 0) && cid.IsOk()) {
253 cid = m_prioTree->GetNextChild(rootID, cookie);
254 i--;
255 }
256
257 wxTreeItemId ccid = m_prioTree->GetFirstChild(cid, cookie);
258
259 int j = m_selIndex;
260 while ((j > 0) && ccid.IsOk()) {
261 ccid = m_prioTree->GetNextChild(cid, cookie);
262 j--;
263 }
264
265 if (ccid.IsOk()) m_prioTree->SelectItem(ccid);
266}
267
268void PriorityDlg::OnItemSelected(wxCommandEvent& event) {
269 btnMoveUp->Disable();
270 btnMoveDown->Disable();
271
272 wxTreeItemId id = m_prioTree->GetSelection();
273 PriorityEntry* pe = (PriorityEntry*)m_prioTree->GetItemData(id);
274 if (!pe) return;
275
276 m_selIndex = pe->m_index;
277 m_selmap_index = pe->m_category;
278
279 if (pe->m_index > 0) {
280 btnMoveUp->Enable();
281 }
282
283 wxTreeItemId id_parent = m_prioTree->GetItemParent(id);
284
285 // count siblings
286 int n_sibs = 0;
287 wxTreeItemIdValue cookie;
288 wxTreeItemId ch = m_prioTree->GetFirstChild(id_parent, cookie);
289 while (ch.IsOk()) {
290 n_sibs++;
291 ch = m_prioTree->GetNextChild(id_parent, cookie);
292 }
293
294 if (pe->m_index < n_sibs - 1) btnMoveDown->Enable();
295}
296
297void PriorityDlg::OnMoveUpClick(wxCommandEvent& event) {
298 ProcessMove(m_prioTree->GetSelection(), -1);
299}
300
301void PriorityDlg::OnMoveDownClick(wxCommandEvent& event) {
302 ProcessMove(m_prioTree->GetSelection(), 1);
303}
304
305void PriorityDlg::ProcessMove(wxTreeItemId id, int dir) {
306 // Get the extra data
307 PriorityEntry* pe = (PriorityEntry*)m_prioTree->GetItemData(id);
308 if (!pe) return;
309 if (pe->m_category > 4) return;
310
311 // Get the selected category string from the map
312 wxString priority_string = wxString(m_map[pe->m_category].c_str());
313
314 // Build an array
315 wxString prio_array[16]; // enough, plus
316
317 wxStringTokenizer tk(priority_string, "|");
318 int index = 0;
319 while (tk.HasMoreTokens() && index < 16) {
320 prio_array[index] = tk.GetNextToken();
321 index++;
322 }
323 int max_index = index;
324
325 // perform the action
326 if (dir == -1) { // Move UP
327 if (pe->m_index > 0) {
328 // swap entries in array
329 wxString s_above = prio_array[pe->m_index - 1];
330 wxString s_move = prio_array[pe->m_index];
331 prio_array[pe->m_index - 1] = s_move;
332 prio_array[pe->m_index] = s_above;
333 m_selIndex--;
334 }
335 } else { // Move DOWN
336 if (pe->m_index < max_index) {
337 // swap entries in array
338 wxString s_below = prio_array[pe->m_index + 1];
339 wxString s_move = prio_array[pe->m_index];
340 prio_array[pe->m_index + 1] = s_move;
341 prio_array[pe->m_index] = s_below;
342 m_selIndex++;
343 }
344 }
345
346 // create the new string
347 wxString prio_mod;
348 for (int i = 0; i < 16; i++) {
349 if (prio_array[i].Length()) {
350 prio_mod += prio_array[i];
351 prio_mod += wxString("|");
352 }
353 }
354
355 // update the string in the map
356 std::string s_upd(prio_mod.c_str());
357 m_map[pe->m_category] = s_upd;
358
359 // Auto-adjust Sat and COG/SOG priorities if POS has been moved up/down
360 if (pe->m_category == 0) {
361 AdjustSatPriority();
362 AdjustCOGSOGPriority();
363 }
364
365 // Update the priority mechanism
366 MyApp& app = wxGetApp();
367 app.m_comm_bridge.UpdateAndApplyMaps(m_map);
368
369 // And reload the tree GUI
370 m_map = app.m_comm_bridge.GetPriorityMaps();
371 Populate();
372}
373
374void PriorityDlg::OnRefreshClick(wxCommandEvent& event) {
375 // Reload the tree GUI
376 MyApp& app = wxGetApp();
377 m_map = app.m_comm_bridge.GetPriorityMaps();
378 Populate();
379}
380
381void PriorityDlg::OnClearClick(wxCommandEvent& event) {
382 m_map[0].clear();
383 m_map[1].clear();
384 m_map[2].clear();
385 m_map[3].clear();
386 m_map[4].clear();
387
388 m_selmap_index = m_selIndex = 0;
389
390 // Update the priority mechanism
391 MyApp& app = wxGetApp();
392 app.m_comm_bridge.UpdateAndApplyMaps(m_map);
393
394 // And reload the tree GUI
395 m_map = app.m_comm_bridge.GetPriorityMaps();
396 Populate();
397}
398
399void PriorityDlg::AdjustSatPriority() {
400 // Get an array of available sat sources
401 std::string sat_prio = m_map[4];
402 wxArrayString sat_sources;
403 wxString sat_priority_string(sat_prio.c_str());
404 wxStringTokenizer tks(sat_priority_string, "|");
405 while (tks.HasMoreTokens()) {
406 wxString item_string = tks.GetNextToken();
407 sat_sources.Add(item_string);
408 }
409
410 // Step thru the POS priority map
411 std::string pos_prio = m_map[0];
412 wxString pos_priority_string(pos_prio.c_str());
413 wxStringTokenizer tk(pos_priority_string, "|");
414 wxArrayString new_sat_prio;
415 while (tk.HasMoreTokens()) {
416 wxString item_string = tk.GetNextToken();
417 wxString pos_channel = item_string.BeforeFirst(';');
418
419 // search the sat sources array for a match
420 // if found, add to proposed new priority array
421 for (size_t i = 0; i < sat_sources.GetCount(); i++) {
422 if (pos_channel.IsSameAs(sat_sources[i].BeforeFirst(';'))) {
423 new_sat_prio.Add(sat_sources[i]);
424 // Mark this source as "used"
425 sat_sources[i] = "USED";
426 break;
427 } else { // no match, what to do? //FIXME (dave)
428 int yyp = 4;
429 }
430 }
431 }
432 // Create a new sat priority string from new_sat_prio array
433 wxString proposed_sat_prio;
434 for (size_t i = 0; i < new_sat_prio.GetCount(); i++) {
435 proposed_sat_prio += new_sat_prio[i];
436 proposed_sat_prio += wxString("|");
437 }
438
439 // Update the maps with the new sat priority string
440 m_map[4] = proposed_sat_prio.ToStdString();
441}
442
443void PriorityDlg::AdjustCOGSOGPriority() {
444 // Get an array of available COG/SOG sources
445 std::string cogsog_prio = m_map[1];
446 wxArrayString cogsog_sources;
447 wxString cogsog_priority_string(cogsog_prio.c_str());
448 wxStringTokenizer tks(cogsog_priority_string, "|");
449 while (tks.HasMoreTokens()) {
450 wxString item_string = tks.GetNextToken();
451 cogsog_sources.Add(item_string);
452 }
453
454 // Step thru the POS priority map
455 std::string pos_prio = m_map[0];
456 wxString pos_priority_string(pos_prio.c_str());
457 wxStringTokenizer tk(pos_priority_string, "|");
458 wxArrayString new_cogsog_prio;
459 while (tk.HasMoreTokens()) {
460 wxString item_string = tk.GetNextToken();
461 wxString pos_channel = item_string.BeforeFirst(';');
462
463 // search the cogsog sources array for a match
464 // if found, add to proposed new priority array
465 for (size_t i = 0; i < cogsog_sources.GetCount(); i++) {
466 if (pos_channel.IsSameAs(cogsog_sources[i].BeforeFirst(';'))) {
467 new_cogsog_prio.Add(cogsog_sources[i]);
468 // Mark this source as "used"
469 cogsog_sources[i] = "USED";
470 break;
471 } else { // no match, what to do? //FIXME (dave)
472 int yyp = 4;
473 }
474 }
475 }
476 // Create a new cog/sog priority string from new_cogsog_prio array
477 wxString proposed_cogsog_prio;
478 for (size_t i = 0; i < new_cogsog_prio.GetCount(); i++) {
479 proposed_cogsog_prio += new_cogsog_prio[i];
480 proposed_cogsog_prio += wxString("|");
481 }
482
483 // Update the maps with the new cog/sog priority string
484 m_map[1] = proposed_cogsog_prio.ToStdString();
485}
CommBridge class and helpers.
OpenCPN main program.
OpenCPN top window.
PlugIn Object Definition/API.
wxFont * OCPNGetFont(wxString TextElement, int default_size)
Gets a font for UI elements.
Input priorities management dialog.