1 /*
2  * Tlf - contest logging program for amateur radio operators
3  * Copyright (C) 2011 Thomas Beierlein <tb@forth-ev.de>
4  *
5  * This program is free software; you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation; either version 2 of the License, or
8  * (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program; if not, write to the Free Software
17  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
18  */
19 
20 
21 #include <ctype.h>
22 #include <pthread.h>
23 #include <string.h>
24 #include <stdlib.h>
25 #include <stdio.h>
26 #include <sys/time.h>
27 #include <math.h>
28 
29 #include "bandmap.h"
30 #include "qtcutil.h"
31 #include "qtcvars.h"		// Includes globalvars.h
32 #include "searchcallarray.h"
33 #include "searchlog.h"
34 #include "tlf_curses.h"
35 #include "ui_utils.h"
36 #include "getctydata.h"
37 #include "dxcc.h"
38 #include "initial_exchange.h"
39 #include "bands.h"
40 
41 #define TOLERANCE 100 		/* spots with a QRG +/-TOLERANCE
42 				   will be counted as the same QRG */
43 
44 #define SPOT_COLUMN_WIDTH 22
45 #define SPOT_FREQ_WIDTH 7
46 #define SPOT_CALL_WIDTH SPOT_COLUMN_WIDTH-SPOT_FREQ_WIDTH-4     // 3 spaces before and 1 after call
47 
48 #define DISTANCE(x, y) \
49     ( x < y ? y - x : x -y )
50 
51 #define TOPLINE 14
52 #define LASTLINE (LINES - 2)
53 
54 #define LINELENGTH 80
55 #define COLUMNS ((LINELENGTH - 14) / SPOT_COLUMN_WIDTH)
56 #define NR_SPOTS ((LASTLINE - TOPLINE + 1) * COLUMNS)
57 
58 pthread_mutex_t bm_mutex = PTHREAD_MUTEX_INITIALIZER;
59 
60 /** \brief sorted list of all recent DX spots
61  */
62 GList *allspots = NULL;
63 
64 /** \brief sorted list of filtered spots
65  */
66 GPtrArray *spots;
67 
68 
69 bm_config_t bm_config = {
70     1,	/* show all bands */
71     1,  /* show all mode */
72     1,  /* show dupes */
73     1,	/* skip dupes during grab */
74     900,/* default livetime */
75     0  /* DO NOT show ONLY multipliers */
76 };
77 short	bm_initialized = 0;
78 
79 extern freq_t freq;
80 extern int trx_control;
81 extern int bandinx;
82 extern int trxmode;
83 extern char thisnode;
84 extern struct worked_t worked[];
85 extern int contest;
86 extern char whichcontest[];
87 
88 char *qtc_format(char *call);
89 
90 gint cmp_freq(spot *a, spot *b);
91 
92 /*
93  * write bandmap spots to a file
94  */
bmdata_write_file()95 void bmdata_write_file() {
96 
97     FILE *fp;
98     spot *sp;
99     GList *found;
100     struct timeval tv;
101 
102     if ((fp = fopen(".bmdata.dat", "w")) == NULL) {
103 	attron(modify_attr(COLOR_PAIR(CB_DUPE) | A_BOLD));
104 	mvprintw(13, 29, "can't open bandmap data file!");
105 	refreshp();
106 	return;
107     }
108 
109     gettimeofday(&tv, NULL);
110 
111     pthread_mutex_lock(&bm_mutex);
112 
113     found = allspots;
114     fprintf(fp, "%d\n", (int)tv.tv_sec);
115     while (found != NULL) {
116 	sp = found->data;
117 	fprintf(fp, "%s;%d;%d;%d;%c;%u;%d;%d;%d;%s\n",
118 		sp->call, sp->freq, sp->mode, sp->band,
119 		sp->node, (int)sp->timeout, sp->dupe, sp->cqzone,
120 		sp->ctynr, g_strchomp(sp->pfx));
121 	found = found->next;
122     }
123 
124     pthread_mutex_unlock(&bm_mutex);
125 
126     fclose(fp);
127 }
128 
129 /*
130  * read bandmap spots from file, put them to allspots list
131  */
bmdata_read_file()132 void bmdata_read_file() {
133     FILE *fp;
134     struct timeval tv;
135     int timediff, last_bm_save_time, fc;
136     char line[50], *token;
137     static int bmdata_parsed = 0;
138 
139     if ((fp = fopen(".bmdata.dat", "r")) != NULL && bmdata_parsed == 0) {
140 	bmdata_parsed = 1;
141 	if (fgets(line, 50, fp)) {
142 	    sscanf(line, "%d", &last_bm_save_time);
143 	    gettimeofday(&tv, NULL);
144 	    timediff = (int)tv.tv_sec - last_bm_save_time;
145 	    if (timediff < 0)
146 		timediff = 0;
147 
148 	    while (fgets(line, 50, fp)) {
149 		spot *entry = g_new0(spot, 1);
150 		fc = 0;
151 		token = strtok(line, ";");
152 		while (token != NULL) {
153 		    switch (fc) {
154 			case 0:		entry -> call = g_strdup(token);
155 			    break;
156 			case 1:		sscanf(token, "%d", &entry->freq);
157 			    break;
158 			case 2:		sscanf(token, "%hhd", &entry->mode);
159 			    break;
160 			case 3:		sscanf(token, "%hd", &entry->band);
161 			    break;
162 			case 4:		sscanf(token, "%c", &entry->node);
163 			    break;
164 			case 5:		sscanf(token, "%u", &entry->timeout);
165 			    break;
166 			case 6:		sscanf(token, "%hhd", &entry->dupe);
167 			    break;
168 			case 7:		sscanf(token, "%u", &entry->cqzone);
169 			    break;
170 			case 8:		sscanf(token, "%u", &entry->ctynr);
171 			    break;
172 			case 9:		entry->pfx = g_strdup(token);
173 			    break;
174 		    }
175 		    fc++;
176 		    token = strtok(NULL, ";");
177 		}
178 		if (entry->timeout > timediff) {
179 		    entry->timeout -= timediff;	/* remaining time */
180 		    allspots = g_list_insert_sorted(allspots, entry, (GCompareFunc)cmp_freq);
181 		} else {
182 		    g_free(entry);
183 		}
184 	    }
185 	}
186 	fclose(fp);
187     }
188 }
189 
190 /** \brief initialize bandmap
191  *
192  * initalize colors and data structures for bandmap operation
193  */
bm_init()194 void bm_init() {
195 
196     pthread_mutex_lock(&bm_mutex);
197 
198     init_pair(CB_NEW, COLOR_CYAN, COLOR_WHITE);
199     init_pair(CB_NORMAL, COLOR_BLUE, COLOR_WHITE);
200     init_pair(CB_DUPE, COLOR_BLACK, COLOR_WHITE);
201     init_pair(CB_OLD, COLOR_YELLOW, COLOR_WHITE);
202     init_pair(CB_MULTI, COLOR_WHITE, COLOR_BLUE);
203 
204     spots = g_ptr_array_sized_new(128);
205 
206     bmdata_read_file();
207 
208     pthread_mutex_unlock(&bm_mutex);
209 }
210 
211 
212 /** \brief guess mode based on frequency
213  *
214  * \return CWMODE, DIGIMODE or SSBMODE
215  */
freq2mode(freq_t freq,int band)216 int freq2mode(freq_t freq, int band) {
217     if (freq <= cwcorner[band])
218 	return CWMODE;
219     else if (freq < ssbcorner[band])
220 	return DIGIMODE;
221     else
222 	return SSBMODE;
223 }
224 
225 
226 
227 /** \brief add DX spot message to bandmap
228  *
229  * check if cluster message is a dx spot,
230  * if so split it into pieces and insert in spot list */
bm_add(char * s)231 void bm_add(char *s) {
232     char *line;
233     char *call;
234     char node = ' ';
235 
236     line = g_strdup(s);
237     if (strncmp(line, "DX de ", 6) != 0) {
238 	g_free(line);
239 	return;
240     }
241 
242     if ((call = strtok(line + 26, " \t")) == NULL) {
243 	g_free(line);
244 	return;
245     }
246 
247     if (strncmp(line + 6, "TLF-", 4) == 0)
248 	node = line[10];		/* get sending node id */
249 
250     bandmap_addspot(call, atof(line + 16) * 1000, node);
251     g_free(line);
252 }
253 
254 
255 /* compare functions to search in list */
cmp_call(spot * ldata,char * call)256 gint	cmp_call(spot *ldata, char *call) {
257 
258     return g_strcmp0(ldata->call, call);
259 }
260 
cmp_freq(spot * a,spot * b)261 gint	cmp_freq(spot *a, spot *b) {
262     unsigned int af = a->freq;
263     unsigned int bf = b->freq;
264 
265     if (af < bf)  return -1;
266     if (af > bf)  return  1;
267     return 0;
268 }
269 
270 /** add a new spot to bandmap data
271  * \param call  	the call to add
272  * \param freq 		on which frequency heard
273  * \param node		reporting node
274  */
bandmap_addspot(char * call,freq_t freq,char node)275 void bandmap_addspot(char *call, freq_t freq, char node) {
276     /* - if a spot on that band and mode is already in list replace old entry
277      *   with new one and set timeout to SPOT_NEW,
278      *   otherwise add it to the list as new
279      * - if other call on same frequency (with some TOLERANCE) replace it and set
280      *   timeout to SPOT_NEW
281      * - all frequencies from cluster are rounded to 100 Hz,
282      *   remember all other frequencies exactly
283      *   but display only rounded to 100 Hz - sort exact
284      */
285     GList *found;
286     int band;
287     char mode;
288     dxcc_data *dxccdata;
289     int dxccindex;
290     int wi;
291     char *lastexch;
292     extern struct ie_list *main_ie_list;
293     struct ie_list *current_ie;
294 
295     /* add only HF spots */
296     if (freq > 30000000)
297 	return;
298 
299     band = freq2band(freq);
300     if (band == BANDINDEX_OOB)  /* no ham band */
301 	return;
302 
303     mode = freq2mode(freq, band);
304 
305     /* acquire bandmap mutex */
306     pthread_mutex_lock(&bm_mutex);
307 
308     /* look if call is already on list in that mode and band */
309     /* each call is allowed in every combination of band and mode
310      * but only once */
311     found = g_list_find_custom(allspots, call, (GCompareFunc)cmp_call);
312 
313     while (found != NULL) {
314 
315 	/* if same band and mode -> found spot already in list */
316 	if (((spot *)found->data)->band == band &&
317 		((spot *)found->data)->mode == mode)
318 	    break;
319 
320 	found = g_list_find_custom(found->next, call, (GCompareFunc)cmp_call);
321     }
322 
323     /* if already in list on that band and mode
324      * 		-> set timeout to SPOT_NEW, and set new freq and reporting node
325      *   		if freq has changed enough sort list anew by freq
326      */
327     if (found) {
328 	((spot *)found->data)->timeout = SPOT_NEW;
329 	((spot *)found->data)->node = node;
330 	if (DISTANCE(((spot *)found->data)->freq, freq) > TOLERANCE) {
331 	    ((spot *)found->data)->freq = freq;
332 	    allspots = g_list_sort(allspots, (GCompareFunc)cmp_freq);
333 	}
334     } else {
335 	/* if not in list already -> prepare new entry and
336 	 * insert in list at correct freq */
337 	spot *entry = g_new(spot, 1);
338 	entry -> call = g_strdup(call);
339 	entry -> freq = freq;
340 	entry -> mode = mode;
341 	entry -> band = band;
342 	entry -> node = node;
343 	entry -> timeout = SPOT_NEW;
344 	entry -> dupe = 0;	/* Dupe will be determined later. */
345 
346 	lastexch = NULL;
347 	dxccindex = getctynr(entry->call);
348 	if (cqww == 1) {
349 	    // check if the callsign exists in worked list
350 	    wi = searchcallarray(call);
351 	    if (wi >= 0) {
352 		lastexch = g_strdup(worked[wi].exchange);
353 	    }
354 
355 	    if (lastexch == NULL && main_ie_list != NULL) {
356 		current_ie = main_ie_list;
357 
358 		while (current_ie) {
359 		    if (strcmp(call, current_ie->call) == 0) {
360 			lastexch = g_strdup(current_ie->exchange);
361 			break;
362 		    }
363 		    current_ie = current_ie->next;
364 		}
365 	    }
366 	}
367 	if (dxccindex > 0) {
368 	    dxccdata = dxcc_by_index(dxccindex);
369 	    entry -> cqzone = dxccdata->cq;
370 	    if (lastexch != NULL) {
371 		entry -> cqzone = atoi(lastexch);
372 		g_free(lastexch);
373 	    }
374 	    entry -> ctynr = dxccindex;
375 	    entry -> pfx = g_strdup(dxccdata->pfx);
376 	} else {
377 	    entry -> cqzone = 0;
378 	    entry -> ctynr = 0;
379 	    entry -> pfx = g_strdup("");
380 	}
381 	allspots = g_list_insert_sorted(allspots, entry, (GCompareFunc)cmp_freq);
382 	/* lookup where it is */
383 	found = g_list_find(allspots, entry);
384     }
385 
386     /* check that spot is unique on freq +/- TOLERANCE Hz,
387      * drop other entries if needed */
388     if (found->prev &&
389 	    (DISTANCE(((spot *)(found->prev)->data)->freq, freq) < TOLERANCE)) {
390 	spot *olddata;
391 	olddata = found->prev->data;
392 	allspots = g_list_remove_link(allspots, found->prev);
393 	g_free(olddata->call);
394 	g_free(olddata->pfx);
395 	g_free(olddata);
396     }
397     if (found->next &&
398 	    (DISTANCE(((spot *)(found->next)->data)->freq, freq) < TOLERANCE)) {
399 	spot *olddata;
400 	olddata = found->next->data;
401 	allspots = g_list_remove_link(allspots, found->next);
402 	g_free(olddata->call);
403 	g_free(olddata->pfx);
404 	g_free(olddata);
405     }
406 
407 
408     pthread_mutex_unlock(&bm_mutex);
409 }
410 
411 
bandmap_age()412 void bandmap_age() {
413     /*
414      * go through all entries
415      *   + decrement timeout
416      *   + set state to new, normal, aged or dead
417      *   + if dead -> drop it from collection
418      */
419 
420     pthread_mutex_lock(&bm_mutex);
421 
422     GList *list = allspots;
423 
424     while (list) {
425 	spot *data = list->data;
426 	GList *temp = list;
427 	list = list->next;
428 	if (data->timeout) {
429 	    data->timeout--;
430 	}
431 	if (data->timeout == 0) {
432 	    allspots = g_list_remove_link(allspots, temp);
433 	    g_free(data->call);
434 	    g_free(data->pfx);
435 	    g_free(data);
436 	}
437     }
438 
439     pthread_mutex_unlock(&bm_mutex);
440 }
441 
442 
443 /** check if call is new multi
444  *
445  * \return true if new multi
446  */
bm_ismulti(char * call,spot * data,int band)447 bool bm_ismulti(char *call, spot *data, int band) {
448 
449     if (data == NULL || data->cqzone <= 0 || data->ctynr <= 0) {
450 	return false;   // no data
451     }
452 
453     if (cqww == 1) {
454 	if ((zones[data->cqzone] & inxes[band]) == 0
455 		|| (countries[data->ctynr] & inxes[band]) == 0) {
456 	    return true;
457 	}
458     }
459 
460     return false;
461 }
462 
463 
464 /** check if call is a dupe
465  *
466  * \return true if is dupe
467  */
468 /** \todo should check band AND mode if already worked.... */
469 
bm_isdupe(char * call,int band)470 bool bm_isdupe(char *call, int band) {
471 
472     /* spots for warc bands are never dupes */
473     if (IsWarcIndex(band))
474 	return false;
475 
476     int found = searchcallarray(call);
477 
478     if (found == -1)		/* new call */
479 	return false;
480 
481     if (qtcdirection > 0) {
482 	struct t_qtc_store_obj *qtc_obj = qtc_get(call);
483 	if (qtc_obj->total > 0 && qtc_obj->total < 10) {
484 	    return false;
485 	}
486 	if (qtc_obj->total == 0 && qtc_obj->capable > 0) {
487 	    return false;
488 	}
489     }
490 
491     if (worked[found].band & inxes[band]) {
492 	return worked_in_current_minitest_period(found);
493     }
494 
495     return false;
496 }
497 
498 
bm_show_info()499 void bm_show_info() {
500 
501     int curx, cury;
502 
503     getyx(stdscr, cury, curx);		/* remember cursor */
504 
505     /* show info field on the right */
506     attrset(COLOR_PAIR(CB_DUPE) | A_BOLD);
507     move(TOPLINE, 66);
508     vline(ACS_VLINE, LINES - TOPLINE - 1);
509 
510     mvprintw(LASTLINE - 5, 67, " bands: %s", bm_config.allband ? "all" : "own");
511     mvprintw(LASTLINE - 4, 67, " modes: %s", bm_config.allmode ? "all" : "own");
512     mvprintw(LASTLINE - 3, 67, " dupes: %s", bm_config.showdupes ? "yes" : "no");
513     mvprintw(LASTLINE - 2, 67, " onl.ml: %s", bm_config.onlymults ? "yes" : "no");
514 
515     attrset(COLOR_PAIR(CB_NEW) | A_STANDOUT);
516     mvprintw(LASTLINE - 1, 67, "  MULTI");
517 
518     attrset(COLOR_PAIR(CB_NEW) | A_BOLD);
519     printw(" NEW");
520 
521     attrset(COLOR_PAIR(CB_NORMAL));
522     mvprintw(LASTLINE, 67, "SPOT");
523 
524     attrset(COLOR_PAIR(CB_OLD));
525     printw(" OLD");
526 
527     attrset(COLOR_PAIR(CB_DUPE) | A_BOLD);
528     printw(" dupe");
529 
530     attroff(A_BOLD | A_STANDOUT);
531 
532     move(cury, curx);			/* reset cursor */
533 }
534 
535 
536 /* helper function for bandmap display
537  * mark entries according to age, source and worked state. Mark new multis
538  * - new 	brigth blue
539  * - normal	blue
540  * - aged	brown
541  * - worked	small caps */
colorize_spot(spot * data)542 void colorize_spot(spot *data) {
543     if (data -> timeout > SPOT_NORMAL)
544 	attrset(COLOR_PAIR(CB_NEW) | A_BOLD);
545 
546     else if (data -> timeout > SPOT_OLD)
547 	attrset(COLOR_PAIR(CB_NORMAL));
548 
549     else
550 	attrset(COLOR_PAIR(CB_OLD));
551 
552     if (bm_ismulti(NULL, data, data->band)) {
553 	attrset(COLOR_PAIR(CB_NEW) | A_STANDOUT);
554 	attron(A_STANDOUT);
555     }
556 
557     else if (data->dupe && bm_config.showdupes) {
558 	attrset(COLOR_PAIR(CB_DUPE) | A_BOLD);
559 	attroff(A_STANDOUT);
560     }
561 }
562 
563 /* helper function for bandmap display
564  * convert dupes to lower case
565  * add QTC flags for WAE contest
566  */
format_spot(spot * data)567 char *format_spot(spot *data) {
568     char *temp;
569     char *temp2;
570 
571     if (qtcdirection > 0) {
572 	temp = qtc_format(data->call);
573     } else
574 	temp = g_strdup(data->call);
575 
576     if (data->dupe && bm_config.showdupes) {
577 	temp2 = temp;
578 	temp = g_ascii_strdown(temp2, -1);
579 	g_free(temp2);
580     }
581     return temp;
582 }
583 
584 
585 /* helper function for bandmap display
586  * shows formatted spot on actual cursor position
587  */
show_spot(spot * data)588 void show_spot(spot *data) {
589     attrset(COLOR_PAIR(CB_DUPE) | A_BOLD);
590     printw("%7.1f %c ", data->freq / 1000.,
591 	   (data->node == thisnode ? '*' : data->node));
592 
593     char *temp = format_spot(data);
594     colorize_spot(data);
595     printw("%-12s", temp);
596     g_free(temp);
597 }
598 
599 
600 /* helper function for bandmap display
601  * shows spot on actual working frequency
602  */
show_spot_on_qrg(spot * data)603 void show_spot_on_qrg(spot *data) {
604 
605     printw("%7.1f %c ", (data->freq / 1000.),
606 	   (data->node == thisnode ? '*' : data->node));
607 
608     char *temp = format_spot(data);
609     printw("%-12s", temp);
610     g_free(temp);
611 }
612 
613 
614 /* helper function for bandmap display
615  * advance to next spot position
616  */
next_spot_position(int * y,int * x)617 void next_spot_position(int *y, int *x) {
618     *y += 1;
619     if (*y == LASTLINE + 1) {
620 	*y = TOPLINE;
621 	*x += SPOT_COLUMN_WIDTH;
622     }
623 }
624 
625 /* helper function for bandmap display
626  * provide center frequency for display
627  *
628  * If we have a rig online, read the frequency from it.
629  * Otherwise calculate center frequency from band and mode
630  * as middle value of the band/mode corners.
631  */
bm_get_center(int band,int mode)632 freq_t bm_get_center(int band, int mode) {
633     freq_t centerfrequency;
634 
635     if (trx_control)
636 	return freq;		/* return freq from rig */
637 
638     /* calculate center frequency for current band and mode */
639     if (CWMODE == mode) {
640 	centerfrequency = (bandcorner[band][0] + cwcorner[band]) / 2.;
641     } else if (SSBMODE == mode) {
642 	centerfrequency = (ssbcorner[band] + bandcorner[band][1]) / 2.;
643     } else {
644 	centerfrequency = (cwcorner[band] + ssbcorner[band]) / 2.;
645     }
646     return centerfrequency;
647 }
648 
649 
bandmap_show()650 void bandmap_show() {
651     /*
652      * display depending on filter state
653      * - all bands on/off
654      * - all mode  on/off
655      * - dupes     on/off
656      *
657      * If more entries to show than room in window, show around current frequency
658      *
659      * mark entries according to age, source and worked state. Mark new multis
660      * - new 	brigth blue
661      * - normal	blue
662      * - aged	black
663      * - worked	small caps
664      * - new multi	underlined
665      * - self announced stations
666      *   		small preceeding letter for reporting station
667      *
668      * maybe show own frequency as dashline in other color
669      * (maybee green highlighted)
670      * - highligth actual spot if near its frequency
671      *
672      * Allow selection of one of the spots (switches to S&P)
673      * - Ctrl-G as known
674      * - '.' and cursor plus 'Enter' \Todo
675      * - Test mouseclick..           \Todo
676      *
677      * '.' goes into map, shows help line above and supports
678      * - cursormovement
679      * - 'ESC' leaves mode
680      * - 'Enter' selects spot
681      * - 'B', 'D', 'M', 'O' switches filtering for band, dupes, mode and multiPlier on or off.
682      */
683 
684     GList *list;
685     spot *data;
686     int curx, cury;
687     int bm_x, bm_y;
688     int i, j;
689     bool dupe, multi;
690 
691     if (!bm_initialized) {
692 	bm_init();
693 	bm_initialized = 1;
694     }
695 
696     /* acquire mutex
697      * do not add new spots to allspots during
698      * - aging and
699      * - filtering
700      * furthermore do not allow call lookup as long as
701      * filtered spot array is build anew */
702 
703     pthread_mutex_lock(&bm_mutex);
704 
705     /* make array of spots to display
706      * filter spotlist according to settings */
707 
708     if (spots)
709 	g_ptr_array_free(spots, TRUE);		/* free array */
710 
711     spots = g_ptr_array_sized_new(128);	/* allocate new one */
712 
713     list = allspots;
714 
715     while (list) {
716 	data = list->data;
717 
718 	/* if spot is allband or allmode is set or band or mode matches
719 	 * actual one than add it to the filtered 'spot' array
720 	 * drop spots on WARC bands if in contest mode
721 	 */
722 	multi = bm_ismulti(NULL, data, data->band);
723 	dupe = bm_isdupe(data->call, data->band);
724 
725 	if ((!contest || !IsWarcIndex(data->band))         &&
726 		(bm_config.allband || (data->band == bandinx)) &&
727 		(bm_config.allmode || (data->mode == trxmode)) &&
728 		(bm_config.showdupes || !dupe) &&
729 		(! bm_config.onlymults || multi)) {
730 
731 	    data -> dupe = dupe;
732 	    g_ptr_array_add(spots, data);
733 	}
734 
735 	list = list->next;
736     }
737 
738     pthread_mutex_unlock(&bm_mutex);
739 
740 
741     /* afterwards display filtered list around own QRG +/- some offest
742      * (offset gets reset if we change frequency */
743 
744     getyx(stdscr, cury, curx);		/* remember cursor */
745 
746     /* start in TOPLINE, column 0 */
747     bm_y = TOPLINE;
748     bm_x = 0;
749 
750     /* clear space for bandmap */
751     attrset(COLOR_PAIR(CB_DUPE) | A_BOLD);
752 
753     move(bm_y, 0);			/* do not overwrite # frequency */
754     for (j = 0; j < 67; j++)
755 	addch(' ');
756 
757     for (i = bm_y + 1; i < LASTLINE + 1; i++) {
758 	move(i, 0);
759 	for (j = 0; j < 80; j++)
760 	    addch(' ');
761     }
762 
763     /* show info text */
764     bm_show_info();
765 
766     /* split bandmap into two parts below and above current QRG.
767      * Give both both parts equal size.
768      * If there are less spots then reserved in the half
769      * give the remaining room to the other half.
770      *
771      * These results in maximized usage of the bandmap display while
772      * trying to keep the actual frequency in the center.
773      */
774     unsigned int below_qrg = 0;
775     unsigned int on_qrg = 0;
776     unsigned int startindex, stopindex;
777 
778     const freq_t centerfrequency = bm_get_center(bandinx, trxmode);
779 
780     /* calc number of spots below your current QRG */
781     for (i = 0; i < spots->len; i++) {
782 	data = g_ptr_array_index(spots, i);
783 
784 	if (data->freq <= centerfrequency - TOLERANCE)
785 	    below_qrg++;
786 	else
787 	    break;
788     }
789 
790     /* check if current QRG is on a spot */
791     if (below_qrg < spots->len) {
792 	data = g_ptr_array_index(spots, below_qrg);
793 
794 	if (!(data->freq > centerfrequency + TOLERANCE))
795 	    on_qrg = 1;
796     }
797 
798     /* calc the index into the spot array of the first spot to show */
799     {
800 	unsigned int max_below;
801 	unsigned int above_qrg = spots->len - below_qrg - on_qrg;
802 
803 	if (above_qrg < ((NR_SPOTS - 1) / 2)) {
804 	    max_below = NR_SPOTS - above_qrg - 1;
805 	} else
806 	    max_below = NR_SPOTS / 2;
807 
808 	startindex = (below_qrg < max_below) ? 0 : (below_qrg - max_below);
809     }
810 
811     /* calculate the index+1 of the last spot to show */
812     stopindex  = (spots->len < startindex + NR_SPOTS - (1 - on_qrg))
813 		 ? spots->len
814 		 : (startindex + NR_SPOTS - (1 - on_qrg));
815 
816     /* correct calculations if we have no rig frequency to show */
817     if (trx_control == 0) {
818 	if (on_qrg) {
819 	    on_qrg = 0;
820 	} else {
821 	    stopindex += 1;
822 	}
823 	if (spots->len < stopindex)
824 	    stopindex = spots->len;
825     }
826 
827     /* show spots below QRG */
828     for (i = startindex; i < below_qrg; i++) {
829 	move(bm_y, bm_x);
830 	show_spot(g_ptr_array_index(spots, i));
831 	next_spot_position(&bm_y, &bm_x);
832     }
833 
834     /* show highlighted frequency marker or spot on QRG if rig control
835      * is active */
836     if (trx_control != 0) {
837 	move(bm_y, bm_x);
838 	attrset(COLOR_PAIR(C_HEADER) | A_STANDOUT);
839 	if (!on_qrg) {
840 	    printw("%7.1f   %s", centerfrequency / 1000.0,  "============");
841 	} else {
842 	    show_spot_on_qrg(g_ptr_array_index(spots, below_qrg));
843 	}
844 	next_spot_position(&bm_y, &bm_x);
845     }
846 
847     /* show spots above QRG */
848     for (i = below_qrg + on_qrg; i < stopindex; i++) {
849 	move(bm_y, bm_x);
850 	show_spot(g_ptr_array_index(spots, i));
851 	next_spot_position(&bm_y, &bm_x);
852     }
853 
854     attroff(A_BOLD);
855     move(cury, curx);			/* reset cursor */
856 
857     refreshp();
858 }
859 
860 
861 /** allow control of bandmap features
862  */
bm_menu()863 void bm_menu() {
864     int curx, cury;
865     char c = -1;
866     int j;
867 
868     getyx(stdscr, cury, curx);		/* remember cursor */
869 
870     attrset(COLOR_PAIR(C_LOG) | A_STANDOUT);
871     mvprintw(13, 0, "  Toggle <B>and, <M>ode, <D>upes or <O>nly multi filter");
872     printw(" | any other - leave ");
873 
874     c = toupper(key_get());
875     switch (c) {
876 	case 'B':
877 	    bm_config.allband = 1 - bm_config.allband;
878 	    break;
879 
880 	case 'M':
881 	    bm_config.allmode = 1 - bm_config.allmode;
882 	    break;
883 
884 	case 'D':
885 	    bm_config.showdupes = 1 - bm_config.showdupes;
886 	    break;
887 
888 	case 'O':
889 	    bm_config.onlymults = 1 - bm_config.onlymults;
890 	    break;
891     }
892     bandmap_show();		/* refresh display */
893 
894     move(13, 0);
895     for (j = 0; j < 80; j++)
896 	addch(' ');
897 
898     move(cury, curx);
899     refreshp();
900 }
901 
copy_spot(spot * data)902 spot *copy_spot(spot *data) {
903     spot *result = NULL;
904 
905     result = g_new(spot, 1);
906     result -> call = g_strdup(data -> call);
907     result -> freq = data -> freq;
908     result -> mode = data -> mode;
909     result -> band = data -> band;
910     result -> node = data -> node;
911     result -> timeout = data -> timeout;
912     result -> dupe = data -> dupe;
913     result -> cqzone = data -> cqzone;
914     result -> ctynr = data -> ctynr;
915     result -> pfx = g_strdup(data -> pfx);
916 
917     return result;
918 }
919 
920 /** Search partialcall in filtered bandmap
921  *
922  * Lookup given partial call in the list of filtered bandmap spots.
923  * Return a copy of the first entry found (means with teh lowest frequency).
924  *
925  * \param 	partialcall - part of call to look up
926  * \return 	spot * structure with a copy of the found spot
927  * 		or NULL if not found (You have to free the structure
928  * 		after use).
929  */
bandmap_lookup(char * partialcall)930 spot *bandmap_lookup(char *partialcall) {
931     spot *result = NULL;
932 
933     if ((*partialcall != '\0') && (spots->len > 0)) {
934 	int i;
935 
936 	pthread_mutex_lock(&bm_mutex);
937 
938 	for (i = 0; i < spots->len; i++) {
939 	    spot *data;
940 	    data = g_ptr_array_index(spots, i);
941 
942 	    if (strstr(data->call, partialcall) != NULL) {
943 
944 		/* copy data into a new Spot structure */
945 		result = copy_spot(data);
946 
947 		break;
948 	    }
949 	}
950 
951 	pthread_mutex_unlock(&bm_mutex);
952 
953     }
954     return result;
955 }
956 
957 /** Lookup next call in filtered spotlist
958  *
959  * Starting at given frequency lookup the array of filtered spots for
960  * the next call up- or downwards.
961  * Apply some headroom for frequency comparison (see problem with ORION rig
962  * (Dec2011).
963  * Returns a copy of the spot data or NULL if no such entry.
964  *
965  * \param 	upwards - lookup upwards if not 0
966  * \param 	freq - frequency to start from
967  *
968  * \return 	spot * structure with a copy of the found spot
969  * 		or NULL if not found (You have to free the structure
970  * 		after use).
971  */
972 
bandmap_next(unsigned int upwards,freq_t freq)973 spot *bandmap_next(unsigned int upwards, freq_t freq) {
974     spot *result = NULL;
975 
976     if (spots->len > 0) {
977 	int i;
978 
979 	pthread_mutex_lock(&bm_mutex);
980 
981 	if (upwards) {
982 
983 	    for (i = 0; i < spots->len; i++) {
984 		spot *data;
985 		data = g_ptr_array_index(spots, i);
986 
987 		if ((data->freq > freq + TOLERANCE / 2) &&
988 			(!bm_config.skipdupes || data->dupe == 0)) {
989 		    /* copy data into a new Spot structure */
990 		    result = copy_spot(data);
991 
992 		    break;
993 		}
994 	    }
995 	} else {
996 	    for (i = spots->len - 1; i >= 0; i--) {
997 		spot *data;
998 		data = g_ptr_array_index(spots, i);
999 
1000 		if ((data->freq < freq - TOLERANCE / 2) &&
1001 			(!bm_config.skipdupes || data->dupe == 0)) {
1002 		    /* copy data into a new Spot structure */
1003 		    result = copy_spot(data);
1004 
1005 		    break;
1006 		}
1007 	    }
1008 	}
1009 	pthread_mutex_unlock(&bm_mutex);
1010 
1011     }
1012     return result;
1013 }
1014 
1015 /*
1016  * copy string to buffer but truncate it to n characters
1017  * If truncated show it by replacing last two chars by '..'
1018  * The buffer has to be at least n+1 chars long.
1019  */
str_truncate(char * buffer,char * string,int n)1020 void str_truncate(char *buffer, char *string, int n) {
1021     if (strlen(string) > n) {
1022 	g_strlcpy(buffer, string, n - 1);   	/* truncate to n-2 chars */
1023 	strcat(buffer, "..");
1024     } else {
1025 	g_strlcpy(buffer, string, n + 1); 	/* copy up to n chars */
1026     }
1027 }
1028 
1029 /*
1030  * format bandmap call output for WAE
1031  * - prepare and return a temporary string from call and number of QTC's
1032  *   (if any)
1033  */
qtc_format(char * call)1034 char *qtc_format(char *call) {
1035     char tcall[15];
1036     char qtcflag;
1037     struct t_qtc_store_obj *qtc_temp_ptr;
1038 
1039     qtc_temp_ptr = qtc_get(call);
1040     qtcflag = qtc_get_value(qtc_temp_ptr);
1041 
1042     if (qtc_temp_ptr->total <= 0 && qtcflag == '\0') {
1043 	str_truncate(tcall, call, SPOT_CALL_WIDTH);
1044     } else {
1045 	str_truncate(tcall, call, SPOT_CALL_WIDTH - 2);
1046 	sprintf(tcall + strlen(tcall), " %c", qtcflag);
1047     }
1048     return g_strdup(tcall);
1049 }
1050 
1051 
1052 /** Search filtered bandmap for a spot near the given frequency
1053  *
1054  * Return the call found at that frequency or NULL if no spot found
1055  *
1056  * \param 	dest - place to put the call in
1057  * \param 	freq - the frequency where to look for a spot
1058  */
get_spot_on_qrg(char * dest,freq_t freq)1059 void get_spot_on_qrg(char *dest, freq_t freq) {
1060 
1061     *dest = '\0';
1062 
1063     if (spots->len > 0) {
1064 	int i;
1065 
1066 	pthread_mutex_lock(&bm_mutex);
1067 
1068 	for (i = 0; i < spots->len; i++) {
1069 	    spot *data;
1070 	    data = g_ptr_array_index(spots, i);
1071 
1072 	    if ((fabs(data->freq - freq) < TOLERANCE) &&
1073 		    (!bm_config.skipdupes || data->dupe == 0)) {
1074 		strcpy(dest, data->call);
1075 		break;
1076 	    }
1077 	}
1078 
1079 	pthread_mutex_unlock(&bm_mutex);
1080 
1081     }
1082 }
1083