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