OpenCPN Partial API docs
Loading...
Searching...
No Matches
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#include <wx/datetime.h>
37#include <wx/event.h>
38#include <wx/string.h>
39
40#include "o_sound/o_sound.h"
41
42#include "model/ais_decoder.h"
45#include "model/route_point.h"
46
47#include "ais_info_gui.h"
49#include "chcanv.h"
50#include "navutil.h"
51#include "ocpn_frame.h"
52#include "ocpn_platform.h"
53#include "routemanagerdialog.h"
54#include "undo.h"
55#include "model/navobj_db.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
64extern ArrayOfMmsiProperties g_MMSI_Props_Array;
65extern bool g_bquiting;
66extern int g_iSoundDeviceIndex;
67extern OCPNPlatform *g_Platform;
68extern Route *pAISMOBRoute;
69extern wxString g_CmdSoundString;
70extern MyConfig *pConfig;
71extern RouteManagerDialog *pRouteManagerDialog;
72extern MyFrame *gFrame;
73
74AisInfoGui *g_pAISGUI;
75
76static void onSoundFinished(void *ptr) {
77 if (!g_bquiting) {
78 wxCommandEvent ev(SOUND_PLAYED_EVTYPE);
79 wxPostEvent(g_pAISGUI, ev); // FIXME(leamas): Review sound handling.
80 }
81}
82
83static void OnNewAisWaypoint(RoutePoint *pWP) {
84 NavObj_dB::GetInstance().InsertRoutePoint(pWP);
85
86 if (pRouteManagerDialog && pRouteManagerDialog->IsShown())
87 pRouteManagerDialog->UpdateWptListCtrl();
88 if (gFrame->GetPrimaryCanvas()) {
89 gFrame->GetPrimaryCanvas()->undo->BeforeUndoableAction(
90 Undo_CreateWaypoint, pWP, Undo_HasParent, NULL);
91 gFrame->GetPrimaryCanvas()->undo->AfterUndoableAction(NULL);
92 gFrame->RefreshAllCanvas(false);
93 gFrame->InvalidateAllGL();
94 }
95}
96
97const static char *const kDeleteTrackPrompt = _(R"(
98This AIS target has Persistent Tracking selected by MMSI properties
99A Persistent track recording will therefore be restarted for this target.
100
101Do you instead want to stop Persistent Tracking for this target?
102)");
103
104static void OnDeleteTrack(MmsiProperties *props) {
105 if (wxID_NO == OCPNMessageBox(NULL, kDeleteTrackPrompt, _("OpenCPN Info"),
106 wxYES_NO | wxCENTER, 60)) {
107 props->m_bPersistentTrack = true;
108 }
109}
110
111AisInfoGui::AisInfoGui() {
112 ais_info_listener.Listen(g_pAIS->info_update, this, EVT_AIS_INFO);
113
114 Bind(EVT_AIS_INFO, [&](ObservedEvt &ev) {
115 auto ptr = ev.GetSharedPtr();
116 auto palert_target = std::static_pointer_cast<const AisTargetData>(ptr);
117 ShowAisInfo(palert_target);
118 });
119
120 ais_touch_listener.Listen(g_pAIS->touch_state, this, EVT_AIS_TOUCH);
121 Bind(EVT_AIS_TOUCH, [&](wxCommandEvent ev) { gFrame->TouchAISActive(); });
122
123 ais_wp_listener.Listen(g_pAIS->new_ais_wp, this, EVT_AIS_WP);
124 Bind(EVT_AIS_WP, [&](wxCommandEvent ev) {
125 auto pWP = static_cast<RoutePoint *>(ev.GetClientData());
126 OnNewAisWaypoint(pWP);
127 });
128
129 ais_new_track_listener.Listen(g_pAIS->new_ais_wp, this, EVT_AIS_NEW_TRACK);
130 Bind(EVT_AIS_NEW_TRACK, [&](wxCommandEvent ev) {
131 auto t = static_cast<Track *>(ev.GetClientData());
132 NavObj_dB::GetInstance().InsertTrack(t);
133 });
134
135 ais_del_track_listener.Listen(g_pAIS->new_ais_wp, this, EVT_AIS_DEL_TRACK);
136 Bind(EVT_AIS_DEL_TRACK, [&](wxCommandEvent ev) {
137 auto t = static_cast<MmsiProperties *>(ev.GetClientData());
138 OnDeleteTrack(t);
139 });
140
141 Bind(SOUND_PLAYED_EVTYPE,
142 [&](wxCommandEvent ev) { OnSoundFinishedAISAudio(ev); });
143
144 m_AIS_Sound = 0;
145 m_bAIS_Audio_Alert_On = false;
146 m_bAIS_AlertPlaying = false;
147}
148
149void AisInfoGui::OnSoundFinishedAISAudio(wxCommandEvent &event) {
150 // By clearing this flag the main event loop will trigger repeated
151 // sounds for as long as the alert condition remains.
152
153 // Unload/reset OcpnSound object.
154 m_AIS_Sound->UnLoad();
155
156 m_bAIS_AlertPlaying = false;
157}
158
159void AisInfoGui::ShowAisInfo(
160 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 =
180 (palert_target->Class == AIS_SART) || (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, b_jumpto,
202 b_createWP, b_ack, -1, _("AIS Alert"));
203
204 g_pais_alert_dialog_active = pAISAlertDialog;
205
206 wxTimeSpan alertLifeTime(0, 1, 0,
207 0); // Alert default lifetime, 1 minute.
208 auto alert_dlg_active =
209 dynamic_cast<AISTargetAlertDialog *>(g_pais_alert_dialog_active);
210 alert_dlg_active->dtAlertExpireTime = wxDateTime::Now() + alertLifeTime;
211 g_Platform->PositionAISAlert(pAISAlertDialog);
212
213 pAISAlertDialog->Show(); // Show modeless, so it stays on the screen
214 }
215
216 // Audio alert if requested
217 m_bAIS_Audio_Alert_On = true; // always on when alert is first shown
218 }
219
220 // The AIS Alert dialog is already shown. If the dialog MMSI number is
221 // still alerted, update the dialog otherwise, destroy the dialog
222 else {
223 // Find the target with shortest CPA, ignoring DSC and SART targets
224 double tcpa_min = 1e6; // really long
225 AisTargetData *palert_target_lowestcpa = NULL;
226 const auto &current_targets = g_pAIS->GetTargetList();
227 for (auto &it : current_targets) {
228 auto td = it.second;
229 if (td) {
230 if ((td->Class != AIS_SART) && (td->Class != AIS_DSC)) {
231 if (g_bAIS_CPA_Alert && td->b_active) {
232 if ((AIS_ALERT_SET == td->n_alert_state) && !td->b_in_ack_timeout) {
233 if (td->TCPA < tcpa_min) {
234 tcpa_min = td->TCPA;
235 palert_target_lowestcpa = td.get();
236 }
237 }
238 }
239 }
240 }
241 }
242
243 // Get the target currently displayed
244 auto alert_dlg_active =
245 dynamic_cast<AISTargetAlertDialog *>(g_pais_alert_dialog_active);
246 palert_target =
247 g_pAIS->Get_Target_Data_From_MMSI(alert_dlg_active->Get_Dialog_MMSI());
248
249 // If the currently displayed target is not alerted, it must be in "expiry
250 // delay" We should cancel that alert display now, and pick up the more
251 // critical CPA target on the next timer tick
252 if (palert_target) {
253 if (AIS_NO_ALERT == palert_target->n_alert_state) {
254 if (palert_target_lowestcpa) {
255 palert_target = NULL;
256 }
257 }
258 }
259
260 if (palert_target) {
261 wxDateTime now = wxDateTime::Now();
262 if (((AIS_ALERT_SET == palert_target->n_alert_state) &&
263 !palert_target->b_in_ack_timeout) ||
264 (palert_target->Class == AIS_SART)) {
265 alert_dlg_active->UpdateText();
266 // Retrigger the alert expiry timeout if alerted now
267 wxTimeSpan alertLifeTime(0, 1, 0,
268 0); // Alert default lifetime, 1 minute.
269 alert_dlg_active->dtAlertExpireTime = wxDateTime::Now() + alertLifeTime;
270 }
271 // In "expiry delay"?
272 else if (!palert_target->b_in_ack_timeout &&
273 (now.IsEarlierThan(alert_dlg_active->dtAlertExpireTime))) {
274 alert_dlg_active->UpdateText();
275 } else {
276 alert_dlg_active->Close();
277 m_bAIS_Audio_Alert_On = false;
278 }
279
280 if (true == palert_target->b_suppress_audio)
281 m_bAIS_Audio_Alert_On = false;
282 else
283 m_bAIS_Audio_Alert_On = true;
284 } else { // this should not happen, however...
285 alert_dlg_active->Close();
286 m_bAIS_Audio_Alert_On = false;
287 }
288 }
289
290 // At this point, the audio flag is set
291 // Honor the global flag
292 if (!g_bAIS_CPA_Alert_Audio) m_bAIS_Audio_Alert_On = false;
293
294 if (m_bAIS_Audio_Alert_On) {
295 if (!m_AIS_Sound) {
296 m_AIS_Sound = o_sound::Factory();
297 }
298 if (!AIS_AlertPlaying()) {
299 m_bAIS_AlertPlaying = true;
300 wxString soundFile;
301 switch (audioType) {
302 case AISAUDIO_DSC:
303 if (g_bAIS_DSC_Alert_Audio) soundFile = g_DSC_sound_file;
304 break;
305 case AISAUDIO_SART:
306 if (g_bAIS_SART_Alert_Audio) soundFile = g_SART_sound_file;
307 break;
308 case AISAUDIO_CPA:
309 default:
310 if (g_bAIS_GCPA_Alert_Audio) soundFile = g_AIS_sound_file;
311 break;
312 }
313
314 m_AIS_Sound->Load(soundFile, g_iSoundDeviceIndex);
315 if (m_AIS_Sound->IsOk()) {
316 m_AIS_Sound->SetFinishedCallback(onSoundFinished, this);
317 if (!m_AIS_Sound->Play()) {
318 delete m_AIS_Sound;
319 m_AIS_Sound = 0;
320 m_bAIS_AlertPlaying = false;
321 }
322 } else {
323 delete m_AIS_Sound;
324 m_AIS_Sound = 0;
325 m_bAIS_AlertPlaying = false;
326 }
327 }
328 }
329 // If a SART Alert is active, check to see if the MMSI has special properties
330 // set indicating that this Alert is a MOB for THIS ship.
331 if (palert_target && (palert_target->Class == AIS_SART)) {
332 for (unsigned int i = 0; i < g_MMSI_Props_Array.GetCount(); i++) {
333 if (palert_target->MMSI == g_MMSI_Props_Array[i]->MMSI) {
334 if (pAISMOBRoute)
335 gFrame->UpdateAISMOBRoute(palert_target.get());
336 else
337 gFrame->ActivateAISMOBRoute(palert_target.get());
338 break;
339 }
340 }
341 }
342}
ArrayOfMmsiProperties g_MMSI_Props_Array
Global instance.
AisDecoder * g_pAIS
Global instance.
Class AisDecoder and helpers.
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
Main application frame.
Definition ocpn_frame.h:139
Provides platform-specific support utilities for OpenCPN.
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 navigational route in the navigation system.
Definition route.h:99
Represents a track, which is a series of connected track points.
Definition track.h:117
MySQL based storage for routes, tracks, etc.
Utility functions.
OpenCPN top window.
OpenCPN Platform specific support utilities.
Waypoint or mark abstraction.
Route * pAISMOBRoute
Global instance.
Definition routeman.cpp:61
Manage routes dialog.
Framework for Undo features.