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