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