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