OpenCPN Partial API docs
Loading...
Searching...
No Matches
ais_info_gui.cpp
Go to the documentation of this file.
1/**************************************************************************
2 * Copyright (C) 2010 by David S. Register *
3 * Copyright (C) 2022 Alec Leamas *
4 * *
5 * This program is free software; you can redistribute it and/or modify *
6 * it under the terms of the GNU General Public License as published by *
7 * the Free Software Foundation; either version 2 of the License, or *
8 * (at your option) any later version. *
9 * *
10 * This program is distributed in the hope that it will be useful, *
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of *
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
13 * GNU General Public License for more details. *
14 * *
15 * You should have received a copy of the GNU General Public License *
16 * along with this program; if not, see <https://www.gnu.org/licenses/>. *
17 **************************************************************************/
18
25#include <memory>
26
27// For compilers that support precompilation, includes "wx.h".
28#include <wx/wxprec.h>
29
30#ifndef WX_PRECOMP
31#include <wx/wx.h>
32#endif // precompiled headers
33
34#include <wx/datetime.h>
35#include <wx/event.h>
36#include <wx/string.h>
37
38#include "o_sound/o_sound.h"
39#include "gl_headers.h" // Must be before anything including GL stuff
40
41#include "model/ais_decoder.h"
44#include "model/gui_vars.h"
45#include "model/navobj_db.h"
46#include "model/route_point.h"
47
48#include "ais_info_gui.h"
50#include "chcanv.h"
51#include "navutil.h"
52#include "ocpn_platform.h"
53#include "routemanagerdialog.h"
54#include "top_frame.h"
55#include "undo.h"
56
57wxDEFINE_EVENT(EVT_AIS_DEL_TRACK, wxCommandEvent);
58wxDEFINE_EVENT(EVT_AIS_INFO, ObservedEvt);
59wxDEFINE_EVENT(EVT_AIS_NEW_TRACK, wxCommandEvent);
60wxDEFINE_EVENT(EVT_AIS_TOUCH, wxCommandEvent);
61wxDEFINE_EVENT(EVT_AIS_WP, wxCommandEvent);
62wxDEFINE_EVENT(SOUND_PLAYED_EVTYPE, wxCommandEvent);
63
64AisInfoGui *g_pAISGUI; // global instance
65
66static void onSoundFinished(void *ptr) {
67 if (!g_bquiting) {
68 wxCommandEvent ev(SOUND_PLAYED_EVTYPE);
69 wxPostEvent(g_pAISGUI, ev); // FIXME(leamas): Review sound handling.
70 }
71}
72
73static void OnNewAisWaypoint(RoutePoint *pWP) {
74 NavObj_dB::GetInstance().InsertRoutePoint(pWP);
75
77 pRouteManagerDialog->UpdateWptListCtrl();
78 if (top_frame::Get()->GetAbstractPrimaryCanvas()) {
79 top_frame::Get()->BeforeUndoableAction(Undo_CreateWaypoint, pWP,
80 Undo_HasParent, NULL);
81 top_frame::Get()->AfterUndoableAction(NULL);
82 top_frame::Get()->RefreshAllCanvas(false);
83 top_frame::Get()->InvalidateAllGL();
84 }
85}
86
87const static char *const kDeleteTrackPrompt = _(R"(
88This AIS target has Persistent Tracking selected by MMSI properties
89A Persistent track recording will therefore be restarted for this target.
90
91Do you instead want to stop Persistent Tracking for this target?
92)");
93
94static void OnDeleteTrack(MmsiProperties *props) {
95 if (wxID_NO == OCPNMessageBox(NULL, kDeleteTrackPrompt, _("OpenCPN Info"),
96 wxYES_NO | wxCENTER, 60)) {
97 props->m_bPersistentTrack = true;
98 }
99}
100
101AisInfoGui::AisInfoGui() {
102 ais_info_listener.Listen(g_pAIS->info_update, this, EVT_AIS_INFO);
103
104 Bind(EVT_AIS_INFO, [&](ObservedEvt &ev) {
105 auto ptr = ev.GetSharedPtr();
106 auto palert_target = std::static_pointer_cast<const AisTargetData>(ptr);
107 ShowAisInfo(palert_target);
108 });
109
110 ais_touch_listener.Listen(g_pAIS->touch_state, this, EVT_AIS_TOUCH);
111 Bind(EVT_AIS_TOUCH,
112 [&](wxCommandEvent ev) { top_frame::Get()->TouchAISActive(); });
113
114 ais_wp_listener.Listen(g_pAIS->new_ais_wp, this, EVT_AIS_WP);
115 Bind(EVT_AIS_WP, [&](wxCommandEvent ev) {
116 auto pWP = static_cast<RoutePoint *>(ev.GetClientData());
117 OnNewAisWaypoint(pWP);
118 });
119
120 ais_new_track_listener.Listen(g_pAIS->new_ais_wp, this, EVT_AIS_NEW_TRACK);
121 Bind(EVT_AIS_NEW_TRACK, [&](wxCommandEvent ev) {
122 auto t = static_cast<Track *>(ev.GetClientData());
123 NavObj_dB::GetInstance().InsertTrack(t);
124 });
125
126 ais_del_track_listener.Listen(g_pAIS->new_ais_wp, this, EVT_AIS_DEL_TRACK);
127 Bind(EVT_AIS_DEL_TRACK, [&](wxCommandEvent ev) {
128 auto t = static_cast<MmsiProperties *>(ev.GetClientData());
129 OnDeleteTrack(t);
130 });
131
132 Bind(SOUND_PLAYED_EVTYPE,
133 [&](wxCommandEvent ev) { OnSoundFinishedAISAudio(ev); });
134
135 m_AIS_Sound = 0;
136 m_bAIS_Audio_Alert_On = false;
137 m_bAIS_AlertPlaying = false;
138 m_alarm_defer_count = -1;
139 m_lastMMSItime = wxDateTime::Now();
140}
141
142void AisInfoGui::OnSoundFinishedAISAudio(wxCommandEvent &event) {
143 // By clearing this flag the main event loop will trigger repeated
144 // sounds for as long as the alert condition remains.
145
146 // Unload/reset OcpnSound object.
147 m_AIS_Sound->UnLoad();
148
149 m_bAIS_AlertPlaying = false;
150}
151
152void AisInfoGui::ShowAisInfo(
153 std::shared_ptr<const AisTargetData> palert_target) {
154 if (!palert_target) return;
155
156 int audioType = AISAUDIO_NONE;
157
158 switch (palert_target->Class) {
159 case AIS_DSC:
160 audioType = AISAUDIO_DSC;
161 break;
162 case AIS_SART:
163 audioType = AISAUDIO_SART;
164 break;
165 default:
166 audioType = AISAUDIO_CPA;
167 break;
168 }
169
170 // Maybe Reset deferral counter
171 // Arrange to reset deferral counter if 5 seconds have passed without an alarm
172 int last_alert_MMSI = m_lastMMSI;
173 wxDateTime last_alert_time = m_lastMMSItime;
174
175 if (palert_target->MMSI != last_alert_MMSI) {
176 wxTimeSpan dt = wxDateTime::Now() - last_alert_time;
177 if (dt.GetSeconds() > 5) {
178 m_alarm_defer_count = -1; // reset the counter
179 }
180 }
181
182 m_lastMMSI = palert_target->MMSI;
183 m_lastMMSItime = wxDateTime::Now();
184
185 // printf("defer count: %d\n", m_alarm_defer_count);
186
187 // If no alert dialog shown yet...
188 if (!g_pais_alert_dialog_active) {
189 // Manage deferred Alarm dialog and sound
190 if (m_alarm_defer_count < 0) {
191 m_alarm_defer_count = g_AIS_alert_delay;
192 } else {
193 if (m_alarm_defer_count >= 1) {
194 m_alarm_defer_count--;
195 }
196 }
197 if (m_alarm_defer_count == 0) { // Fire the Alarm, with sound
198 bool b_jumpto = (palert_target->Class == AIS_SART) ||
199 (palert_target->Class == AIS_DSC);
200 bool b_createWP = palert_target->Class == AIS_DSC;
201 bool b_ack = palert_target->Class != AIS_DSC;
202
203 // Show the Alert dialog
204
205 // See FS# 968/998
206 // If alert occurs while OCPN is iconized to taskbar, then clicking
207 // the taskbar icon only brings up the Alert dialog, and not the
208 // entire application. This is an OS specific behavior, not seen on
209 // linux or Mac. This patch will allow the audio alert to occur, and
210 // the visual alert will pop up soon after the user selects the OCPN
211 // icon from the taskbar. (on the next timer tick, probably)
212
213#ifndef __ANDROID__
214// if (gFrame->IsIconized() || !gFrame->IsActive())
215// gFrame->RequestUserAttention();
216#endif
217
218 if (!top_frame::Get()->IsIconized()) {
219 AISTargetAlertDialog *pAISAlertDialog = new AISTargetAlertDialog();
220 pAISAlertDialog->Create(palert_target->MMSI, wxTheApp->GetTopWindow(),
221 g_pAIS, b_jumpto, b_createWP, b_ack, -1,
222 _("AIS Alert"));
223
224 g_pais_alert_dialog_active = pAISAlertDialog;
225
226 wxTimeSpan alertLifeTime(0, 1, 0,
227 0); // Alert default lifetime, 1 minute.
228 auto alert_dlg_active =
229 dynamic_cast<AISTargetAlertDialog *>(g_pais_alert_dialog_active);
230 alert_dlg_active->dtAlertExpireTime = wxDateTime::Now() + alertLifeTime;
231 g_Platform->PositionAISAlert(pAISAlertDialog);
232
233 pAISAlertDialog->Show(); // Show modeless, so it stays on the screen
234 }
235
236 // Audio alert if requested
237 m_bAIS_Audio_Alert_On = true; // always on when alert is first shown
238 }
239 }
240
241 // The AIS Alert dialog is already shown. If the dialog MMSI number is
242 // still alerted, update the dialog otherwise, destroy the dialog
243 else {
244 // Find the target with shortest CPA, ignoring DSC and SART targets
245 double tcpa_min = 1e6; // really long
246 AisTargetData *palert_target_lowestcpa = NULL;
247 const auto &current_targets = g_pAIS->GetTargetList();
248 for (auto &it : current_targets) {
249 auto td = it.second;
250 if (td) {
251 if ((td->Class != AIS_SART) && (td->Class != AIS_DSC)) {
252 if (g_bAIS_CPA_Alert && td->b_active) {
253 if ((AIS_ALERT_SET == td->n_alert_state) && !td->b_in_ack_timeout) {
254 if (td->TCPA < tcpa_min) {
255 tcpa_min = td->TCPA;
256 palert_target_lowestcpa = td.get();
257 }
258 }
259 }
260 }
261 }
262 }
263
264 // Get the target currently displayed
265 auto alert_dlg_active =
266 dynamic_cast<AISTargetAlertDialog *>(g_pais_alert_dialog_active);
267 palert_target =
268 g_pAIS->Get_Target_Data_From_MMSI(alert_dlg_active->Get_Dialog_MMSI());
269
270 // If the currently displayed target is not alerted, it must be in "expiry
271 // delay" We should cancel that alert display now, and pick up the more
272 // critical CPA target on the next timer tick
273 if (palert_target) {
274 if (AIS_NO_ALERT == palert_target->n_alert_state) {
275 if (palert_target_lowestcpa) {
276 palert_target = NULL;
277 }
278 m_alarm_defer_count = -1;
279 }
280 }
281
282 if (palert_target) {
283 wxDateTime now = wxDateTime::Now();
284 if (((AIS_ALERT_SET == palert_target->n_alert_state) &&
285 !palert_target->b_in_ack_timeout) ||
286 (palert_target->Class == AIS_SART)) {
287 alert_dlg_active->UpdateText();
288 // Retrigger the alert expiry timeout if alerted now
289 wxTimeSpan alertLifeTime(0, 1, 0,
290 0); // Alert default lifetime, 1 minute.
291 alert_dlg_active->dtAlertExpireTime = wxDateTime::Now() + alertLifeTime;
292 }
293 // In "expiry delay"?
294 else if (!palert_target->b_in_ack_timeout &&
295 (now.IsEarlierThan(alert_dlg_active->dtAlertExpireTime))) {
296 alert_dlg_active->UpdateText();
297 } else {
298 alert_dlg_active->Close();
299 m_bAIS_Audio_Alert_On = false;
300 m_alarm_defer_count = -1;
301 }
302
303 if (true == palert_target->b_suppress_audio)
304 m_bAIS_Audio_Alert_On = false;
305 else
306 m_bAIS_Audio_Alert_On = true;
307 } else { // this should not happen, however...
308 alert_dlg_active->Close();
309 m_bAIS_Audio_Alert_On = false;
310 m_alarm_defer_count = -1;
311 }
312 }
313
314 // At this point, the audio flag (m_bAIS_Audio_Alert_On) is set
315 // Honor the global flag
316 if (!g_bAIS_CPA_Alert_Audio) m_bAIS_Audio_Alert_On = false;
317
318 // Ensure that an Alert dialog in visible before activating sound
319 auto alert_dlg_active_audio_check =
320 dynamic_cast<AISTargetAlertDialog *>(g_pais_alert_dialog_active);
321 if (alert_dlg_active_audio_check && alert_dlg_active_audio_check->IsShown()) {
322 if (m_bAIS_Audio_Alert_On) {
323 if (!m_AIS_Sound) {
324 m_AIS_Sound = o_sound::Factory();
325 }
326 if (!AIS_AlertPlaying()) {
327 m_bAIS_AlertPlaying = true;
328 wxString soundFile;
329 switch (audioType) {
330 case AISAUDIO_DSC:
331 if (g_bAIS_DSC_Alert_Audio) soundFile = g_DSC_sound_file;
332 break;
333 case AISAUDIO_SART:
334 if (g_bAIS_SART_Alert_Audio) soundFile = g_SART_sound_file;
335 break;
336 case AISAUDIO_CPA:
337 default:
338 if (g_bAIS_GCPA_Alert_Audio) soundFile = g_AIS_sound_file;
339 break;
340 }
341
342 m_AIS_Sound->Load(soundFile, g_iSoundDeviceIndex);
343 if (m_AIS_Sound->IsOk()) {
344 m_AIS_Sound->SetFinishedCallback(onSoundFinished, this);
345 if (!m_AIS_Sound->Play()) {
346 delete m_AIS_Sound;
347 m_AIS_Sound = 0;
348 m_bAIS_AlertPlaying = false;
349 }
350 } else {
351 delete m_AIS_Sound;
352 m_AIS_Sound = 0;
353 m_bAIS_AlertPlaying = false;
354 }
355 }
356 }
357 }
358
359 // If a SART Alert is active, check to see if the MMSI has special properties
360 // set indicating that this Alert is a MOB for THIS ship.
361 if (palert_target && (palert_target->Class == AIS_SART)) {
362 for (unsigned int i = 0; i < g_MMSI_Props_Array.GetCount(); i++) {
363 if (palert_target->MMSI == g_MMSI_Props_Array[i]->MMSI) {
364 if (pAISMOBRoute)
365 top_frame::Get()->UpdateAISMOBRoute(palert_target.get());
366 else
367 top_frame::Get()->ActivateAISMOBRoute(palert_target.get());
368 break;
369 }
370 }
371 }
372}
ArrayOfMmsiProperties g_MMSI_Props_Array
Global instance.
AisDecoder * g_pAIS
Global instance.
Class AisDecoder and helpers.
AisInfoGui * g_pAISGUI
Global instance.
Global state for AIS decoder.
Class AISTargetAlertDialog and helpers.
AIS target definitions.
Generic Chart canvas base.
Dialog for displaying AIS target alerts.
EventVar new_ais_wp
Notified when new AIS wp is created.
EventVar info_update
Notified when AIS user dialogs should update.
EventVar touch_state
Notified when gFrame->TouchAISActive() should be invoked.
Handles the AIS information GUI and sound alerts.
Process incoming AIS messages.
Definition ais_decoder.h:74
void Listen(const std::string &key, wxEvtHandler *listener, wxEventType evt)
Set object to send wxEventType ev to listener on changes in key.
Custom event class for OpenCPN's notification system.
std::shared_ptr< const void > GetSharedPtr() const
Gets the event's payload data.
Represents a waypoint or mark within the navigation system.
Definition route_point.h:71
Represents a track, which is a series of connected track points.
Definition track.h:117
Platform independent GL includes.
Miscellaneous globals primarely used by gui layer, not persisted in configuration file.
MySQL based storage for routes, tracks, etc.
Utility functions.
OpenCPN Platform specific support utilities.
Waypoint or mark abstraction.
Route * pAISMOBRoute
Global instance.
Definition routeman.cpp:61
RouteManagerDialog * pRouteManagerDialog
Global instance.
Manage routes dialog.
Abstract gFrame/MyFrame interface.
Framework for Undo features.