OpenCPN Partial API docs
ais_info_gui.cpp
1 /***************************************************************************
2  *
3  * Project: OpenCPN
4  * Purpose: AIS info GUI parts.
5  * Author: David Register
6  *
7  ***************************************************************************
8  * Copyright (C) 2010 by David S. Register *
9  * Copyright (C) 2022 Alec Leamas *
10  * *
11  * This program is free software; you can redistribute it and/or modify *
12  * it under the terms of the GNU General Public License as published by *
13  * the Free Software Foundation; either version 2 of the License, or *
14  * (at your option) any later version. *
15  * *
16  * This program is distributed in the hope that it will be useful, *
17  * but WITHOUT ANY WARRANTY; without even the implied warranty of *
18  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
19  * GNU General Public License for more details. *
20  * *
21  * You should have received a copy of the GNU General Public License *
22  * along with this program; if not, write to the *
23  * Free Software Foundation, Inc., *
24  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. *
25  **************************************************************************/
26 
27 #include <memory>
28 
29 // For compilers that support precompilation, includes "wx.h".
30 #include <wx/wxprec.h>
31 
32 #ifndef WX_PRECOMP
33 #include <wx/wx.h>
34 #endif // precompiled headers
35 
36 
37 #include <wx/datetime.h>
38 #include <wx/event.h>
39 #include <wx/string.h>
40 
41 #include "model/ais_decoder.h"
42 #include "model/ais_state_vars.h"
43 #include "model/ais_target_data.h"
44 #include "model/route_point.h"
45 
46 #include "ais_info_gui.h"
47 #include "AISTargetAlertDialog.h"
48 #include "chcanv.h"
49 #include "navutil.h"
50 #include "ocpn_frame.h"
51 #include "OCPNPlatform.h"
52 #include "routemanagerdialog.h"
53 #include "SoundFactory.h"
54 #include "undo.h"
55 
56 wxDEFINE_EVENT(EVT_AIS_DEL_TRACK, wxCommandEvent);
57 wxDEFINE_EVENT(EVT_AIS_INFO, ObservedEvt);
58 wxDEFINE_EVENT(EVT_AIS_NEW_TRACK, wxCommandEvent);
59 wxDEFINE_EVENT(EVT_AIS_TOUCH, wxCommandEvent);
60 wxDEFINE_EVENT(EVT_AIS_WP, wxCommandEvent);
61 wxDEFINE_EVENT(SOUND_PLAYED_EVTYPE, wxCommandEvent);
62 
63 extern ArrayOfMmsiProperties g_MMSI_Props_Array;
64 extern bool g_bquiting;
65 extern int g_iSoundDeviceIndex;
66 extern OCPNPlatform *g_Platform;
67 extern Route *pAISMOBRoute;
68 extern wxString g_CmdSoundString;
69 extern MyConfig* pConfig;
70 extern RouteManagerDialog *pRouteManagerDialog;
71 extern MyFrame* gFrame;
72 extern AisInfoGui *g_pAISGUI;
73 
74 static void onSoundFinished(void *ptr) {
75  if (!g_bquiting) {
76  wxCommandEvent ev(SOUND_PLAYED_EVTYPE);
77  wxPostEvent(g_pAISGUI, ev); // FIXME(leamas): Review sound handling.
78  }
79 }
80 
81 static void OnNewAisWaypoint(RoutePoint* pWP) {
82  pConfig->AddNewWayPoint(pWP, -1); // , -1 use auto next num
83  if (pRouteManagerDialog && pRouteManagerDialog->IsShown())
84  pRouteManagerDialog->UpdateWptListCtrl();
85  if (gFrame->GetPrimaryCanvas()) {
86  gFrame->GetPrimaryCanvas()->undo->BeforeUndoableAction(
87  Undo_CreateWaypoint, pWP, Undo_HasParent, NULL);
88  gFrame->GetPrimaryCanvas()->undo->AfterUndoableAction(NULL);
89  gFrame->RefreshAllCanvas(false);
90  gFrame->InvalidateAllGL();
91  }
92 }
93 
94 const static char* const kDeleteTrackPrompt =
95 _(R"(
96 This AIS target has Persistent Tracking selected by MMSI properties
97 A Persistent track recording will therefore be restarted for this target.
98 
99 Do you instead want to stop Persistent Tracking for this target?
100 )");
101 
102 
103 static void OnDeleteTrack(MmsiProperties* props) {
104  if (wxID_NO == OCPNMessageBox(NULL, kDeleteTrackPrompt, _("OpenCPN Info"),
105  wxYES_NO | wxCENTER, 60))
106  {
107  props->m_bPersistentTrack = true;
108  }
109 }
110 
111 
112 AisInfoGui::AisInfoGui() {
113  ais_info_listener.Listen(g_pAIS->info_update, this, EVT_AIS_INFO);
114 
115  Bind(EVT_AIS_INFO, [&](ObservedEvt &ev) {
116  auto ptr = ev.GetSharedPtr();
117  auto palert_target = std::static_pointer_cast<const AisTargetData>(ptr);
118  ShowAisInfo(palert_target); }
119  );
120 
121  ais_touch_listener.Listen(g_pAIS->touch_state, this, EVT_AIS_TOUCH);
122  Bind(EVT_AIS_TOUCH, [&](wxCommandEvent ev) { gFrame->TouchAISActive(); });
123 
124  ais_wp_listener.Listen(g_pAIS->new_ais_wp, this, EVT_AIS_WP);
125  Bind(EVT_AIS_WP, [&](wxCommandEvent ev) {
126  auto pWP = static_cast<RoutePoint*>(ev.GetClientData());
127  OnNewAisWaypoint(pWP); });
128 
129  ais_new_track_listener.Listen(g_pAIS->new_ais_wp, this,
130  EVT_AIS_NEW_TRACK);
131  Bind(EVT_AIS_NEW_TRACK, [&](wxCommandEvent ev) {
132  auto t = static_cast<Track*>(ev.GetClientData());
133  pConfig->AddNewTrack(t); });
134 
135  ais_del_track_listener.Listen(g_pAIS->new_ais_wp, this,
136  EVT_AIS_DEL_TRACK);
137  Bind(EVT_AIS_DEL_TRACK, [&](wxCommandEvent ev) {
138  auto t = static_cast< MmsiProperties*>(ev.GetClientData());
139  OnDeleteTrack(t); });
140 
141  Bind(SOUND_PLAYED_EVTYPE, [&](wxCommandEvent ev) {
142  OnSoundFinishedAISAudio(ev); });
143 
144  m_AIS_Sound = 0;
145  m_bAIS_Audio_Alert_On = false;
146  m_bAIS_AlertPlaying = false;
147 
148 }
149 
150 void AisInfoGui::OnSoundFinishedAISAudio(wxCommandEvent &event) {
151  // By clearing this flag the main event loop will trigger repeated
152  // sounds for as long as the alert condition remains.
153 
154  // Unload/reset OcpnSound object.
155  m_AIS_Sound->UnLoad();
156 
157  m_bAIS_AlertPlaying = false;
158 }
159 
160 void AisInfoGui::ShowAisInfo(std::shared_ptr<const AisTargetData> palert_target) {
161  if (!palert_target) return;
162 
163  int audioType = AISAUDIO_NONE;
164 
165  switch (palert_target->Class){
166  case AIS_DSC:
167  audioType = AISAUDIO_DSC;
168  break;
169  case AIS_SART:
170  audioType = AISAUDIO_SART;
171  break;
172  default:
173  audioType = AISAUDIO_CPA;
174  break;
175  }
176 
177  // If no alert dialog shown yet...
178  if (!g_pais_alert_dialog_active) {
179  bool b_jumpto = (palert_target->Class == AIS_SART) ||
180  (palert_target->Class == AIS_DSC);
181  bool b_createWP = palert_target->Class == AIS_DSC;
182  bool b_ack = palert_target->Class != AIS_DSC;
183 
184  // Show the Alert dialog
185 
186  // See FS# 968/998
187  // If alert occurs while OCPN is iconized to taskbar, then clicking
188  // the taskbar icon only brings up the Alert dialog, and not the
189  // entire application. This is an OS specific behavior, not seen on
190  // linux or Mac. This patch will allow the audio alert to occur, and
191  // the visual alert will pop up soon after the user selects the OCPN
192  // icon from the taskbar. (on the next timer tick, probably)
193 
194 #ifndef __ANDROID__
195  if (gFrame->IsIconized() || !gFrame->IsActive())
196  gFrame->RequestUserAttention();
197 #endif
198 
199  if (!gFrame->IsIconized()) {
200  AISTargetAlertDialog *pAISAlertDialog = new AISTargetAlertDialog();
201  pAISAlertDialog->Create(palert_target->MMSI, gFrame, g_pAIS,
202  b_jumpto, b_createWP, b_ack, -1,
203  _("AIS Alert"));
204 
205  g_pais_alert_dialog_active = pAISAlertDialog;
206 
207  wxTimeSpan alertLifeTime(0, 1, 0,
208  0); // Alert default lifetime, 1 minute.
209  auto alert_dlg_active =
210  dynamic_cast<AISTargetAlertDialog*>(g_pais_alert_dialog_active);
211  alert_dlg_active->dtAlertExpireTime = wxDateTime::Now() + alertLifeTime;
212  g_Platform->PositionAISAlert(pAISAlertDialog);
213 
214  pAISAlertDialog->Show(); // Show modeless, so it stays on the screen
215  }
216 
217  // Audio alert if requested
218  m_bAIS_Audio_Alert_On = true; // always on when alert is first shown
219  }
220 
221 
222  // The AIS Alert dialog is already shown. If the dialog MMSI number is
223  // still alerted, update the dialog otherwise, destroy the dialog
224  else {
225  // Find the target with shortest CPA, ignoring DSC and SART targets
226  double tcpa_min = 1e6; // really long
227  AisTargetData *palert_target_lowestcpa = NULL;
228  const auto& current_targets = g_pAIS->GetTargetList();
229  for (auto& it : current_targets) {
230  auto td = it.second;
231  if (td) {
232  if ((td->Class != AIS_SART) && (td->Class != AIS_DSC)) {
233  if (g_bAIS_CPA_Alert && td->b_active) {
234  if ((AIS_ALERT_SET == td->n_alert_state) && !td->b_in_ack_timeout) {
235  if (td->TCPA < tcpa_min) {
236  tcpa_min = td->TCPA;
237  palert_target_lowestcpa = td.get();
238  }
239  }
240  }
241  }
242  }
243  }
244 
245  // Get the target currently displayed
246  auto alert_dlg_active =
247  dynamic_cast<AISTargetAlertDialog*>(g_pais_alert_dialog_active);
248  palert_target = g_pAIS->Get_Target_Data_From_MMSI(
249  alert_dlg_active->Get_Dialog_MMSI());
250 
251  // If the currently displayed target is not alerted, it must be in "expiry
252  // delay" We should cancel that alert display now, and pick up the more
253  // critical CPA target on the next timer tick
254  if (palert_target) {
255  if (AIS_NO_ALERT == palert_target->n_alert_state) {
256  if (palert_target_lowestcpa) {
257  palert_target = NULL;
258  }
259  }
260  }
261 
262  if (palert_target) {
263  wxDateTime now = wxDateTime::Now();
264  if (((AIS_ALERT_SET == palert_target->n_alert_state) &&
265  !palert_target->b_in_ack_timeout) ||
266  (palert_target->Class == AIS_SART) ) {
267  alert_dlg_active->UpdateText();
268  // Retrigger the alert expiry timeout if alerted now
269  wxTimeSpan alertLifeTime(0, 1, 0,
270  0); // Alert default lifetime, 1 minute.
271  alert_dlg_active->dtAlertExpireTime = wxDateTime::Now() + alertLifeTime;
272  }
273  // In "expiry delay"?
274  else if (!palert_target->b_in_ack_timeout &&
275  (now.IsEarlierThan(alert_dlg_active->dtAlertExpireTime))) {
276  alert_dlg_active->UpdateText();
277  } else {
278  alert_dlg_active->Close();
279  m_bAIS_Audio_Alert_On = false;
280  }
281 
282  if (true == palert_target->b_suppress_audio)
283  m_bAIS_Audio_Alert_On = false;
284  else
285  m_bAIS_Audio_Alert_On = true;
286  } else { // this should not happen, however...
287  alert_dlg_active->Close();
288  m_bAIS_Audio_Alert_On = false;
289  }
290  }
291 
292  // At this point, the audio flag is set
293  // Honor the global flag
294  if (!g_bAIS_CPA_Alert_Audio) m_bAIS_Audio_Alert_On = false;
295 
296  if (m_bAIS_Audio_Alert_On) {
297  if (!m_AIS_Sound) {
298  m_AIS_Sound = SoundFactory(/*g_CmdSoundString.mb_str(wxConvUTF8)*/);
299  }
300  if (!AIS_AlertPlaying()) {
301  m_bAIS_AlertPlaying = true;
302  wxString soundFile;
303  switch (audioType) {
304  case AISAUDIO_DSC:
305  if (g_bAIS_DSC_Alert_Audio) soundFile = g_DSC_sound_file;
306  break;
307  case AISAUDIO_SART:
308  if (g_bAIS_SART_Alert_Audio) soundFile = g_SART_sound_file;
309  break;
310  case AISAUDIO_CPA:
311  default:
312  if (g_bAIS_GCPA_Alert_Audio) soundFile = g_AIS_sound_file;
313  break;
314  }
315 
316  m_AIS_Sound->Load(soundFile, g_iSoundDeviceIndex);
317  if (m_AIS_Sound->IsOk()) {
318  m_AIS_Sound->SetFinishedCallback(onSoundFinished, this);
319  if (!m_AIS_Sound->Play()){
320  delete m_AIS_Sound;
321  m_AIS_Sound = 0;
322  m_bAIS_AlertPlaying = false;
323  }
324  } else {
325  delete m_AIS_Sound;
326  m_AIS_Sound = 0;
327  m_bAIS_AlertPlaying = false;
328  }
329  }
330  }
331  // If a SART Alert is active, check to see if the MMSI has special properties
332  // set indicating that this Alert is a MOB for THIS ship.
333  if (palert_target && (palert_target->Class == AIS_SART)) {
334  for (unsigned int i = 0; i < g_MMSI_Props_Array.GetCount(); i++) {
335  if (palert_target->MMSI == g_MMSI_Props_Array[i]->MMSI) {
336  if (pAISMOBRoute)
337  gFrame->UpdateAISMOBRoute(palert_target.get());
338  else
339  gFrame->ActivateAISMOBRoute(palert_target.get());
340  break;
341  }
342  }
343  }
344 }
Global state for AIS decoder.
EventVar new_ais_wp
Notified when new AIS wp is created.
Definition: ais_decoder.h:133
EventVar info_update
Notified when AIS user dialogs should update.
Definition: ais_decoder.h:121
EventVar touch_state
Notified when gFrame->TouchAISActive() should be invoked.
Definition: ais_decoder.h:130
void Listen(const std::string &key, wxEvtHandler *listener, wxEventType evt)
Set object to send wxEventType ev to listener on changes in key.
Definition: observable.cpp:98
Adds a std::shared<void> element to wxCommandEvent.
Definition: ocpn_plugin.h:1652
Definition: route.h:75
Definition: track.h:78
wxDEFINE_EVENT(REST_IO_EVT, ObservedEvt)
Event from IO thread to main.