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 // Display all SART/MOB Alerts immediately.
186 if (palert_target->Class == AIS_SART) m_alarm_defer_count = 1;
187
188 // If no alert dialog shown yet...
189 if (!g_pais_alert_dialog_active) {
190 // Manage deferred Alarm dialog and sound
191 if (m_alarm_defer_count < 0) {
192 m_alarm_defer_count = g_AIS_alert_delay;
193 } else {
194 if (m_alarm_defer_count >= 1) {
195 m_alarm_defer_count--;
196 }
197 }
198 if (m_alarm_defer_count == 0) { // Fire the Alarm, with sound
199 bool b_jumpto = (palert_target->Class == AIS_SART) ||
200 (palert_target->Class == AIS_DSC);
201 bool b_createWP = palert_target->Class == AIS_DSC;
202 bool b_ack = palert_target->Class != AIS_DSC;
203
204 // Show the Alert dialog
205
206 // See FS# 968/998
207 // If alert occurs while OCPN is iconized to taskbar, then clicking
208 // the taskbar icon only brings up the Alert dialog, and not the
209 // entire application. This is an OS specific behavior, not seen on
210 // linux or Mac. This patch will allow the audio alert to occur, and
211 // the visual alert will pop up soon after the user selects the OCPN
212 // icon from the taskbar. (on the next timer tick, probably)
213
214#ifndef __ANDROID__
215// if (gFrame->IsIconized() || !gFrame->IsActive())
216// gFrame->RequestUserAttention();
217#endif
218
219 if (!top_frame::Get()->IsIconized()) {
220 AISTargetAlertDialog *pAISAlertDialog = new AISTargetAlertDialog();
221 pAISAlertDialog->Create(palert_target->MMSI, wxTheApp->GetTopWindow(),
222 g_pAIS, b_jumpto, b_createWP, b_ack, -1,
223 _("AIS Alert"));
224
225 g_pais_alert_dialog_active = pAISAlertDialog;
226
227 wxTimeSpan alertLifeTime(0, 1, 0,
228 0); // Alert default lifetime, 1 minute.
229 auto alert_dlg_active =
230 dynamic_cast<AISTargetAlertDialog *>(g_pais_alert_dialog_active);
231 alert_dlg_active->dtAlertExpireTime = wxDateTime::Now() + alertLifeTime;
232 g_Platform->PositionAISAlert(pAISAlertDialog);
233
234 pAISAlertDialog->Show(); // Show modeless, so it stays on the screen
235 }
236
237 // Audio alert if requested
238 m_bAIS_Audio_Alert_On = true; // always on when alert is first shown
239 }
240 }
241
242 // The AIS Alert dialog is already shown. If the dialog MMSI number is
243 // still alerted, update the dialog otherwise, destroy the dialog
244 else {
245 // Find the target with shortest CPA, ignoring DSC and SART targets
246 double tcpa_min = 1e6; // really long
247 AisTargetData *palert_target_lowestcpa = NULL;
248 const auto &current_targets = g_pAIS->GetTargetList();
249 for (auto &it : current_targets) {
250 auto td = it.second;
251 if (td) {
252 if ((td->Class != AIS_SART) && (td->Class != AIS_DSC)) {
253 if (g_bAIS_CPA_Alert && td->b_active) {
254 if ((AIS_ALERT_SET == td->n_alert_state) && !td->b_in_ack_timeout) {
255 if (td->TCPA < tcpa_min) {
256 tcpa_min = td->TCPA;
257 palert_target_lowestcpa = td.get();
258 }
259 }
260 }
261 }
262 }
263 }
264
265 // Get the target currently displayed
266 auto alert_dlg_active =
267 dynamic_cast<AISTargetAlertDialog *>(g_pais_alert_dialog_active);
268 palert_target =
269 g_pAIS->Get_Target_Data_From_MMSI(alert_dlg_active->Get_Dialog_MMSI());
270
271 // If the currently displayed target is not alerted, it must be in "expiry
272 // delay" We should cancel that alert display now, and pick up the more
273 // critical CPA target on the next timer tick
274 if (palert_target) {
275 if (AIS_NO_ALERT == palert_target->n_alert_state) {
276 if (palert_target_lowestcpa) {
277 palert_target = NULL;
278 }
279 m_alarm_defer_count = -1;
280 }
281 }
282
283 if (palert_target) {
284 wxDateTime now = wxDateTime::Now();
285 if (((AIS_ALERT_SET == palert_target->n_alert_state) &&
286 !palert_target->b_in_ack_timeout) ||
287 (palert_target->Class == AIS_SART)) {
288 alert_dlg_active->UpdateText();
289 // Retrigger the alert expiry timeout if alerted now
290 wxTimeSpan alertLifeTime(0, 1, 0,
291 0); // Alert default lifetime, 1 minute.
292 alert_dlg_active->dtAlertExpireTime = wxDateTime::Now() + alertLifeTime;
293 }
294 // In "expiry delay"?
295 else if (!palert_target->b_in_ack_timeout &&
296 (now.IsEarlierThan(alert_dlg_active->dtAlertExpireTime))) {
297 alert_dlg_active->UpdateText();
298 } else {
299 alert_dlg_active->Close();
300 m_bAIS_Audio_Alert_On = false;
301 m_alarm_defer_count = -1;
302 }
303
304 if (true == palert_target->b_suppress_audio)
305 m_bAIS_Audio_Alert_On = false;
306 else
307 m_bAIS_Audio_Alert_On = true;
308 } else { // this should not happen, however...
309 alert_dlg_active->Close();
310 m_bAIS_Audio_Alert_On = false;
311 m_alarm_defer_count = -1;
312 }
313 }
314
315 // At this point, the audio flag (m_bAIS_Audio_Alert_On) is set
316 // Honor the global flag
317 if (!g_bAIS_CPA_Alert_Audio) m_bAIS_Audio_Alert_On = false;
318
319 // Ensure that an Alert dialog in visible before activating sound
320 auto alert_dlg_active_audio_check =
321 dynamic_cast<AISTargetAlertDialog *>(g_pais_alert_dialog_active);
322 if (alert_dlg_active_audio_check && alert_dlg_active_audio_check->IsShown()) {
323 if (m_bAIS_Audio_Alert_On) {
324 if (!m_AIS_Sound) {
325 m_AIS_Sound = o_sound::Factory();
326 }
327 if (!AIS_AlertPlaying()) {
328 m_bAIS_AlertPlaying = true;
329 wxString soundFile;
330 switch (audioType) {
331 case AISAUDIO_DSC:
332 if (g_bAIS_DSC_Alert_Audio) soundFile = g_DSC_sound_file;
333 break;
334 case AISAUDIO_SART:
335 if (g_bAIS_SART_Alert_Audio) soundFile = g_SART_sound_file;
336 break;
337 case AISAUDIO_CPA:
338 default:
339 if (g_bAIS_GCPA_Alert_Audio) soundFile = g_AIS_sound_file;
340 break;
341 }
342
343 m_AIS_Sound->Load(soundFile, g_iSoundDeviceIndex);
344 if (m_AIS_Sound->IsOk()) {
345 m_AIS_Sound->SetFinishedCallback(onSoundFinished, this);
346 if (!m_AIS_Sound->Play()) {
347 delete m_AIS_Sound;
348 m_AIS_Sound = 0;
349 m_bAIS_AlertPlaying = false;
350 }
351 } else {
352 delete m_AIS_Sound;
353 m_AIS_Sound = 0;
354 m_bAIS_AlertPlaying = false;
355 }
356 }
357 }
358 }
359
360 // If a SART Alert is active, check to see if the MMSI has special properties
361 // set indicating that this Alert is a MOB for THIS ship.
362 if (palert_target && (palert_target->Class == AIS_SART)) {
363 for (unsigned int i = 0; i < g_MMSI_Props_Array.GetCount(); i++) {
364 if (palert_target->MMSI == g_MMSI_Props_Array[i]->MMSI) {
365 if (pAISMOBRoute)
366 top_frame::Get()->UpdateAISMOBRoute(palert_target.get());
367 else
368 top_frame::Get()->ActivateAISMOBRoute(palert_target.get());
369 break;
370 }
371 }
372 }
373}
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.