1 // ----------------------------------------------------------------------------
2 // notify.cxx
3 //
4 // Copyright (C) 2009-2010
5 // Stelios Bounanos, M0GLD
6 //
7 // Generic notifier
8 //
9 //
10 // This file is part of fldigi.
11 //
12 // fldigi is free software; you can redistribute it and/or modify
13 // it under the terms of the GNU General Public License as published by
14 // the Free Software Foundation; either version 3 of the License, or
15 // (at your option) any later version.
16 //
17 // fldigi is distributed in the hope that it will be useful,
18 // but WITHOUT ANY WARRANTY; without even the implied warranty of
19 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 // GNU General Public License for more details.
21 //
22 // You should have received a copy of the GNU General Public License
23 // along with this program. If not, see <http://www.gnu.org/licenses/>.
24 // ----------------------------------------------------------------------------
25
26 #include <config.h>
27
28 #include <string>
29 #include <vector>
30 #include <list>
31 #include <algorithm>
32 #include <cstring>
33 #include <cctype>
34 #include <cstdlib>
35
36 #include <sstream>
37
38 #include "timeops.h"
39
40 #if HAVE_STD_HASH
41 # define MAP_TYPE std::unordered_map
42 # define HASH_TYPE std::hash
43 # include <unordered_map>
44 #else
45 # if (__GNUC__ > 4) || (__GNUC__ == 4 && __GNUC_MINOR__ >= 1)
46 # define MAP_TYPE std::tr1::unordered_map
47 # include <tr1/unordered_map>
48 # else
49 // use the non-standard gnu hash_map on gcc <= 4.0.x,
50 // which has a broken tr1::unordered_map::operator=
51 # define MAP_TYPE __gnu_cxx::hash_map
52 # include <ext/hash_map>
53 namespace __gnu_cxx {
54 // define the missing hash specialisation for std::string
55 // using the 'const char*' hash function
56 template<> struct hash<std::string> {
operator ()__gnu_cxx::hash57 size_t operator()(const std::string& s) const { return __stl_hash_string(s.c_str()); }
58 };
59 }
60 # endif
61 #endif
62
63 #include <unistd.h>
64 #include <time.h>
65 #include <sys/time.h>
66
67 #ifdef __MINGW32__
68 # include <windows.h>
69 #endif
70
71 #include <FL/Fl_Menu_Item.H>
72 #include <FL/Fl_Preferences.H>
73 #include <FL/Fl_Pixmap.H>
74 #include <FL/Fl.H>
75 #include <FL/Enumerations.H>
76 #include <FL/Fl_Button.H>
77
78 #include "flinput2.h"
79 #include "flmisc.h"
80 #include "macros.h"
81 #include "debug.h"
82 #include "dxcc.h"
83 #include "spot.h"
84 #include "pskrep.h"
85 #include "logsupport.h"
86 #include "re.h"
87 #include "fileselect.h"
88 #include "icons.h"
89 #include "configuration.h"
90 #include "macroedit.h"
91 #include "main.h"
92 #include "fl_digi.h"
93 #include "waterfall.h"
94 #include "globals.h"
95 #include "trx.h"
96 #include "rsid.h"
97 #include "gettext.h"
98 #include "notifydialog.h"
99 #include "notify.h"
100 #include "qrunner.h"
101
102 using namespace std;
103
104 struct notify_action_t
105 {
106 string alert;
107 string rx_marker;
108 string macro;
109 string program;
110 time_t alert_timeout;
111 time_t trigger_limit;
112 };
113
114 enum notify_filter_match_t { NOTIFY_FILTER_CALLSIGN, NOTIFY_FILTER_DXCC };
115 typedef MAP_TYPE<string, bool> notify_filter_dxcc_t;
116 struct notify_filter_t
117 {
118 notify_filter_match_t match;
119 string callsign;
120 notify_filter_dxcc_t dxcc;
121 string dxcc_last;
122 bool nwb, lotw, eqsl;
123 };
124
125 struct notify_dup_t
126 {
127 time_t when;
128 band_t band;
129 trx_mode mode;
130 long long freq;
131 };
132
133 typedef MAP_TYPE<string, notify_dup_t> notify_seen_t;
134 enum notify_event_t { NOTIFY_EVENT_MYCALL, NOTIFY_EVENT_STATION, NOTIFY_EVENT_CUSTOM, NOTIFY_EVENT_RSID };
135 struct notify_t
136 {
137 notify_event_t event;
138 time_t last_trigger;
139 string re;
140 bool enabled;
141 int afreq;
142 long long rfreq;
143 trx_mode mode;
144
145 const char* match_string;
146 const regmatch_t* submatch_offsets;
147 size_t submatch_length;
148
149 notify_dup_t dup;
150 bool dup_ignore;
151 size_t dup_ref;
152 notify_seen_t last_seen;
153
154 notify_action_t action;
155 notify_filter_t filter;
156 };
157 typedef list<notify_t> notify_list_t;
158
159
160 static void notify_init_window(void);
161 static void notify_save(void);
162 static void notify_load(void);
163 static void notify_register(notify_t& n);
164 static void notify_unregister(const notify_t& n);
165 static void notify_set_qsodb_cache(void);
166
167 static void notify_event_cb(Fl_Widget* w, void* arg);
168 static void notify_select_cb(Fl_Widget* w, void* arg);
169 static void notify_dxcc_browse_cb(Fl_Widget* w, void* arg);
170 static void notify_add_cb(Fl_Widget* w, void* arg);
171 static void notify_remove_cb(Fl_Widget* w, void* arg);
172 static void notify_update_cb(Fl_Widget* w, void* arg);
173 static void notify_dialog_default_cb(Fl_Widget* w, void* arg);
174 static void notify_rx_default_cb(Fl_Widget* w, void* arg);
175 static void notify_macro_edit_cb(Fl_Widget* w, void* arg);
176 static void notify_program_select_cb(Fl_Widget* w, void* arg);
177 static void notify_dxcc_check_cb(Fl_Widget* w, void* arg);
178 static void notify_test_cb(Fl_Widget* w, void* arg);
179 static void notify_filter_dxcc_select_cb(Fl_Widget* w, void* arg);
180 static void notify_filter_dxcc_search(Fl_Widget* w, void* arg);
181 static void notify_dup_ignore_cb(Fl_Widget* w, void* arg);
182 static void notify_re_cb(Fl_Widget* w, void* arg);
183
184 static void notify_recv(trx_mode mode, int afreq, const char* str, const regmatch_t* sub, size_t len, void* data);
185 static void notify_table_append(const notify_t& n);
186
187 ////////////////////////////////////////////////////////////////////////////////
188
189 struct event_regex_t {
190 const char* regex;
191 size_t index;
192 };
193
194 static Fl_Menu_Item notify_event_menu[] = {
195 { _("My callsign de CALL") },
196 { _("Station heard twice") },
197 { _("Custom text search") },
198 { _("RSID reception") },
199 { 0 }
200 };
201
202 #define NOTIFY_SET_DUP_MENU (void*)1
203
204 enum { NOTIFY_LIST_MENU_TOGGLE, NOTIFY_LIST_MENU_UPDATE, NOTIFY_LIST_MENU_REMOVE };
205 static Fl_Menu_Item notify_list_context_menu[] = {
206 { icons::make_icon_label(_("Toggle"), shutdown_icon), 0, notify_update_cb, (void*)NOTIFY_LIST_MENU_TOGGLE },
207 { icons::make_icon_label(_("Update"), refresh_icon), 0, notify_update_cb, (void*)NOTIFY_LIST_MENU_UPDATE },
208 { icons::make_icon_label(_("Remove"), minus_icon), 0, notify_remove_cb, (void*)NOTIFY_LIST_MENU_REMOVE },
209 { 0 }
210 };
211
212 enum {
213 NOTIFY_DXCC_SELECT_CONT, NOTIFY_DXCC_SELECT_ITU,
214 NOTIFY_DXCC_SELECT_CQ, NOTIFY_DXCC_SELECT_ALL,
215 NOTIFY_DXCC_DESELECT_CONT, NOTIFY_DXCC_DESELECT_ITU,
216 NOTIFY_DXCC_DESELECT_CQ, NOTIFY_DXCC_DESELECT_ALL
217 };
218 static Fl_Menu_Item notify_dxcc_context_menu[] = {
219 { _("Select"), 0, 0, 0, FL_SUBMENU },
220 { _("Continent"), 0, notify_filter_dxcc_select_cb, (void*)NOTIFY_DXCC_SELECT_CONT },
221 { _("ITU zone"), 0, notify_filter_dxcc_select_cb, (void*)NOTIFY_DXCC_SELECT_ITU },
222 { _("CQ zone"), 0, notify_filter_dxcc_select_cb, (void*)NOTIFY_DXCC_SELECT_CQ, FL_MENU_DIVIDER },
223 { _("All"), 0, notify_filter_dxcc_select_cb, (void*)NOTIFY_DXCC_SELECT_ALL },
224 { 0 },
225 { _("Deselect"), 0, 0, 0, FL_SUBMENU },
226 { _("Continent"), 0, notify_filter_dxcc_select_cb, (void*)NOTIFY_DXCC_DESELECT_CONT },
227 { _("ITU zone"), 0, notify_filter_dxcc_select_cb, (void*)NOTIFY_DXCC_DESELECT_ITU },
228 { _("CQ zone"), 0, notify_filter_dxcc_select_cb, (void*)NOTIFY_DXCC_DESELECT_CQ, FL_MENU_DIVIDER },
229 { _("All"), 0, notify_filter_dxcc_select_cb, (void*)NOTIFY_DXCC_DESELECT_ALL },
230 { 0 },
231 { 0 }
232 };
233
234 static event_regex_t event_regex[] = {
235 { "<MYCALL>.+de[[:space:]]+(<CALLSIGN_RE>)", 1 },
236 { PSKREP_RE, PSKREP_RE_INDEX },
237 { "", 0 },
238 { "", 1 }
239 };
240
241 static const char* default_alert_text[] = {
242 "$CALLSIGN is calling you\n $TEXT\nTime: %X %Z (%z)\nMode: $MODEM @ $RF_KHZ KHz",
243 "Heard $CALLSIGN ($COUNTRY)\n $TEXT\nTime: %X %Z (%z)\nMode: $MODEM @ $RF_KHZ KHz",
244 "",
245 "RSID received\nMode: $MODEM @ $RF_KHZ KHz\nTime: %X %Z (%z)",
246 };
247
248 static Fl_Menu_Item notify_dup_callsign_menu[] = {
249 { "" }, { "" }, { "" }, { "" }, { "" }, { "" }, { "" }, { "" }, { "" }, { "" }, { 0 }
250 };
251
252 static Fl_Menu_Item notify_dup_refs_menu[] = {
253 { "Substring \\0" }, { "Substring \\1" }, { "Substring \\2" }, { "Substring \\3" },
254 { "Substring \\4" }, { "Substring \\5" }, { "Substring \\6" }, { "Substring \\7" },
255 { "Substring \\8" }, { "Substring \\9" }, { 0 }
256 };
257
258 enum {
259 NOTIFY_DXCC_COL_SEL, NOTIFY_DXCC_COL_CN, NOTIFY_DXCC_COL_CT,
260 NOTIFY_DXCC_COL_ITU, NOTIFY_DXCC_COL_CQ, NOTIFY_DXCC_NUMCOL
261 };
262
263
advli(T & i,int n)264 template <typename T, typename U> static T& advli(T& i, int n) { advance(i, n); return i; }
advli(T i,U n)265 template <typename T, typename U> static T advli(T i, U n) { advance(i, n); return i; }
266
267
268 static notify_list_t notify_list;
269 static notify_t notify_tmp;
270 static const vector<dxcc*>* dxcc_list;
271
272 Fl_Double_Window* notify_window;
273 Fl_Double_Window* dxcc_window;
274
275
276 ////////////////////////////////////////////////////////////////////////////////
277 // public interface
278 ////////////////////////////////////////////////////////////////////////////////
279
notify_start(void)280 void notify_start(void)
281 {
282 if (!notify_window)
283 notify_init_window();
284
285 notify_load();
286 if (!notify_list.empty()) {
287 for (notify_list_t::iterator i = notify_list.begin(); i != notify_list.end(); ++i)
288 if (i->enabled)
289 notify_register(*i);
290 tblNotifyList->value(0);
291 tblNotifyList->do_callback();
292 notify_set_qsodb_cache();
293 }
294 }
295
notify_stop(void)296 void notify_stop(void)
297 {
298 for (notify_list_t::iterator i = notify_list.begin(); i != notify_list.end(); ++i)
299 notify_unregister(*i);
300 notify_list.clear();
301 notify_set_qsodb_cache();
302 tblNotifyList->clear();
303 }
304
notify_show(void)305 void notify_show(void)
306 {
307 if (!notify_window)
308 notify_window = make_notify_window();
309 notify_window->show();
310 }
311
312 // display the dxcc window
notify_dxcc_show(bool readonly)313 void notify_dxcc_show(bool readonly)
314 {
315 if (!dxcc_list)
316 return;
317 if (readonly) {
318 btnNotifyDXCCSelect->hide();
319 btnNotifyDXCCDeselect->hide();
320 tblNotifyFilterDXCC->callback(Fl_Widget::default_callback);
321 tblNotifyFilterDXCC->menu(0);
322 btnNotifyDXCCDeselect->do_callback(); // deselect all
323 if (dxcc_window->shown())
324 dxcc_window->hide();
325 if (dxcc_window->modal())
326 dxcc_window->set_non_modal();
327 }
328 else {
329 btnNotifyDXCCSelect->show();
330 btnNotifyDXCCDeselect->show();
331 tblNotifyFilterDXCC->callback(notify_dxcc_check_cb);
332 tblNotifyFilterDXCC->menu(notify_dxcc_context_menu);
333 dxcc_window->set_modal();
334 }
335 dxcc_window->show();
336 }
337
338 // called by the myCall callback when the operator callsign is changed
notify_change_callsign(void)339 void notify_change_callsign(void)
340 {
341 for (notify_list_t::iterator i = notify_list.begin(); i != notify_list.end(); ++i) {
342 if (i->event == NOTIFY_EVENT_MYCALL) { // re-register
343 notify_unregister(*i);
344 notify_register(*i);
345 }
346 }
347 }
348
349 // called by the RSID decoder
notify_rsid(trx_mode mode,int afreq)350 void notify_rsid(trx_mode mode, int afreq)
351 {
352 const char* mode_name = mode_info[mode].name;
353 regmatch_t sub[2] = { { 0, (regoff_t)strlen(mode_name) } };
354 sub[1] = sub[0];
355 for (notify_list_t::iterator i = notify_list.begin(); i != notify_list.end(); ++i)
356 if (i->event == NOTIFY_EVENT_RSID)
357 notify_recv(mode, afreq, mode_name, sub, 2, &*i);
358 }
359
360 // called by the config dialog when the "notifications only"
361 // rsid option is selected
notify_create_rsid_event(bool val)362 void notify_create_rsid_event(bool val)
363 {
364 if (!val)
365 return;
366 for (notify_list_t::iterator i = notify_list.begin(); i != notify_list.end(); ++i)
367 if (i->event == NOTIFY_EVENT_RSID)
368 return;
369
370 notify_t rsid_event = {
371 NOTIFY_EVENT_RSID, 0, "", true, 0, 0LL, NUM_MODES, NULL, NULL, 0,
372 { 0, NUM_BANDS, NUM_MODES, 0LL }, false, 0
373 };
374 notify_action_t rsid_action = { default_alert_text[NOTIFY_EVENT_RSID], "", "", "", 30, 1 };
375 rsid_event.action = rsid_action;
376
377 notify_list.push_back(rsid_event);
378 notify_table_append(notify_list.back());
379 tblNotifyList->do_callback();
380 notify_save();
381 }
382
383
384 ////////////////////////////////////////////////////////////////////////////////
385 // misc utility functions
386 ////////////////////////////////////////////////////////////////////////////////
387
388 static void notify_set_event_dup(const notify_t& n);
389 static void notify_set_event_dup_menu(const char* re);
390 static bool notify_dxcc_row_checked(int i);
391
392 // return actual regular expression for event n
notify_get_re(const notify_t & n)393 static string notify_get_re(const notify_t& n)
394 {
395 string::size_type pos;
396 string s = n.re;
397
398 struct { const char* str; const char* rep; } subst[] = {
399 { "<MYCALL>", progdefaults.myCall.c_str() },
400 { "<CALLSIGN_RE>", CALLSIGN_RE }
401 };
402 for (size_t i = 0; i < sizeof(subst)/sizeof(*subst); i++)
403 if ((pos = s.find(subst[i].str)) != string::npos)
404 s.replace(pos, strlen(subst[i].str), subst[i].rep);
405
406 return s;
407 }
408
409 // set the widget values using event n
notify_event_to_gui(const notify_t & n)410 static void notify_event_to_gui(const notify_t& n)
411 {
412 // event
413 mnuNotifyEvent->value(n.event);
414 notify_event_cb(mnuNotifyEvent, 0);
415 if (!n.re.empty() && inpNotifyRE->visible())
416 inpNotifyRE->value(n.re.c_str());
417 btnNotifyEnabled->value(n.enabled);
418
419 // action
420 inpNotifyActionDialog->value(n.action.alert.c_str());
421 inpNotifyActionRXMarker->value(n.action.rx_marker.c_str());
422 inpNotifyActionMacro->value(n.action.macro.c_str());
423 inpNotifyActionProgram->value(n.action.program.c_str());
424 cntNotifyActionLimit->value(n.action.trigger_limit);
425 cntNotifyActionDialogTimeout->value(n.action.alert_timeout);
426
427 // dup
428 chkNotifyDupIgnore->value(n.dup_ignore);
429 chkNotifyDupIgnore->do_callback();
430 notify_set_event_dup(n);
431 cntNotifyDupTime->value(n.dup.when);
432 chkNotifyDupBand->value(n.dup.band);
433 chkNotifyDupMode->value(n.dup.mode);
434
435 // filter
436 btnNotifyDXCCDeselect->do_callback(); // deselect all
437 if (!grpNotifyFilter->active())
438 return;
439 chkNotifyFilterCall->value(0);
440 inpNotifyFilterCall->hide();
441 chkNotifyFilterDXCC->value(0);
442 btnNotifyFilterDXCC->hide();
443 inpNotifyFilterCall->value(0);
444
445 if (n.filter.match == NOTIFY_FILTER_CALLSIGN) {
446 chkNotifyFilterCall->value(1);
447 inpNotifyFilterCall->show();
448 inpNotifyFilterCall->value(n.filter.callsign.c_str());
449 }
450 else if (n.filter.match == NOTIFY_FILTER_DXCC) {
451 chkNotifyFilterDXCC->value(1);
452 btnNotifyFilterDXCC->show();
453 }
454 chkNotifyFilterNWB->value(n.filter.nwb);
455 chkNotifyFilterLOTW->value(n.filter.lotw);
456 chkNotifyFilterEQSL->value(n.filter.eqsl);
457 }
458
459 // copy widget values to event n
notify_gui_to_event(notify_t & n)460 static void notify_gui_to_event(notify_t& n)
461 {
462 // event
463 n.event = static_cast<notify_event_t>(mnuNotifyEvent->value());
464 n.last_trigger = 0;
465 if (n.event == NOTIFY_EVENT_CUSTOM)
466 n.re = inpNotifyRE->value();
467 else
468 n.re = event_regex[n.event].regex;
469 n.enabled = btnNotifyEnabled->value();
470 n.afreq = 0;
471 n.rfreq = 0;
472 n.match_string = 0;
473 n.submatch_offsets = 0;
474 n.submatch_length = 0;
475
476 // action
477 n.action.alert = inpNotifyActionDialog->value();
478 n.action.rx_marker = inpNotifyActionRXMarker->value();
479 n.action.macro = inpNotifyActionMacro->value();
480 n.action.program = inpNotifyActionProgram->value();
481 n.action.trigger_limit = static_cast<time_t>(cntNotifyActionLimit->value());
482 n.action.alert_timeout = static_cast<time_t>(cntNotifyActionDialogTimeout->value());
483
484 // filter
485 if (chkNotifyFilterCall->value()) {
486 n.filter.callsign = inpNotifyFilterCall->value();
487 n.filter.match = NOTIFY_FILTER_CALLSIGN;
488 }
489 else if (chkNotifyFilterDXCC->value()) {
490 n.filter.match = NOTIFY_FILTER_DXCC;
491
492 n.filter.dxcc.clear();
493 for (int i = 0; i < tblNotifyFilterDXCC->rows(); i++) {
494 if (notify_dxcc_row_checked(i))
495 n.filter.dxcc[tblNotifyFilterDXCC->valueAt(i, NOTIFY_DXCC_COL_CN)] = true;
496 }
497 }
498 n.filter.nwb = chkNotifyFilterNWB->value();
499 n.filter.lotw = chkNotifyFilterLOTW->value();
500 n.filter.eqsl = chkNotifyFilterEQSL->value();
501
502 // dup
503 n.dup_ignore = chkNotifyDupIgnore->value();
504 n.dup_ref = mnuNotifyDupWhich->value();
505 n.dup.when = static_cast<time_t>(cntNotifyDupTime->value());
506 n.dup.band = chkNotifyDupBand->value() ? NUM_BANDS : static_cast<band_t>(0);
507 n.dup.mode = chkNotifyDupMode->value() ? NUM_MODES : static_cast<trx_mode>(0);
508 }
509
510 // initialise the notifications window
notify_init_window(void)511 static void notify_init_window(void)
512 {
513 notify_window = make_notify_window();
514 notify_window->xclass(PACKAGE_TARNAME);
515 dxcc_window = make_dxcc_window();
516 dxcc_window->xclass(PACKAGE_TARNAME);
517
518 struct { Fl_Button* button; const char* label; } buttons[] = {
519 { btnNotifyAdd, icons::make_icon_label(_("Add"), plus_icon) },
520 { btnNotifyRemove, icons::make_icon_label(_("Remove"), minus_icon) },
521 { btnNotifyUpdate, icons::make_icon_label(_("Update"), refresh_icon) },
522 { btnNotifyTest, icons::make_icon_label(_("Test..."), applications_system_icon) },
523 { btnNotifyClose, icons::make_icon_label(_("Close"), close_icon) },
524
525 { btnNotifyDXCCSelect, icons::make_icon_label(_("Select All"), edit_select_all_icon) },
526 { btnNotifyDXCCDeselect, icons::make_icon_label(_("Clear All"), edit_clear_icon) },
527 { btnNotifyDXCCClose, icons::make_icon_label(_("Close"), close_icon) },
528 };
529 for (size_t i = 0; i < sizeof(buttons)/sizeof(*buttons); i++) {
530 buttons[i].button->label(buttons[i].label);
531 icons::set_icon_label(buttons[i].button);
532 buttons[i].button->align(FL_ALIGN_LEFT | FL_ALIGN_INSIDE);
533 }
534 struct { Fl_Button* button; const char** icon; } buttons2[] = {
535 { btnNotifyFilterDXCC, text_editor_icon },
536 { btnNotifyActionDialogDefault, text_icon },
537 { btnNotifyActionMarkerDefault, text_icon },
538 { btnNotifyActionMacro, text_editor_icon },
539 { btnNotifyActionProgram, folder_open_icon }
540 };
541 for (size_t i = 0; i < sizeof(buttons2)/sizeof(*buttons2); i++)
542 buttons2[i].button->image(new Fl_Pixmap(buttons2[i].icon));
543 inpNotifyRE->hide();
544 grpNotifyFilter->deactivate();
545 mnuNotifyEvent->menu(notify_event_menu);
546 btnNotifyEnabled->value(1);
547 inpNotifyRE->callback(notify_re_cb);
548
549 int w = (tblNotifyList->w() - Fl::box_dw(tblNotifyList->box())) / 4;
550 struct col_info_t {
551 const char* label;
552 int width;
553 };
554 col_info_t ncols[] = {
555 { "Event", w + 30 }, { "Filter", w + 30 },
556 { "Action", w - 30 }, { "Enabled", w - 30 },
557 };
558 for (size_t i = 0; i < sizeof(ncols)/sizeof(*ncols); i++) {
559 tblNotifyList->addColumn(ncols[i].label, ncols[i].width,
560 static_cast<Fl_Align>(FL_ALIGN_CENTER | FL_ALIGN_CLIP));
561 }
562 tblNotifyList->rowSize(FL_NORMAL_SIZE);
563 tblNotifyList->headerSize(FL_NORMAL_SIZE);
564 tblNotifyList->allowSort(false);
565 tblNotifyList->when(FL_WHEN_CHANGED | FL_WHEN_NOT_CHANGED);
566 tblNotifyList->menu(notify_list_context_menu);
567 for (int i = 0; i < notify_list_context_menu->size(); i++)
568 icons::set_icon_label(¬ify_list_context_menu[i]);
569
570 w = (tblNotifyFilterDXCC->w() - Fl::box_dw(tblNotifyFilterDXCC->box()) -
571 tblNotifyFilterDXCC->scrollbSize()) / NOTIFY_DXCC_NUMCOL;
572 col_info_t dcols[NOTIFY_DXCC_NUMCOL] = {
573 { "", 25 }, { _("Country"), w + w - 25 + 2*(w - 45) },
574 { _("Continent"), w }, { "ITU", 45 }, { "CQ", 45 }
575 };
576 for (size_t i = 0; i < NOTIFY_DXCC_NUMCOL; i++) {
577 tblNotifyFilterDXCC->addColumn(dcols[i].label, dcols[i].width,
578 static_cast<Fl_Align>(FL_ALIGN_CENTER | FL_ALIGN_CLIP), strcmp);
579 }
580 tblNotifyFilterDXCC->columnAlign(NOTIFY_DXCC_COL_CN, static_cast<Fl_Align>(FL_ALIGN_LEFT | FL_ALIGN_CLIP));
581 tblNotifyFilterDXCC->rowSize(FL_NORMAL_SIZE);
582 tblNotifyFilterDXCC->headerSize(FL_NORMAL_SIZE);
583 tblNotifyFilterDXCC->when(FL_WHEN_CHANGED | FL_WHEN_NOT_CHANGED);
584 tblNotifyFilterDXCC->menu(notify_dxcc_context_menu);
585 btnNotifyDXCCSelect->callback(notify_filter_dxcc_select_cb, (void*)NOTIFY_DXCC_SELECT_ALL);
586 btnNotifyDXCCDeselect->callback(notify_filter_dxcc_select_cb, (void*)NOTIFY_DXCC_DESELECT_ALL);
587 inpNotifyDXCCSearchCountry->callback(notify_filter_dxcc_search);
588 inpNotifyDXCCSearchCountry->when(FL_WHEN_CHANGED | FL_WHEN_NOT_CHANGED | FL_WHEN_ENTER_KEY);
589 inpNotifyDXCCSearchCallsign->callback(notify_filter_dxcc_search);
590 inpNotifyDXCCSearchCallsign->when(FL_WHEN_CHANGED);
591 inpNotifyDXCCSearchCallsign->textfont(FL_HELVETICA);
592
593 inpNotifyActionDialog->textfont(FL_HELVETICA);
594 inpNotifyActionRXMarker->textfont(FL_HELVETICA);
595 inpNotifyActionMacro->textfont(FL_HELVETICA);
596 inpNotifyActionProgram->textfont(FL_HELVETICA);
597 inpNotifyRE->textfont(FL_HELVETICA);
598 inpNotifyFilterCall->textfont(FL_HELVETICA);
599
600 tblNotifyList->callback(notify_select_cb);
601 mnuNotifyEvent->callback(notify_event_cb, NOTIFY_SET_DUP_MENU);
602 btnNotifyFilterDXCC->callback(notify_dxcc_browse_cb);
603 btnNotifyAdd->callback(notify_add_cb);
604 btnNotifyRemove->callback(notify_remove_cb);
605 btnNotifyUpdate->callback(notify_update_cb, (void*)NOTIFY_LIST_MENU_UPDATE);
606 btnNotifyActionDialogDefault->callback(notify_dialog_default_cb);
607 btnNotifyActionMarkerDefault->callback(notify_rx_default_cb);
608 btnNotifyActionProgram->callback(notify_program_select_cb);
609 btnNotifyActionMacro->callback(notify_macro_edit_cb);
610 btnNotifyTest->callback(notify_test_cb);
611 tblNotifyFilterDXCC->callback(notify_dxcc_check_cb);
612 chkNotifyDupIgnore->callback(notify_dup_ignore_cb);
613 mnuNotifyDupWhich->menu(notify_dup_refs_menu);
614
615 chkNotifyFilterCall->value(0);
616 chkNotifyFilterDXCC->value(0);
617 inpNotifyFilterCall->hide();
618 btnNotifyFilterDXCC->hide();
619
620 chkNotifyDupIgnore->value(1);
621 chkNotifyDupIgnore->do_callback();
622 chkNotifyDupBand->value(1);
623 chkNotifyDupMode->value(1);
624 cntNotifyDupTime->value(3600);
625 mnuNotifyEvent->do_callback(); // for the dup menu
626
627 dxcc_list = dxcc_entity_list();
628 if (dxcc_list) {
629 char cq[5], itu[5];
630 for (vector<dxcc*>::const_iterator i = dxcc_list->begin(); i != dxcc_list->end(); ++i) {
631 snprintf(itu, sizeof(itu), "%02d", (*i)->itu_zone);
632 snprintf(cq, sizeof(cq), "%02d", (*i)->cq_zone);
633 tblNotifyFilterDXCC->addRow(NOTIFY_DXCC_NUMCOL, "[x]", (*i)->country,
634 (*i)->continent, itu, cq);
635 }
636 }
637 else {
638 chkNotifyFilterDXCC->deactivate();
639 btnNotifyFilterDXCC->deactivate();
640 }
641
642 unsigned char q = qsl_is_open();
643 if (!(q & (1 << QSL_LOTW)))
644 chkNotifyFilterLOTW->deactivate();
645 if (!(q & (1 << QSL_EQSL)))
646 chkNotifyFilterEQSL->deactivate();
647
648 }
649
650 // append event n to the table widget
notify_table_append(const notify_t & n)651 static void notify_table_append(const notify_t& n)
652 {
653 // add to table
654 string fcol, acol;
655 if (n.event == NOTIFY_EVENT_MYCALL)
656 fcol = "My callsign";
657 else if (n.event == NOTIFY_EVENT_STATION) {
658 if (n.filter.match == NOTIFY_FILTER_CALLSIGN)
659 fcol += "Callsign";
660 else if (n.filter.match == NOTIFY_FILTER_DXCC)
661 fcol += "DXCC";
662 }
663 else if (n.event == NOTIFY_EVENT_CUSTOM)
664 fcol = n.re;
665 if (n.filter.nwb)
666 fcol += ", N";
667 if (n.filter.lotw)
668 fcol += ", L";
669 if (n.filter.eqsl)
670 fcol += ", E";
671
672 if (!n.action.alert.empty())
673 acol = "A";
674 if (!n.action.rx_marker.empty()) {
675 if (!acol.empty())
676 acol += ", ";
677 acol += "RX";
678 }
679 if (!n.action.macro.empty()) {
680 if (!acol.empty())
681 acol += ", ";
682 acol += "TX";
683 }
684 if (!n.action.program.empty()) {
685 if (!acol.empty())
686 acol += ", ";
687 acol += "P";
688 }
689 tblNotifyList->addRow(4, notify_event_menu[n.event].label(),
690 fcol.c_str(), acol.c_str(), n.enabled ? "Y" : "N");
691 tblNotifyList->value(tblNotifyList->rows() - 1);
692 tblNotifyList->redraw();
693 }
694
695 // clear and reload the event table
notify_table_reload(void)696 static void notify_table_reload(void)
697 {
698 tblNotifyList->clear();
699 for (notify_list_t::const_iterator i = notify_list.begin(); i != notify_list.end(); ++i)
700 notify_table_append(*i);
701 }
702
703 ////////////////////////////////////////////////////////////////////////////////
704 // spotter and notification functions
705 ////////////////////////////////////////////////////////////////////////////////
706
707 static notify_dialog *alert_window = 0;
708
notify_goto_freq_cb(Fl_Widget * w,void * arg)709 static void notify_goto_freq_cb(Fl_Widget* w, void* arg)
710 {
711 const notify_t* n = static_cast<notify_t*>(arg);
712 if (progdefaults.rsid_mark) // mark current modem & freq
713 REQ(note_qrg, false, "\nBefore RSID: ", "\n",
714 active_modem->get_mode(), 0LL, active_modem->get_freq());
715 if (active_modem->get_mode() != n->mode)
716 init_modem_sync(n->mode);
717 qsy(n->rfreq, n->afreq);
718 if (n->event == NOTIFY_EVENT_RSID && btnRSID->value() &&
719 progdefaults.rsid_auto_disable && progdefaults.rsid_notify_only)
720 toggleRSID();
721 w->parent()->hide();
722 }
723
notify_alert_window_cb(Fl_Widget * w,void * arg)724 static void notify_alert_window_cb(Fl_Widget* w, void* arg)
725 {
726 delete static_cast<notify_t*>(arg);
727 w->hide();
728 }
729
notify_show_alert(const notify_t & n,const char * msg)730 static void notify_show_alert(const notify_t& n, const char* msg)
731 {
732 if (!alert_window) alert_window = new notify_dialog;
733 if (alert_window->visible())
734 return;
735
736 Fl_Button* goto_freq = alert_window->make_button(120);
737 if (goto_freq) {
738 char label[32];
739 snprintf(label, sizeof(label), "Go to %d Hz", n.afreq);
740 goto_freq->copy_label(label);
741 goto_freq->callback(notify_goto_freq_cb, new notify_t(n));
742 alert_window->callback(notify_alert_window_cb, goto_freq->user_data());
743 }
744
745 alert_window->notify(msg, n.action.alert_timeout);
746 REQ(show_notifier, alert_window);
747 }
748
749 struct replace_refs
750 {
751 enum {
752 REF_MODEM, REF_DF_HZ, REF_RF_HZ, REF_RF_KHZ, REF_AF_HZ,
753 REF_LF_KHZ, REF_CALLSIGN, REF_COUNTRY, REF_MATCHED_TEXT, REF_TEXT
754 };
replace_varreplace_refs755 static void replace_var(const notify_t& n, int t, char* str, size_t len)
756 {
757 switch (t) {
758 case REF_MODEM:
759 strncpy(str, mode_info[n.mode].sname, len-1);
760 str[len - 1] = '\0';
761 break;
762 case REF_DF_HZ:
763 snprintf(str, len, "%lld", wf->rfcarrier());
764 break;
765 case REF_RF_HZ: case REF_RF_KHZ: {
766 long long hz = wf->rfcarrier() + (wf->USB() ? n.afreq : -n.afreq);
767 if (t == REF_RF_HZ)
768 snprintf(str, len, "%lld", hz);
769 else // REF_RF_KHZ
770 snprintf(str, len, "%.3f", (double)hz / 1000.0);
771 break;
772 }
773 case REF_AF_HZ:
774 snprintf(str, len, "%d", n.afreq);
775 break;
776 case REF_LF_KHZ:
777 strncpy(str, inpFreq->value(), len-1);
778 str[len - 1] = '\0';
779 break;
780 case REF_CALLSIGN:
781 if (n.event == NOTIFY_EVENT_MYCALL || n.event == NOTIFY_EVENT_STATION) {
782 stringstream info;
783 info << "\\" << event_regex[n.event].index;
784 strncpy(str, info.str().c_str(), len-1);
785 }
786 break;
787 case REF_COUNTRY:
788 if (n.event == NOTIFY_EVENT_STATION) {
789 strncpy(str, n.filter.dxcc_last.c_str(), len-1);
790 str[len - 1] = '\0';
791 }
792 break;
793 case REF_MATCHED_TEXT:
794 snprintf(str, len, "\\0");
795 break;
796 case REF_TEXT:
797 strncpy(str, n.match_string, len-1);
798 str[len - 1] = '\0';
799 break;
800 }
801 }
802
operator ()replace_refs803 void operator()(const notify_t& n, string& edit)
804 {
805 char buf[128];
806 string::size_type p;
807 // replace $VARIABLES
808 const char* vars[] = {
809 "$MODEM", "$DF_HZ", "$RF_HZ", "$RF_KHZ", "$AF_HZ",
810 "$LF_KHZ", "$CALLSIGN", "$COUNTRY", "$MATCHED_TEXT", "$TEXT"
811 };
812 for (size_t i = 0; i < sizeof(vars)/sizeof(*vars); i++) {
813 if ((p = edit.find(vars[i])) != string::npos) {
814 replace_var(n, i, buf, sizeof(buf));
815 edit.replace(p, strlen(vars[i]), buf);
816 }
817 }
818 // replace \X refs with regex substrings
819 strcpy(buf, "\\0");
820 for (size_t i = 0; i < n.submatch_length; i++, buf[1]++)
821 for (p = 0; (p = edit.find(buf, p)) != string::npos; p = 0)
822 edit.replace(p, 2, n.match_string + n.submatch_offsets[i].rm_so,
823 n.submatch_offsets[i].rm_eo - n.submatch_offsets[i].rm_so);
824 }
825 };
826
827 // perform the actions for event n
notify_notify(const notify_t & n)828 static void notify_notify(const notify_t& n)
829 {
830 // show alert window with timeout
831 if (!n.action.alert.empty()) {
832 string alert = n.action.alert;
833 replace_refs()(n, alert);
834 if (alert.find('%') == string::npos)
835 notify_show_alert(n, alert.c_str());
836 else { // treat alert text as strftime format string
837 size_t len = alert.length() + 256;
838 char* buf = new char[len];
839 time_t t = time(NULL);
840 struct tm ts;
841 if (localtime_r(&t, &ts) && strftime(buf, len, alert.c_str(), &ts))
842 notify_show_alert(n, buf);
843 delete [] buf;
844 }
845 }
846
847 // append to receive text
848 if (!n.action.rx_marker.empty()) {
849 string text = n.action.rx_marker;
850 replace_refs()(n, text);
851 string::size_type p;
852 if ((p = text.find("$RX_MARKER")) != string::npos) {
853 text[p] = '\0';
854 note_qrg(false, text.c_str(), text.c_str() + p + strlen("$RX_MARKER"), n.mode, 0LL, n.afreq);
855 }
856 else
857 ReceiveText->addstr(text);
858 }
859
860 // expand macros and append to transmit text
861 if (!n.action.macro.empty()) {
862 MACROTEXT m;
863 m.text[0] = n.action.macro;
864 replace_refs()(n, m.text[0]);
865 m.execute(0);
866 }
867
868 // define substring & macro variables and run program
869 if (!n.action.program.empty()) {
870 #ifndef __MINGW32__
871 switch (fork()) {
872 case -1:
873 LOG_PERROR("fork");
874 // fall through
875 default:
876 break;
877 case 0:
878 #endif
879 char var[] = "FLDIGI_NOTIFY_STR_0";
880 string val;
881 for (size_t i = 0; i < n.submatch_length; i++, var[sizeof(var) - 2]++) {
882 val.assign(n.match_string + n.submatch_offsets[i].rm_so,
883 n.submatch_offsets[i].rm_eo - n.submatch_offsets[i].rm_so);
884 setenv(var, val.c_str(), 1);
885 if (i == event_regex[n.event].index)
886 setenv("FLDIGI_NOTIFY_CALLSIGN", val.c_str(), 1);
887 }
888 setenv("FLDIGI_NOTIFY_TEXT", n.match_string, 1);
889
890 unsigned int un = n.submatch_length;
891 snprintf(var, sizeof(var), "%u", un);
892
893 setenv("FLDIGI_NOTIFY_STR_NUM", var, 1);
894 snprintf(var, sizeof(var), "%d", n.afreq);
895 setenv("FLDIGI_NOTIFY_AUDIO_FREQUENCY", var, 1);
896 snprintf(var, sizeof(var), "%u", (unsigned)n.event);
897 setenv("FLDIGI_NOTIFY_EVENT", var, 1);
898 if (n.event == NOTIFY_EVENT_STATION)
899 setenv("FLDIGI_NOTIFY_COUNTRY", n.filter.dxcc_last.c_str(), 1);
900
901 set_macro_env(); // also set macro variables
902 #ifdef __MINGW32__
903 char* cmd = strdup(n.action.program.c_str());
904 STARTUPINFO si;
905 PROCESS_INFORMATION pi;
906 memset(&si, 0, sizeof(si));
907 si.cb = sizeof(si);
908 memset(&pi, 0, sizeof(pi));
909 if (!CreateProcess(NULL, cmd, NULL, NULL, FALSE, CREATE_NO_WINDOW, NULL, NULL, &si, &pi))
910 LOG_ERROR("CreateProcess failed with error code %ld", GetLastError());
911 CloseHandle(pi.hProcess);
912 CloseHandle(pi.hThread);
913 free(cmd);
914 #else
915 execl("/bin/sh", "sh", "-c", n.action.program.c_str(), (char *)NULL);
916 perror("execl");
917 exit(EXIT_FAILURE);
918 }
919 #endif
920 }
921 }
922
923 // return true if the event n is a dup
notify_is_dup(notify_t & n,const char * str,const regmatch_t * sub,size_t len,time_t now,int afreq,trx_mode mode)924 static bool notify_is_dup(notify_t& n, const char* str, const regmatch_t* sub, size_t len,
925 time_t now, int afreq, trx_mode mode)
926 {
927 if (!n.dup_ignore)
928 return false;
929
930 if (n.dup_ref == 0 || n.dup_ref >= len) {
931 stringstream info;
932 info << "Bad dup_ref: " << n.dup_ref << " >= " << len;
933 LOG_ERROR("%s", info.str().c_str());
934 return false;
935 }
936 const regmatch_t& subidx = sub[n.dup_ref];
937 string dupstr(subidx.rm_eo - subidx.rm_so, '\0');
938 transform(str + subidx.rm_so, str + subidx.rm_eo, dupstr.begin(), static_cast<int (*)(int)>(toupper));
939
940 notify_dup_t cur = { now, band(wf->rfcarrier()), mode };
941 if (n.event == NOTIFY_EVENT_RSID)
942 cur.freq = wf->rfcarrier() + (wf->USB() ? afreq : -afreq);
943
944 notify_seen_t::iterator i;
945 bool is_dup = false;
946 if ((i = n.last_seen.find(dupstr)) != n.last_seen.end()) {
947 const notify_dup_t& prev = i->second;
948 is_dup = (cur.when - prev.when < n.dup.when);
949 if (n.event == NOTIFY_EVENT_RSID)
950 is_dup = is_dup && ::llabs(cur.freq - prev.freq) <= ceil(RSID_PRECISION);
951 if (n.dup.band)
952 is_dup = is_dup && cur.band == prev.band;
953 if (n.dup.mode)
954 is_dup = is_dup && cur.mode == prev.mode;
955 }
956 if (is_dup)
957 return true;
958
959 n.last_seen[dupstr] = cur;
960
961 if (n.last_seen.size() > 1) { // remove old data
962 for (i = n.last_seen.begin(); i != n.last_seen.end();) {
963 if (now - i->second.when > n.dup.when)
964 n.last_seen.erase(i++);
965 else
966 ++i;
967 }
968 }
969
970 return false;
971 }
972
973 static fre_t notify_filter_call_re("", REG_EXTENDED | REG_NOSUB | REG_ICASE);
974
975 // Called by the spotter when an event's regular expression matches the decoded text.
976 // Also called by notify_rsid for RSID recepction with: the frequency in afreq,
977 // the mode name in str, the str bounds in the sub array, and len = 1.
notify_recv(trx_mode mode,int afreq,const char * str,const regmatch_t * sub,size_t len,void * data)978 static void notify_recv(trx_mode mode, int afreq, const char* str, const regmatch_t* sub, size_t len, void* data)
979 {
980 notify_t* n = reinterpret_cast<notify_t*>(data);
981 time_t now = time(0);
982
983 // check if we may trigger this event
984 if (!n->enabled || now - n->last_trigger < n->action.trigger_limit)
985 return;
986
987 switch (n->event) {
988 case NOTIFY_EVENT_MYCALL: case NOTIFY_EVENT_CUSTOM: case NOTIFY_EVENT_RSID:
989 break;
990 case NOTIFY_EVENT_STATION:
991 size_t re_idx = event_regex[n->event].index;
992 string call(str + sub[re_idx].rm_so, sub[re_idx].rm_eo - sub[re_idx].rm_so);
993 const dxcc* e = dxcc_lookup(call.c_str());
994
995 if (n->filter.match == NOTIFY_FILTER_CALLSIGN) {
996 if (e) // remember the country name we found
997 n->filter.dxcc_last = e->country;
998 if (!n->filter.callsign.empty()) { // the callsign must match
999 if (notify_filter_call_re.re() != n->filter.callsign) // compile new re
1000 notify_filter_call_re.recompile(n->filter.callsign.c_str());
1001 if (!notify_filter_call_re.match(call.c_str()))
1002 return;
1003 }
1004 else { // check for nwb, lotw, eqsl
1005 if (n->filter.nwb && SearchLog(call.c_str()))
1006 return;
1007 if ((n->filter.lotw || n->filter.eqsl) && qsl_is_open() &&
1008 !(qsl_lookup(call.c_str()) & (1 << QSL_LOTW | 1 << QSL_EQSL)))
1009 return;
1010 }
1011 }
1012 else if (n->filter.match == NOTIFY_FILTER_DXCC) {
1013 if (e) {
1014 n->filter.dxcc_last = e->country;
1015
1016 // if the dxcc filter is not empty, it must contain the country for call
1017 if (!n->filter.dxcc.empty() && n->filter.dxcc.find(e->country) == n->filter.dxcc.end())
1018 return;
1019 if (n->filter.nwb && qsodb_dxcc_entity_find(e->country))
1020 return;
1021 }
1022
1023 if ((n->filter.lotw || n->filter.eqsl) && qsl_is_open() &&
1024 !(qsl_lookup(call.c_str()) & (1 << QSL_LOTW | 1 << QSL_EQSL)))
1025 return;
1026 }
1027 break;
1028 }
1029
1030 if (!notify_is_dup(*n, str, sub, len, now, afreq, mode)) {
1031 n->last_trigger = now;
1032 n->afreq = afreq;
1033 n->rfreq = wf->rfcarrier();
1034 n->mode = mode;
1035 n->match_string = str;
1036 n->submatch_offsets = sub;
1037 n->submatch_length = min(len, (size_t)10); // whole string + up to 9 user-specified backrefs
1038
1039 //std::cout << "trigger: " << n->last_trigger << "\n" <<
1040 //"audio freq: " << n->afreq << "\n" <<
1041 //"rf carrier: " << n->rfreq << "\n" <<
1042 //"mode #: " << n->mode << "\n" <<
1043 //"mode: " << mode_info[n->mode].name << "\n" <<
1044 //"match: " << n->match_string << "\n" <<
1045 //"offsets: " << n->submatch_offsets << "\n" <<
1046 //"length: " << n->submatch_length << std::endl;
1047
1048 notify_notify(*n);
1049 }
1050 }
1051
notify_set_qsodb_cache(void)1052 static void notify_set_qsodb_cache(void)
1053 {
1054 bool v = false;
1055 for (notify_list_t::iterator i = notify_list.begin(); i != notify_list.end(); ++i) {
1056 if (i->event == NOTIFY_EVENT_STATION && i->filter.match == NOTIFY_FILTER_DXCC && i->enabled) {
1057 v = true;
1058 break;
1059 }
1060 }
1061 dxcc_entity_cache_enable(v);
1062 }
1063
1064 // register event n with the spotter
notify_register(notify_t & n)1065 static void notify_register(notify_t& n)
1066 {
1067 if (n.event != NOTIFY_EVENT_RSID)
1068 spot_register_recv(notify_recv, &n, notify_get_re(n).c_str(), REG_EXTENDED | REG_ICASE);
1069 }
1070
1071 // unregister event n
notify_unregister(const notify_t & n)1072 static void notify_unregister(const notify_t& n)
1073 {
1074 if (n.event != NOTIFY_EVENT_RSID)
1075 spot_unregister_recv(notify_recv, &n);
1076 }
1077
1078 ////////////////////////////////////////////////////////////////////////////////
1079 // callbacks
1080 ////////////////////////////////////////////////////////////////////////////////
1081
1082 // the event table callback
notify_select_cb(Fl_Widget * w,void * arg)1083 static void notify_select_cb(Fl_Widget* w, void* arg)
1084 {
1085 int v = tblNotifyList->value();
1086 if (v >= 0)
1087 notify_event_to_gui(notify_tmp = *advli(notify_list.begin(), v));
1088 }
1089
1090 // the remove button/menu item callback
notify_remove_cb(Fl_Widget * w,void *)1091 static void notify_remove_cb(Fl_Widget* w, void*)
1092 {
1093 int v = tblNotifyList->value();
1094 if (v < 0)
1095 return;
1096
1097 // unregister
1098 notify_unregister(*advli(notify_list.begin(), v));
1099 // remove from list
1100 notify_list.erase(advli(notify_list.begin(), v));
1101 // remove from table
1102 tblNotifyList->removeRow(tblNotifyList->value());
1103 // select next row
1104 if (w == btnNotifyRemove) {
1105 if (v >= tblNotifyList->rows())
1106 v = tblNotifyList->rows() - 1;
1107 if (v >= 0) {
1108 tblNotifyList->value(v);
1109 notify_select_cb(tblNotifyList, 0);
1110 }
1111 }
1112
1113 notify_save();
1114 }
1115
1116 enum {
1117 NOTIFY_CHECK_CUSTOM_RE_EMPTY = 1 << 0,
1118 NOTIFY_CHECK_CUSTOM_RE_VALID = 1 << 1,
1119 NOTIFY_CHECK_MYCALL_NOT_EMPTY = 1 << 2
1120 };
1121 // do some sanity checks on the widget values before adding/updating an event
notify_check(unsigned check)1122 static bool notify_check(unsigned check)
1123 {
1124 if (mnuNotifyEvent->value() == NOTIFY_EVENT_CUSTOM) {
1125 if (check & NOTIFY_CHECK_CUSTOM_RE_EMPTY) {
1126 if (!inpNotifyRE->size()) {
1127 fl_alert2(_("The regular expression field must not be empty."));
1128 return false;
1129 }
1130 }
1131 if (check & NOTIFY_CHECK_CUSTOM_RE_VALID) {
1132 if (!fre_t(inpNotifyRE->value(), REG_EXTENDED | REG_ICASE)) {
1133 fl_alert2(_("The regular expression must be valid."));
1134 return false;
1135 }
1136 }
1137 }
1138 if ((check & NOTIFY_CHECK_MYCALL_NOT_EMPTY) && mnuNotifyEvent->value() == NOTIFY_EVENT_MYCALL) {
1139 if (progdefaults.myCall.empty()) {
1140 fl_alert2(_("Please set your callsign first."));
1141 return false;
1142 }
1143 }
1144 // ...
1145
1146 return true;
1147 }
1148
1149 // the add button callback
notify_add_cb(Fl_Widget * w,void * arg)1150 static void notify_add_cb(Fl_Widget* w, void* arg)
1151 {
1152 if (!notify_check(~0))
1153 return;
1154
1155 notify_gui_to_event(notify_tmp);
1156 // add to list
1157 notify_list.push_back(notify_tmp);
1158 // add to table
1159 notify_table_append(notify_list.back());
1160 // register with spotter
1161 notify_register(notify_list.back());
1162 // save file
1163 notify_save();
1164 }
1165
1166 // the update button/menu item callback
notify_update_cb(Fl_Widget * w,void * arg)1167 static void notify_update_cb(Fl_Widget* w, void* arg)
1168 {
1169 int v = tblNotifyList->value();
1170 if (v < 0)
1171 return;
1172 if (!notify_check(~0))
1173 return;
1174 notify_t& nv = *advli(notify_list.begin(), v);
1175
1176 if ((intptr_t)arg != NOTIFY_LIST_MENU_TOGGLE) {
1177 notify_gui_to_event(notify_tmp);
1178 nv = notify_tmp;
1179 if (nv.dup_ignore)
1180 nv.last_seen.clear();
1181 }
1182 else { // only toggle the enabled status
1183 nv.enabled = !nv.enabled;
1184 btnNotifyEnabled->value(nv.enabled);
1185 }
1186 if (!nv.enabled)
1187 notify_unregister(nv);
1188 else {
1189 notify_unregister(nv);
1190 notify_register(nv);
1191 }
1192
1193 notify_table_reload();
1194 tblNotifyList->value(v);
1195 notify_save();
1196 }
1197
1198 // the event selection menu callback
notify_event_cb(Fl_Widget * w,void * arg)1199 static void notify_event_cb(Fl_Widget* w, void* arg)
1200 {
1201 notify_event_t e = static_cast<notify_event_t>(reinterpret_cast<Fl_Choice*>(w)->value());
1202 switch (e) {
1203 case NOTIFY_EVENT_MYCALL:
1204 mnuNotifyDupWhich->activate();
1205 chkNotifyDupBand->activate();
1206 chkNotifyDupMode->activate();
1207 grpNotifyFilter->deactivate();
1208 chkNotifyFilterCall->value(0);
1209 inpNotifyFilterCall->hide();
1210 chkNotifyFilterDXCC->value(0);
1211 btnNotifyFilterDXCC->hide();
1212 chkNotifyFilterNWB->value(0);
1213 chkNotifyFilterLOTW->value(0);
1214 chkNotifyFilterEQSL->value(0);
1215 inpNotifyRE->value(0);
1216 inpNotifyRE->hide();
1217 btnNotifyActionDialogDefault->show();
1218 break;
1219 case NOTIFY_EVENT_STATION:
1220 mnuNotifyDupWhich->activate();
1221 chkNotifyDupBand->activate();
1222 chkNotifyDupMode->activate();
1223 grpNotifyFilter->activate();
1224 if (!chkNotifyFilterCall->value() && !chkNotifyFilterDXCC->value()) {
1225 chkNotifyFilterCall->value(1);
1226 inpNotifyFilterCall->show();
1227 }
1228 inpNotifyRE->value(0);
1229 inpNotifyRE->hide();
1230 btnNotifyActionDialogDefault->show();
1231 break;
1232 case NOTIFY_EVENT_CUSTOM:
1233 mnuNotifyDupWhich->activate();
1234 chkNotifyDupBand->activate();
1235 chkNotifyDupMode->activate();
1236 grpNotifyFilter->deactivate();
1237 chkNotifyFilterCall->value(0);
1238 inpNotifyFilterCall->hide();
1239 chkNotifyFilterDXCC->value(0);
1240 btnNotifyFilterDXCC->hide();
1241 chkNotifyFilterNWB->value(0);
1242 chkNotifyFilterLOTW->value(0);
1243 chkNotifyFilterEQSL->value(0);
1244 inpNotifyRE->show();
1245 break;
1246 case NOTIFY_EVENT_RSID:
1247 grpNotifyFilter->deactivate();
1248 chkNotifyFilterCall->value(0);
1249 inpNotifyFilterCall->hide();
1250 chkNotifyFilterDXCC->value(0);
1251 btnNotifyFilterDXCC->hide();
1252 chkNotifyFilterNWB->value(0);
1253 chkNotifyFilterLOTW->value(0);
1254 chkNotifyFilterEQSL->value(0);
1255 inpNotifyRE->value(0);
1256 inpNotifyRE->hide();
1257 btnNotifyActionDialogDefault->show();
1258 // limited dup handling
1259 mnuNotifyDupWhich->deactivate();
1260 chkNotifyDupBand->value(0);
1261 chkNotifyDupBand->deactivate();
1262 chkNotifyDupMode->value(1);
1263 chkNotifyDupMode->deactivate();
1264 break;
1265 }
1266
1267 if (arg == NOTIFY_SET_DUP_MENU) {
1268 notify_gui_to_event(notify_tmp);
1269 notify_set_event_dup(notify_tmp);
1270 }
1271 w->parent()->redraw();
1272 }
1273
1274 // the program filesystem browse button callback
notify_program_select_cb(Fl_Widget * w,void * arg)1275 static void notify_program_select_cb(Fl_Widget* w, void* arg)
1276 {
1277 const char* fn = FSEL::select(_("Run program"), "", 0, 0);
1278 if (!fn) return;
1279 if (!*fn) return;
1280
1281 inpNotifyActionProgram->value(fn);
1282 // quote program path
1283 inpNotifyActionProgram->position(0);
1284 inpNotifyActionProgram->insert("\"", 1);
1285 inpNotifyActionProgram->position(inpNotifyActionProgram->size());
1286 inpNotifyActionProgram->insert("\"", 1);
1287
1288 }
1289
1290 // the test button callback
notify_test_cb(Fl_Widget * w,void * arg)1291 static void notify_test_cb(Fl_Widget* w, void* arg)
1292 {
1293 notify_gui_to_event(notify_tmp);
1294
1295 if (notify_tmp.event == NOTIFY_EVENT_RSID) {
1296 notify_tmp.mode = active_modem->get_mode();
1297 regmatch_t sub[2] = { { 0, (regoff_t)strlen(mode_info[notify_tmp.mode].name) } };
1298 sub[1] = sub[0];
1299 notify_recv(notify_tmp.mode, active_modem->get_freq(),
1300 mode_info[notify_tmp.mode].name, sub, 2, ¬ify_tmp);
1301 return;
1302 }
1303
1304 string test_strings[3];
1305 test_strings[NOTIFY_EVENT_MYCALL].assign(progdefaults.myCall).append(" de n0call");
1306 test_strings[NOTIFY_EVENT_STATION] = "cq de n0call n0call ";
1307 static string test;
1308 if (test.empty())
1309 test = test_strings[notify_tmp.event];
1310
1311 string msg;
1312 msg.assign(_("Default test string is:\n \"")).append(test_strings[notify_tmp.event]).append("\"\n")
1313 .append(_("Enter test string or leave blank for default:"));
1314 const char* s = fl_input2("%s", msg.c_str(), test.c_str());
1315 if (s) {
1316 if (test.assign(s).empty()) // empty input
1317 test = test_strings[notify_tmp.event];
1318 }
1319 else // cancelled
1320 return;
1321
1322 fre_t re(notify_get_re(notify_tmp).c_str(), REG_EXTENDED | REG_ICASE);
1323 if (!re)
1324 fl_alert2(_("This event's regular expression is invalid."));
1325 else if (re.match(test.c_str())) {
1326 const vector<regmatch_t>& o = re.suboff();
1327 notify_recv(active_modem->get_mode(), active_modem->get_freq(),
1328 test.c_str(), &o[0], o.size(), ¬ify_tmp);
1329 }
1330 else
1331 fl_message2(_("The test string did not match this event's search pattern."));
1332 }
1333
1334 // the macro editor button callback
notify_macro_edit_cb(Fl_Widget * w,void * arg)1335 static void notify_macro_edit_cb(Fl_Widget* w, void* arg)
1336 {
1337 editMacro(0, MACRO_EDIT_INPUT, inpNotifyActionMacro);
1338 }
1339
1340 // the insert default alert text button callback
notify_dialog_default_cb(Fl_Widget * w,void * arg)1341 static void notify_dialog_default_cb(Fl_Widget* w, void* arg)
1342 {
1343 size_t i = CLAMP((size_t)mnuNotifyEvent->value(), 0,
1344 sizeof(default_alert_text)/sizeof(*default_alert_text) - 1);
1345 string s = default_alert_text[i];
1346 if (s.empty()) { // custom search; count and list refs
1347 size_t nsub = min(fre_t(inpNotifyRE->value(), REG_EXTENDED | REG_ICASE).nsub(), (size_t)9);
1348 if (nsub) {
1349 s.assign(_("Available substrings")).append(":\n\\0\n");
1350 char ref[] = "\\1";
1351 for (size_t i = 1; i < nsub; i++, ref[1]++)
1352 s.append(ref).append("\n");
1353 }
1354 }
1355 inpNotifyActionDialog->value(s.c_str());
1356 }
1357
1358 // the insert default RX text button callback
notify_rx_default_cb(Fl_Widget * w,void * arg)1359 static void notify_rx_default_cb(Fl_Widget* w, void* arg)
1360 {
1361 if (mnuNotifyEvent->value() == NOTIFY_EVENT_RSID)
1362 inpNotifyActionRXMarker->value("\nRSID: $RX_MARKER\n");
1363 else
1364 inpNotifyActionRXMarker->value("\n$RX_MARKER\n");
1365 }
1366
1367 // the ignore duplicates check button callback
notify_dup_ignore_cb(Fl_Widget * w,void * arg)1368 void notify_dup_ignore_cb(Fl_Widget* w, void* arg)
1369 {
1370 if (static_cast<Fl_Check_Button*>(w)->value()) {
1371 mnuNotifyDupWhich->show();
1372 cntNotifyDupTime->show();
1373 chkNotifyDupBand->show();
1374 chkNotifyDupMode->show();
1375 }
1376 else {
1377 mnuNotifyDupWhich->hide();
1378 cntNotifyDupTime->hide();
1379 chkNotifyDupBand->hide();
1380 chkNotifyDupMode->hide();
1381 }
1382 w->parent()->redraw();
1383 }
1384
1385 // the custom re field callback
notify_re_cb(Fl_Widget * w,void * arg)1386 static void notify_re_cb(Fl_Widget* w, void* arg)
1387 {
1388 notify_set_event_dup_menu(static_cast<Fl_Input*>(w)->value());
1389 }
1390
1391 ////////////////////////////////////////////////////////////////////////////////
1392 // dup widget handling
1393 ////////////////////////////////////////////////////////////////////////////////
1394
1395 // set the dup menu substrings for regular epxression string re
notify_set_event_dup_menu(const char * re)1396 static void notify_set_event_dup_menu(const char* re)
1397 {
1398 int v = mnuNotifyDupWhich->value();
1399 size_t nref = fre_t(re, REG_EXTENDED).nsub();
1400 for (size_t i = 0; i < sizeof(notify_dup_refs_menu)/sizeof(*notify_dup_refs_menu); i++)
1401 notify_dup_refs_menu[i].hide();
1402 if (nref) {
1403 size_t i;
1404 for (i = 1; i < nref; i++)
1405 notify_dup_refs_menu[i].show();
1406 if ((size_t)v == nref)
1407 v = mnuNotifyDupWhich->size() - 1;
1408 else
1409 v = 1;
1410 }
1411 else
1412 v = 0;
1413 mnuNotifyDupWhich->value(v);
1414 mnuNotifyDupWhich->redraw();
1415 }
1416
1417 // set the dup group widgets for event n
notify_set_event_dup(const notify_t & n)1418 static void notify_set_event_dup(const notify_t& n)
1419 {
1420 size_t i;
1421 for (i = 0; i < sizeof(notify_dup_refs_menu)/sizeof(*notify_dup_refs_menu); i++)
1422 notify_dup_refs_menu[i].hide();
1423
1424 switch (n.event) {
1425 case NOTIFY_EVENT_MYCALL: case NOTIFY_EVENT_STATION: case NOTIFY_EVENT_RSID:
1426 i = event_regex[n.event].index;
1427 mnuNotifyDupWhich->menu(notify_dup_callsign_menu);
1428 for (size_t j = 0; j < sizeof(notify_dup_callsign_menu)/sizeof(*notify_dup_callsign_menu) - 1; j++)
1429 notify_dup_callsign_menu[j].hide();
1430 notify_dup_callsign_menu[i].show();
1431 if (n.event == NOTIFY_EVENT_RSID)
1432 notify_dup_callsign_menu[i].label(_("Frequency"));
1433 else
1434 notify_dup_callsign_menu[i].label(_("Callsign"));
1435 mnuNotifyDupWhich->value(i);
1436 break;
1437 case NOTIFY_EVENT_CUSTOM:
1438 mnuNotifyDupWhich->menu(notify_dup_refs_menu);
1439 notify_set_event_dup_menu(notify_get_re(n).c_str());
1440 break;
1441 }
1442 }
1443
1444 ////////////////////////////////////////////////////////////////////////////////
1445 // DXCC list
1446 ////////////////////////////////////////////////////////////////////////////////
1447
1448 // set the toggle status of row i to cond
notify_dxcc_row_check(int i,bool cond=true)1449 static void notify_dxcc_row_check(int i, bool cond = true)
1450 {
1451 *tblNotifyFilterDXCC->valueAt(i, NOTIFY_DXCC_COL_SEL) = "["[!cond];
1452 }
1453
1454 // toggle the checked status of row i, return new status
notify_dxcc_row_toggle(int i)1455 static bool notify_dxcc_row_toggle(int i)
1456 {
1457 char* p = tblNotifyFilterDXCC->valueAt(i, NOTIFY_DXCC_COL_SEL);
1458 return (*p = "["[!!*p]);
1459 }
1460
1461 // return checked status of row i
notify_dxcc_row_checked(int i)1462 static bool notify_dxcc_row_checked(int i)
1463 {
1464 return *tblNotifyFilterDXCC->valueAt(i, NOTIFY_DXCC_COL_SEL);
1465 }
1466
1467 // the dxcc list select/deselect callback
notify_filter_dxcc_select_cb(Fl_Widget * w,void * arg)1468 static void notify_filter_dxcc_select_cb(Fl_Widget* w, void* arg)
1469 {
1470
1471 bool val;
1472 const char* str;
1473 int row = tblNotifyFilterDXCC->value();
1474 int col;
1475
1476 intptr_t iarg = (intptr_t)arg;
1477 switch (iarg) {
1478 case NOTIFY_DXCC_SELECT_CONT: case NOTIFY_DXCC_DESELECT_CONT:
1479 str = tblNotifyFilterDXCC->valueAt(row, col = NOTIFY_DXCC_COL_CT);
1480 val = (iarg == NOTIFY_DXCC_SELECT_CONT);
1481 break;
1482 case NOTIFY_DXCC_SELECT_ITU: case NOTIFY_DXCC_DESELECT_ITU:
1483 str = tblNotifyFilterDXCC->valueAt(row, col = NOTIFY_DXCC_COL_ITU);
1484 val = (iarg == NOTIFY_DXCC_SELECT_ITU);
1485 break;
1486 case NOTIFY_DXCC_SELECT_CQ: case NOTIFY_DXCC_DESELECT_CQ:
1487 str = tblNotifyFilterDXCC->valueAt(row, col = NOTIFY_DXCC_COL_CQ);
1488 val = (iarg == NOTIFY_DXCC_SELECT_CQ);
1489 break;
1490
1491 case NOTIFY_DXCC_SELECT_ALL:
1492 for (int i = 0; i < tblNotifyFilterDXCC->rows(); i++)
1493 notify_dxcc_row_check(i);
1494 goto redraw;
1495 case NOTIFY_DXCC_DESELECT_ALL:
1496 for (int i = 0; i < tblNotifyFilterDXCC->rows(); i++)
1497 notify_dxcc_row_check(i, false);
1498 goto redraw;
1499
1500 default:
1501 return;
1502 }
1503
1504 for (int i = 0; i < tblNotifyFilterDXCC->rows(); i++) {
1505 if (!strcmp(tblNotifyFilterDXCC->valueAt(i, col), str))
1506 notify_dxcc_row_check(i, val);
1507 }
1508
1509 redraw:
1510 tblNotifyFilterDXCC->redraw();
1511 }
1512
1513 // the dxcc search field callback
notify_filter_dxcc_search(Fl_Widget * w,void * arg)1514 static void notify_filter_dxcc_search(Fl_Widget* w, void* arg)
1515 {
1516 if (w == inpNotifyDXCCSearchCallsign) {
1517 const dxcc* e = dxcc_lookup(inpNotifyDXCCSearchCallsign->value());
1518 if (e) {
1519 inpNotifyDXCCSearchCountry->value(e->country);
1520 inpNotifyDXCCSearchCountry->do_callback();
1521 inpNotifyDXCCSearchCountry->position(0);
1522 }
1523 return;
1524 }
1525
1526 if (unlikely(!inpNotifyDXCCSearchCountry->size()))
1527 return;
1528 int col = 1, row = tblNotifyFilterDXCC->value() + 1;
1529 row = WCLAMP(row, 0, tblNotifyFilterDXCC->rows() - 1);
1530 if (tblNotifyFilterDXCC->search(row, col, false, inpNotifyDXCCSearchCountry->value())) {
1531 int when = tblNotifyFilterDXCC->when();
1532 tblNotifyFilterDXCC->when(FL_WHEN_NEVER);
1533 tblNotifyFilterDXCC->GotoRow(row);
1534 tblNotifyFilterDXCC->when(when);
1535 inpNotifyDXCCSearchCountry->textcolor(FL_FOREGROUND_COLOR);
1536 }
1537 else
1538 inpNotifyDXCCSearchCountry->textcolor(FL_RED);
1539 inpNotifyDXCCSearchCountry->redraw();
1540 }
1541
1542 // the dxcc filter selection button callback
notify_dxcc_browse_cb(Fl_Widget * w,void * arg)1543 static void notify_dxcc_browse_cb(Fl_Widget* w, void* arg)
1544 {
1545 int v = tblNotifyList->value();
1546 if (v < 0) // no selection, uncheck all rows
1547 btnNotifyDXCCDeselect->do_callback();
1548 else {
1549 const notify_t& n = *advli(notify_list.begin(), v);
1550 for (int i = 0; i < tblNotifyFilterDXCC->rows(); i++)
1551 notify_dxcc_row_check(i, n.filter.dxcc.find(tblNotifyFilterDXCC->valueAt(i, NOTIFY_DXCC_COL_CN))
1552 != n.filter.dxcc.end());
1553 }
1554 notify_dxcc_show(false);
1555 }
1556
1557 // the dxcc table callback
notify_dxcc_check_cb(Fl_Widget * w,void * arg)1558 static void notify_dxcc_check_cb(Fl_Widget* w, void* arg)
1559 {
1560 if (Fl::event_button() != FL_LEFT_MOUSE || (Fl::event() == FL_KEYDOWN && !Fl::event_shift()))
1561 return;
1562 int sel = tblNotifyFilterDXCC->value();
1563 const char* country = tblNotifyFilterDXCC->valueAt(sel, NOTIFY_DXCC_COL_CN);
1564 if (notify_dxcc_row_toggle(sel))
1565 notify_tmp.filter.dxcc[country] = true;
1566 else
1567 notify_tmp.filter.dxcc.erase(country);
1568 }
1569
1570 ////////////////////////////////////////////////////////////////////////////////
1571 // storage functions
1572 ////////////////////////////////////////////////////////////////////////////////
1573
1574 // save the event list
notify_save(void)1575 static void notify_save(void)
1576 {
1577 notify_set_qsodb_cache();
1578
1579 remove(string(HomeDir).append("/").append("notify.prefs").c_str());
1580 Fl_Preferences ndata(HomeDir.c_str(), PACKAGE_TARNAME, "notify");
1581 ndata.set("items", static_cast<int>(notify_list.size()));
1582
1583 size_t num = 0;
1584 stringstream group;
1585 for (notify_list_t::iterator i = notify_list.begin(); i != notify_list.end(); ++i) {
1586 group << "item" << num++;
1587
1588 ndata.set(Fl_Preferences::Name("%s/event", group.str().c_str()), i->event);
1589 if (i->event >= NOTIFY_EVENT_CUSTOM)
1590 ndata.set(Fl_Preferences::Name("%s/re", group.str().c_str()), i->re.c_str());
1591 ndata.set(Fl_Preferences::Name("%s/enabled", group.str().c_str()), i->enabled);
1592
1593 ndata.set(Fl_Preferences::Name("%s/action/alert", group.str().c_str()), i->action.alert.c_str());
1594 ndata.set(Fl_Preferences::Name("%s/action/rx_marker", group.str().c_str()), i->action.rx_marker.c_str());
1595 ndata.set(Fl_Preferences::Name("%s/action/macro", group.str().c_str()), i->action.macro.c_str());
1596 ndata.set(Fl_Preferences::Name("%s/action/program", group.str().c_str()), i->action.program.c_str());
1597 ndata.set(Fl_Preferences::Name("%s/action/trigger_limit", group.str().c_str()),
1598 static_cast<int>(i->action.trigger_limit));
1599 ndata.set(Fl_Preferences::Name("%s/action/alert_timeout", group.str().c_str()),
1600 static_cast<int>(i->action.alert_timeout));
1601
1602 ndata.set(Fl_Preferences::Name("%s/filter/match", group.str().c_str()), i->filter.match);
1603 ndata.set(Fl_Preferences::Name("%s/filter/callsign", group.str().c_str()), i->filter.callsign.c_str());
1604 ndata.set(Fl_Preferences::Name("%s/filter/nwb", group.str().c_str()), i->filter.nwb);
1605 ndata.set(Fl_Preferences::Name("%s/filter/lotw", group.str().c_str()), i->filter.lotw);
1606 ndata.set(Fl_Preferences::Name("%s/filter/eqsl", group.str().c_str()), i->filter.eqsl);
1607 int k = 0;
1608 for (notify_filter_dxcc_t::const_iterator j = i->filter.dxcc.begin();
1609 j != i->filter.dxcc.end() && j->second; ++j) {
1610 ndata.set(Fl_Preferences::Name("%s/filter/dxcc/%d", group.str().c_str(), k++), j->first.c_str());
1611 }
1612
1613 ndata.set(Fl_Preferences::Name("%s/dup/ignore", group.str().c_str()), i->dup_ignore);
1614 ndata.set(Fl_Preferences::Name("%s/dup/ref", group.str().c_str()), static_cast<int>(i->dup_ref));
1615 ndata.set(Fl_Preferences::Name("%s/dup/when", group.str().c_str()), static_cast<int>(i->dup.when));
1616 ndata.set(Fl_Preferences::Name("%s/dup/band", group.str().c_str()), i->dup.band);
1617 ndata.set(Fl_Preferences::Name("%s/dup/mode", group.str().c_str()), static_cast<int>(i->dup.mode));
1618 }
1619 }
1620
1621 // load the event list
notify_load(void)1622 static void notify_load(void)
1623 {
1624 int x;
1625 char s[512];
1626 s[sizeof(s)-1] = '\0';
1627
1628 Fl_Preferences ndata(HomeDir.c_str(), PACKAGE_TARNAME, "notify");
1629 if (!ndata.get("items", x, 0) || x <= 0)
1630 return;
1631 size_t n = static_cast<size_t>(x);
1632
1633 stringstream group;
1634 for (size_t i = 0; i < n; i++) {
1635 notify_t nitem;
1636 group << "item" << i;
1637
1638 ndata.get(Fl_Preferences::Name("%s/event", group.str().c_str()), x, 0);
1639 nitem.event = static_cast<notify_event_t>(x);
1640 nitem.last_trigger = 0;
1641 if (nitem.event >= NOTIFY_EVENT_CUSTOM) {
1642 ndata.get(Fl_Preferences::Name("%s/re", group.str().c_str()), s, "", sizeof(s)-1);
1643 nitem.re = s;
1644 }
1645 else
1646 nitem.re = event_regex[nitem.event].regex;
1647 ndata.get(Fl_Preferences::Name("%s/enabled", group.str().c_str()), x, 1);
1648 nitem.enabled = x;
1649
1650 ndata.get(Fl_Preferences::Name("%s/action/alert", group.str().c_str()), s, "", sizeof(s)-1);
1651 nitem.action.alert = s;
1652 ndata.get(Fl_Preferences::Name("%s/action/macro", group.str().c_str()), s, "", sizeof(s)-1);
1653 nitem.action.macro = s;
1654 ndata.get(Fl_Preferences::Name("%s/action/rx_marker", group.str().c_str()), s, "", sizeof(s)-1);
1655 nitem.action.rx_marker = s;
1656 ndata.get(Fl_Preferences::Name("%s/action/program", group.str().c_str()), s, "", sizeof(s)-1);
1657 nitem.action.program = s;
1658 ndata.get(Fl_Preferences::Name("%s/action/trigger_limit", group.str().c_str()), x, 0);
1659 nitem.action.trigger_limit = x;
1660 ndata.get(Fl_Preferences::Name("%s/action/alert_timeout", group.str().c_str()), x, 5);
1661 nitem.action.alert_timeout = x;
1662
1663 ndata.get(Fl_Preferences::Name("%s/filter/match", group.str().c_str()), x, 0);
1664 nitem.filter.match = static_cast<notify_filter_match_t>(x);
1665 ndata.get(Fl_Preferences::Name("%s/filter/callsign", group.str().c_str()), s, "", sizeof(s)-1);
1666 nitem.filter.callsign = s;
1667 ndata.get(Fl_Preferences::Name("%s/filter/nwb", group.str().c_str()), x, 0);
1668 nitem.filter.nwb = x;
1669 ndata.get(Fl_Preferences::Name("%s/filter/lotw", group.str().c_str()), x, 0);
1670 nitem.filter.lotw = x;
1671 ndata.get(Fl_Preferences::Name("%s/filter/eqsl", group.str().c_str()), x, 0);
1672 nitem.filter.eqsl = x;
1673 for (int k = 0; ndata.get(Fl_Preferences::Name("%s/filter/dxcc/%d", group.str().c_str(), k), s, "", sizeof(s)-1); k++)
1674 nitem.filter.dxcc[s] = true;
1675
1676 ndata.get(Fl_Preferences::Name("%s/dup/ignore", group.str().c_str()), x, 0);
1677 nitem.dup_ignore = x;
1678 ndata.get(Fl_Preferences::Name("%s/dup/ref", group.str().c_str()), x, 0);
1679 nitem.dup_ref = x;
1680 ndata.get(Fl_Preferences::Name("%s/dup/when", group.str().c_str()), x, 600);
1681 nitem.dup.when = x;
1682 ndata.get(Fl_Preferences::Name("%s/dup/band", group.str().c_str()), x, 0);
1683 nitem.dup.band = static_cast<band_t>(x);
1684 ndata.get(Fl_Preferences::Name("%s/dup/mode", group.str().c_str()), x, 0);
1685 nitem.dup.mode = static_cast<trx_mode>(x);
1686
1687 notify_list.push_back(nitem);
1688 }
1689
1690 notify_table_reload();
1691 }
1692