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