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