1 /*
2 qrq - High speed morse trainer, similar to the DOS classic "Rufz"
3
4 Copyright (C) 2006-2019 Fabian Kurz and contributors
5
6 This program is free software; you can redistribute it and/or modify it under
7 the terms of the GNU General Public License as published by the Free Software
8 Foundation; either version 2 of the License, or (at your option) any later
9 version.
10
11 This program is distributed in the hope that it will be useful, but WITHOUT ANY
12 WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
13 PARTICULAR PURPOSE. See the GNU General Public License for more details.
14
15 You should have received a copy of the GNU General Public License along with
16 this program; if not, write to the Free Software Foundation, Inc., 51 Franklin
17 Street, Fifth Floor, Boston, MA 02110-1301, USA.
18
19 */
20 #if WIN32
21 #define WIN_THREADS
22 typedef int AUDIO_HANDLE;
23 #endif
24
25 #ifndef WIN_THREADS
26 #include <pthread.h> /* CW output will be in a separate thread */
27 #endif
28 #include <ncurses.h>
29 #include <stdlib.h>
30 #include <string.h>
31 #include <libgen.h> /* basename */
32 #include <ctype.h>
33 #include <time.h>
34 #include <limits.h> /* PATH_MAX */
35
36 #ifndef PATH_MAX /* Not defined e.g. on GNU/hurd */
37 #define PATH_MAX 4096
38 #endif
39
40 #include <dirent.h>
41 #include <math.h>
42 #include <fcntl.h>
43 #include <unistd.h>
44 #include <sys/stat.h> /* mkdir */
45 #include <sys/types.h>
46 #include <errno.h>
47 #include <stdio.h>
48 #ifdef WIN32
49 #include <windows.h>
50 #endif
51
52 #define PI M_PI
53
54 #define SILENCE 0 /* Waveforms for the tone generator */
55 #define SINE 1
56 #define SAWTOOTH 2
57 #define SQUARE 3
58
59 #ifndef DESTDIR
60 # define DESTDIR "/usr"
61 #endif
62
63 #ifndef VERSION
64 # define VERSION "0.0.0"
65 #endif
66
67 #ifdef CA
68 #include "coreaudio.h"
69 typedef void *AUDIO_HANDLE;
70 #endif
71
72 #ifdef OSS
73 #include "oss.h"
74 #define write_audio(x, y, z) write(x, y, z)
75 #define close_audio(x) close(x)
76 typedef int AUDIO_HANDLE;
77 #endif
78
79 #ifdef PA
80 #include "pulseaudio.h"
81 typedef void *AUDIO_HANDLE;
82 #endif
83
84 /* callsign array will be dynamically allocated */
85 static char **calls = NULL;
86
87 const static char *codetable[] = {
88 ".-", "-...", "-.-.", "-..", ".", "..-.", "--.", "....", "..",".---",
89 "-.-",".-..","--","-.","---",".--.","--.-",".-.","...","-","..-","...-",
90 ".--","-..-","-.--","--..","-----",".----","..---","...--","....-",".....",
91 "-....", "--...","---..","----."};
92
93 /* List of available callbase files. Probably no need to do dynamic memory allocation for that list.... */
94
95 static char cblist[100][PATH_MAX];
96
97 static char mycall[15]="DJ1YFK"; /* mycall. will be read from qrqrc */
98 static char dspdevice[PATH_MAX]="/dev/dsp"; /* will also be read from qrqrc */
99 static int score = 0; /* qrq score */
100 static int sending_complete; /* global lock for "enter" while sending */
101 static int callnr = 0; /* nr of actual call in attempt */
102 static int initialspeed=200; /* initial speed. to be read from file*/
103 static int mincharspeed=0; /* min. char. speed, below: farnsworth*/
104 static int speed=200; /* current speed in cpm */
105 static int maxspeed=0;
106 static int freq=800; /* current cw sidetone freq */
107 static int errornr=0; /* number of errors in attempt */
108 static int p=0; /* position of cursor, relative to x */
109 static int status=1; /* 1= attempt, 2=config */
110 static int mode=1; /* 0 = overwrite, 1 = insert */
111 static int j=0; /* counter etc. */
112 static int constanttone=0; /* if 1 don't change the pitch */
113 static int ctonefreq=800; /* if constanttone=1 use this freq */
114 static int f6=0; /* f6 = 1: allow unlimited repeats */
115 static int fixspeed=0; /* keep speed fixed, regardless of err*/
116 static int unlimitedattempt=0; /* attempt with all calls of the DB */
117 static int attemptvalid=1; /* 1 = not using any "cheats" */
118 static unsigned long int nrofcalls=0;
119
120 long samplerate=44100;
121 static long long_i;
122 static int waveform = SINE; /* waveform: (0 = none) */
123 static char wavename[10]="Sine "; /* Name of the waveform */
124 static double edge=2.0; /* rise/fall time in milliseconds */
125 static int ed; /* risetime, normalized to samplerate */
126
127 static short buffer[88200];
128 static int full_buf[882000]; /* 20 second max buffer */
129 static int full_bufpos = 0;
130
131 AUDIO_HANDLE dsp_fd;
132
133 static int display_toplist();
134 static int calc_score (char * realcall, char * input, int speed, char * output, int f6pressed);
135 static int update_score();
136 static int show_error (char * realcall, char * wrongcall);
137 static int clear_display();
138 static int add_to_toplist(char * mycall, int score, int maxspeed);
139 static int read_config();
140 static int save_config();
141 static int tonegen(int freq, int length, int waveform);
142 static void *morse(void * arg);
143 static int add_to_buf(void* data, int size);
144 static int readline(WINDOW *win, int y, int x, char *line, int i);
145 static void thread_fail (int j);
146 static int check_toplist ();
147 static int find_files ();
148 static int statistics ();
149 static int read_callbase ();
150 static void find_callbases();
151 static void select_callbase ();
152 static void help ();
153 static void callbase_dialog();
154 static void parameter_dialog();
155 static int clear_parameter_display();
156 static void update_parameter_dialog();
157 static void start_summary_file();
158 static void close_summary_file();
159
160 #ifdef WIN_THREADS
161 HANDLE cwthread;
162 #else
163 pthread_t cwthread; /* thread for CW output, to enable
164 keyboard reading at the same time */
165 pthread_attr_t cwattr;
166 #endif
167
168 char rcfilename[PATH_MAX]=""; /* filename and path to qrqrc */
169 char tlfilename[PATH_MAX]=""; /* filename and path to toplist */
170 char cbfilename[PATH_MAX]=""; /* filename and path to callbase */
171 char sumfilepath[PATH_MAX]=""; /* path where to save summary files for each attempt */
172
173 char destdir[PATH_MAX]="";
174
175 char summary[65536]=""; /* detailled attempt summary, saved in a file */
176 int s_pos = 0; /* Position within summary */
177
178 /* create windows */
179 WINDOW *top_w; /* actual score */
180 WINDOW *mid_w; /* callsign history/mistakes */
181 WINDOW *conf_w; /* parameter config display */
182 WINDOW *bot_w; /* user input line */
183 WINDOW *inf_w; /* info window for param displ */
184 WINDOW *right_w; /* highscore list/settings */
185
186
main(int argc,char * argv[])187 int main (int argc, char *argv[]) {
188
189 /* if built as osx bundle set DESTDIR to Resources dir of bundle */
190 #ifdef OSX_BUNDLE
191 char tempdir[PATH_MAX]="";
192 char* p_slash = strrchr(argv[0], '/');
193 strncpy(tempdir, argv[0], p_slash - argv[0]);
194 p_slash = strrchr(tempdir, '/');
195 strncpy(destdir, tempdir, p_slash - tempdir);
196 strcat(destdir, "/Resources");
197 #else
198 strcpy(destdir, DESTDIR);
199 #endif
200
201 char abort = 0;
202 char tmp[80]="";
203 char input[15]="";
204 int i=0,j=0,k=0; /* counter etc. */
205 char previouscall[80]="";
206 int previousfreq = 0;
207 int f6pressed=0;
208
209 if (argc > 1) {
210 help();
211 }
212
213 (void) initscr();
214 cbreak();
215 noecho();
216 curs_set(FALSE);
217 keypad(stdscr, TRUE);
218 scrollok(stdscr, FALSE);
219
220 printw("qrq v%s - Copyright (C) 2006-2019 Fabian Kurz, DJ1YFK\n", VERSION);
221 printw("This is free software, and you are welcome to redistribute it\n");
222 printw("under certain conditions (see COPYING).\n");
223
224 refresh();
225
226 /* search for 'toplist', 'qrqrc' and callbase.qcb and put their locations
227 * into tlfilename, rcfilename, cbfilename */
228 find_files();
229
230 /* check if the toplist is in the suitable format. as of 0.0.7, each line
231 * is 31 characters long, with the added time stamp */
232 check_toplist();
233
234 /* buffer for audio */
235 for (long_i=0;long_i<88200;long_i++) {
236 buffer[long_i]=0;
237 }
238
239 /* random seed from time */
240 srand( (unsigned) time(NULL) );
241
242 #ifndef WIN_THREADS
243 /* Initialize cwthread. We have to wait for the cwthread to finish before
244 * the next cw output can be made, this will be done with pthread_join */
245 pthread_attr_init(&cwattr);
246 pthread_attr_setdetachstate(&cwattr, PTHREAD_CREATE_JOINABLE);
247 #endif
248
249 /****** Reading configuration file ******/
250 printw("\nReading configuration file qrqrc \n");
251 read_config();
252
253 attemptvalid = 1;
254 if (f6 || fixspeed || unlimitedattempt) {
255 attemptvalid = 0;
256 }
257
258 /****** Reading callsign database ******/
259 printw("\nReading callsign database... ");
260 nrofcalls = read_callbase();
261
262 printw("done. %d calls read.\n\n", nrofcalls);
263 printw("Press any key to continue...");
264
265 refresh();
266 getch();
267
268 erase();
269 refresh();
270
271 top_w = newwin(4, 60, 0, 0);
272 mid_w = newwin(17, 60, 4, 0);
273 conf_w = newwin(17, 60, 4, 0);
274 bot_w = newwin(3, 60, 21, 0);
275 inf_w = newwin(3, 60, 21, 0);
276 right_w = newwin(24, 20, 0, 60);
277
278 werase(top_w);
279 werase(mid_w);
280 werase(conf_w);
281 werase(bot_w);
282 werase(inf_w);
283 werase(right_w);
284
285 keypad(bot_w, TRUE);
286 keypad(mid_w, TRUE);
287 keypad(conf_w, TRUE);
288
289 #ifdef WIN_THREADS
290 cwthread = (HANDLE) _beginthreadex( NULL, 0, morse,"QRQ",0, NULL);
291 #else
292 /* no need to join here, this is the first possible time CW is sent */
293 pthread_create(&cwthread, NULL, & morse, (void *) "QRQ");
294 #endif
295
296 /* very outter loop */
297 while (1) {
298
299 /* status 1 = running an attempt of 50 calls */
300 while (status == 1) {
301 box(top_w,0,0);
302 box(conf_w,0,0);
303 box(mid_w,0,0);
304 box(bot_w,0,0);
305 box(inf_w,0,0);
306 box(right_w,0,0);
307 wattron(top_w,A_BOLD);
308 mvwaddstr(top_w,1,1, "QRQ v");
309 mvwaddstr(top_w,1,6, VERSION);
310 wattroff(top_w, A_BOLD);
311 mvwaddstr(top_w,1,11, " by Fabian Kurz, DJ1YFK");
312 mvwaddstr(top_w,2,1, "Homepage and Toplist: http://fkurz.net/ham/qrq.html"
313 " ");
314
315 clear_display();
316 wattron(mid_w,A_BOLD);
317 mvwaddstr(mid_w,1,1, "Usage:");
318 mvwaddstr(mid_w,10,2, "F6 F10 ");
319 wattroff(mid_w, A_BOLD);
320 mvwaddstr(mid_w,2,2, "After entering your callsign, 50 random callsigns");
321 mvwaddstr(mid_w,3,2, "from a database will be sent. After each callsign,");
322 mvwaddstr(mid_w,4,2, "enter what you have heard. If you copied correctly,");
323 mvwaddstr(mid_w,5,2, "full points are credited and the speed increases by");
324 mvwaddstr(mid_w,6,2, "2 WpM -- otherwise the speed decreases and only a ");
325 mvwaddstr(mid_w,7,2, "fraction of the points, depending on the number of");
326 mvwaddstr(mid_w,8,2, "errors is credited.");
327 mvwaddstr(mid_w,10,2, "F6 repeats a callsign once, F10 quits.");
328 mvwaddstr(mid_w,12,2, "Settings can be changed with F5 (or in qrqrc).");
329 #ifndef WIN32
330 mvwaddstr(mid_w,14,2, "Score statistics (requires gnuplot) with F7.");
331 #endif
332
333 wattron(right_w,A_BOLD);
334 mvwaddstr(right_w,1, 6, "Toplist");
335 wattroff(right_w,A_BOLD);
336
337 display_toplist();
338
339 p=0; /* cursor to start position */
340 wattron(bot_w,A_BOLD);
341 mvwaddstr(bot_w, 1, 1, "Please enter your callsign: ");
342 wattroff(bot_w,A_BOLD);
343
344 wrefresh(top_w);
345 wrefresh(mid_w);
346 wrefresh(bot_w);
347 wrefresh(right_w);
348
349 /* reset */
350 maxspeed = errornr = score = 0;
351 speed = initialspeed;
352
353 /* prompt for own callsign */
354 i = readline(bot_w, 1, 30, mycall, 1);
355
356 /* F5 -> Configure sound */
357 if (i == 5) {
358 parameter_dialog();
359 break;
360 }
361 /* F6 -> play test CW */
362 else if (i == 6) {
363 freq = constanttone ? ctonefreq : 800;
364 #ifdef WIN_THREADS
365 WaitForSingleObject(cwthread,INFINITE);
366 CloseHandle(cwthread);
367 cwthread = (HANDLE) _beginthreadex( NULL, 0, morse,"VVVTEST",0, NULL);
368 #else
369 pthread_join(cwthread, NULL);
370 j = pthread_create(&cwthread, NULL, &morse, (void *) "VVVTEST");
371 thread_fail(j);
372 #endif
373 break;
374 }
375 else if (i == 7) {
376 #ifndef WIN32
377 statistics();
378 #endif
379 break;
380 }
381
382 if (strlen(mycall) == 0) {
383 strcpy(mycall, "NOCALL");
384 }
385 else if (strlen(mycall) > 7) { /* cut excessively long calls */
386 mycall[7] = '\0';
387 }
388
389 clear_display();
390 wrefresh(mid_w);
391
392 /* update toplist (highlight may change) */
393 display_toplist();
394
395 mvwprintw(top_w,1,1," ");
396 mvwprintw(top_w,2,1," ");
397 mvwprintw(top_w,1,1,"Callsign:");
398 wattron(top_w,A_BOLD);
399 mvwprintw(top_w,1,11, "%s", mycall);
400 wattroff(top_w,A_BOLD);
401 update_score();
402 wrefresh(top_w);
403
404
405 /* Reread callbase */
406 nrofcalls = read_callbase();
407
408 /****** send 50 or unlimited calls, ask for input, score ******/
409 start_summary_file();
410
411 for (callnr=1; callnr < (unlimitedattempt ? nrofcalls : 51); callnr++) {
412 /* Make sure to wait for the cwthread of the previous callsign, if
413 * necessary. */
414 #ifdef WIN_THREADS
415 WaitForSingleObject(cwthread,INFINITE);
416 CloseHandle(cwthread);
417 #else
418 pthread_join(cwthread, NULL);
419 #endif
420 /* select an unused callsign from the calls-array */
421 do {
422 i = (int) ((float) nrofcalls*rand()/(RAND_MAX+1.0));
423 } while (calls[i] == NULL);
424
425 /* only relevant for callbases with less than 50 calls */
426 if (nrofcalls == callnr) { /* Only one call left!" */
427 callnr = 51; /* Get out after next one */
428 }
429
430
431
432 /* output frequency handling a) random b) fixed */
433 if ( constanttone == 0 ) {
434 /* random freq, fraction of samplerate */
435 freq = (int) (samplerate/(50+(40.0*rand()/(RAND_MAX+1.0))));
436 }
437 else { /* fixed frequency */
438 freq = ctonefreq;
439 }
440
441 mvwprintw(bot_w,1,1," ");
442 mvwprintw(bot_w, 1, 1, "%3d/%s", callnr, unlimitedattempt ? "-" : "50");
443 wrefresh(bot_w);
444 tmp[0]='\0';
445
446 /* starting the morse output in a separate process to make keyboard
447 * input and echoing at the same time possible */
448
449 sending_complete = 0;
450 #ifdef WIN_THREADS
451 cwthread = (HANDLE) _beginthreadex( NULL, 0, morse,calls[i],0, NULL);
452 #else
453 j = pthread_create(&cwthread, NULL, morse, calls[i]);
454 thread_fail(j);
455 #endif
456
457 f6pressed=0;
458
459 while (!abort && (j = readline(bot_w, 1, 8, input,1)) > 4) {/* F5..F10 pressed */
460
461 switch (j) {
462 case 6: /* repeat call */
463 if (f6pressed && (f6 == 0)) {
464 continue;
465 }
466 f6pressed=1;
467 /* wait for old cwthread to finish, then send call again */
468
469 #ifdef WIN_THREADS
470 WaitForSingleObject(cwthread,INFINITE);
471 CloseHandle(cwthread);
472 cwthread = (HANDLE) _beginthreadex( NULL, 0, morse,calls[i],0, NULL);
473 #else
474 pthread_join(cwthread, NULL);
475 j = pthread_create(&cwthread, NULL, &morse, calls[i]);
476 thread_fail(j);
477 #endif
478 break; /* 6*/
479 case 7: /* repeat _previous_ call */
480 if (callnr > 1) {
481 k = freq;
482 freq = previousfreq;
483 #ifdef WIN_THREADS
484 WaitForSingleObject(cwthread,INFINITE);
485 CloseHandle(cwthread);
486 cwthread = (HANDLE) _beginthreadex( NULL, 0, morse,previouscall,0, NULL);
487 WaitForSingleObject(cwthread,INFINITE);
488 CloseHandle(cwthread);
489 #else
490 pthread_join(cwthread, NULL);
491 j = pthread_create(&cwthread, NULL, &morse, previouscall);
492 thread_fail(j);
493 pthread_join(cwthread, NULL);
494 #endif
495 /* NB: We must wait for the CW thread before
496 * we set the freq back -- this blocks keyboard
497 * input, but in this case it shouldn't matter */
498 freq = k;
499 }
500 break;
501 case 10: /* abort attempt */
502 abort = 1;
503 continue;
504 break;
505 }
506
507 }
508
509
510 if (abort) {
511 abort = 0;
512 input[0]='\0';
513 break;
514 }
515
516 tmp[0]='\0';
517 score += calc_score(calls[i], input, speed, tmp, f6pressed);
518 update_score();
519 if (strcmp(tmp, "-")) { /* made an error */
520 show_error(calls[i], tmp);
521 }
522 input[0]='\0';
523 strncpy(previouscall, calls[i], 80);
524 previousfreq = freq;
525 calls[i] = NULL;
526 }
527
528 close_summary_file();
529
530 /* attempt is over, send AR */
531 callnr = 0;
532
533 #ifdef WIN_THREADS
534 WaitForSingleObject(cwthread,INFINITE);
535 CloseHandle(cwthread);
536 cwthread = (HANDLE) _beginthreadex( NULL, 0, morse,"+",0, NULL);
537 #else
538 pthread_join(cwthread, NULL);
539 j = pthread_create(&cwthread, NULL, &morse, (void *) "+");
540 thread_fail(j);
541 #endif
542
543 add_to_toplist(mycall, score, maxspeed);
544
545 curs_set(0);
546 wattron(bot_w,A_BOLD);
547 mvwprintw(bot_w,1,1, "Attempt finished. Press any key to continue!");
548 wattroff(bot_w,A_BOLD);
549 wrefresh(bot_w);
550 getch();
551 mvwprintw(bot_w,1,1, " ");
552 curs_set(1);
553
554
555 } /* while (status == 1) */
556
557
558 } /* very outter loop */
559
560 getch();
561 endwin();
562 delwin(top_w);
563 delwin(bot_w);
564 delwin(mid_w);
565 delwin(right_w);
566 getch();
567 return 0;
568 }
569
570
571
572 /* (formerly status == 2). Change parameters */
573
parameter_dialog()574 void parameter_dialog () {
575
576 int j = 0;
577
578
579 update_parameter_dialog();
580
581 while ((j = getch()) != 0) {
582
583 switch ((int) j) {
584 case '+': /* rise/falltime */
585 if (edge <= 9.0) {
586 edge += 0.1;
587 }
588 break;
589 case '-':
590 if (edge > 0.1) {
591 edge -= 0.1;
592 }
593 break;
594 case 'w': /* change waveform */
595 waveform = ((waveform + 1) % 3)+1; /* toggle 1-2-3 */
596 break;
597 case 'k': /* constanttone */
598 if (ctonefreq >= 160) {
599 ctonefreq -= 10;
600 }
601 else {
602 constanttone = 0;
603 }
604 break;
605 case 'l':
606 if (constanttone == 0) {
607 constanttone = 1;
608 }
609 else if (ctonefreq < 1600) {
610 ctonefreq += 10;
611 }
612 break;
613 case '0':
614 if (constanttone == 1) {
615 constanttone = 0;
616 }
617 else {
618 constanttone = 1;
619 }
620 break;
621 case 'f':
622 f6 = (f6 ? 0 : 1);
623 break;
624 case 's':
625 fixspeed = (fixspeed ? 0 : 1);
626 break;
627 case 'u':
628 unlimitedattempt = (unlimitedattempt ? 0 : 1);
629 break;
630 case KEY_UP:
631 initialspeed += 10;
632 break;
633 case KEY_DOWN:
634 if (initialspeed > 10) {
635 initialspeed -= 10;
636 }
637 break;
638 case KEY_RIGHT:
639 mincharspeed += 10;
640 break;
641 case KEY_LEFT:
642 if (mincharspeed > 10) {
643 mincharspeed -= 10;
644 }
645 break;
646 case 'c':
647 readline(conf_w, 5, 25, mycall, 1);
648 if (strlen(mycall) == 0) {
649 strcpy(mycall, "NOCALL");
650 }
651 else if (strlen(mycall) > 7) { /* cut excessively long calls */
652 mycall[7] = '\0';
653 }
654 p=0; /* cursor position */
655 break;
656 #ifdef OSS
657 case 'e':
658 readline(conf_w, 12, 25, dspdevice, 0);
659 if (strlen(dspdevice) == 0) {
660 strcpy(dspdevice, "/dev/dsp");
661 }
662 p=0; /* cursor position */
663 break;
664 #endif
665 case 'd': /* go to database browser */
666 if (!callnr) { /* Only allow outside of attempt */
667 curs_set(1);
668 callbase_dialog();
669 }
670 break;
671 case KEY_F(2):
672 save_config();
673 mvwprintw(conf_w,15,39, "Config saved!");
674 wrefresh(conf_w);
675 #ifdef WIN32
676 Sleep(1000);
677 #else
678 sleep(1);
679 #endif
680 break;
681 case KEY_F(6):
682 freq = constanttone ? ctonefreq : 800;
683 #ifdef WIN_THREADS
684 WaitForSingleObject(cwthread,INFINITE);
685 CloseHandle(cwthread);
686 cwthread = (HANDLE) _beginthreadex( NULL, 0, morse,"TESTING",0, NULL);
687 #else
688 pthread_join(cwthread, NULL);
689 j = pthread_create(&cwthread, NULL, &morse, (void *) "TESTING");
690 thread_fail(j);
691 #endif
692 break;
693 case KEY_F(10):
694 case KEY_F(3):
695 curs_set(1);
696 clear_parameter_display();
697 wrefresh(conf_w);
698 /* restore old windows */
699 touchwin(mid_w);
700 touchwin(bot_w);
701 wrefresh(mid_w);
702 wrefresh(bot_w);
703 return;
704 }
705
706 speed = initialspeed;
707
708 attemptvalid = 1;
709 if (f6 || fixspeed || unlimitedattempt) {
710 attemptvalid = 0;
711 }
712
713 update_parameter_dialog();
714
715 } /* while 1 (return only by F3/F10) */
716
717 } /* parameter_dialog */
718
719
720 /* update_parameter_dialog
721 * repaints the whole config/parameter screen (F5) */
722
723
update_parameter_dialog()724 void update_parameter_dialog () {
725
726 clear_parameter_display();
727 switch (waveform) {
728 case SINE:
729 strcpy(wavename, "Sine ");
730 break;
731 case SAWTOOTH:
732 strcpy(wavename, "Sawtooth");
733 break;
734 case SQUARE:
735 strcpy(wavename, "Square ");
736 break;
737 }
738
739 mvwaddstr(inf_w,1,1, " ");
740 curs_set(0);
741 wattron(conf_w,A_BOLD);
742 mvwaddstr(conf_w,1,1, "Configuration: Value Change");
743 mvwprintw(conf_w,14,2, " F6 F10 ");
744 mvwprintw(conf_w,15,2, " F2");
745 wattroff(conf_w, A_BOLD);
746 mvwprintw(conf_w,2,2, "Initial Speed: %3d CpM / %3d WpM"
747 " up/down", initialspeed, initialspeed/5);
748 mvwprintw(conf_w,3,2, "Min. character Speed: %3d CpM / %3d WpM"
749 " left/right", mincharspeed, mincharspeed/5);
750 mvwprintw(conf_w,4,2, "CW rise/falltime (ms): %1.1f "
751 " +/-", edge);
752 mvwprintw(conf_w,5,2, "Callsign: %-14s"
753 " c", mycall);
754 mvwprintw(conf_w,6,2, "CW pitch (0 = random): %-4d"
755 " k/l or 0", (constanttone)?ctonefreq : 0);
756 mvwprintw(conf_w,7,2, "CW waveform: %-8s"
757 " w", wavename);
758 mvwprintw(conf_w,8,2, "Allow unlimited F6*: %-3s"
759 " f", (f6 ? "yes" : "no"));
760 mvwprintw(conf_w,9,2, "Fixed CW speed*: %-3s"
761 " s", (fixspeed ? "yes" : "no"));
762 mvwprintw(conf_w,10,2, "Unlimited attempt*: %-3s"
763 " u", (unlimitedattempt ? "yes" : "no"));
764 if (!callnr) {
765 mvwprintw(conf_w,11,2, "Callsign database: %-15s"
766 " d (%d)", basename(cbfilename),nrofcalls);
767 }
768 #ifdef OSS
769 mvwprintw(conf_w,12,2, "DSP device: %-15s"
770 " e", dspdevice);
771 #endif
772 mvwprintw(conf_w,14,2, "Press");
773 mvwprintw(conf_w,14,11, "to play sample CW,");
774 mvwprintw(conf_w,14,34, "to go back.");
775 mvwprintw(conf_w,15,2, "Press");
776 mvwprintw(conf_w,15,11, "to save config permanently.");
777 mvwprintw(inf_w,1,1, " * Makes scores ineligible for toplist");
778 wrefresh(conf_w);
779 wrefresh(inf_w);
780
781
782 } /* update_parameter_dialog */
783
784
785
786
callbase_dialog()787 void callbase_dialog () {
788
789 clear_parameter_display();
790
791 wattron(conf_w,A_BOLD);
792 mvwaddstr(conf_w,1,1, "Change Callsign Database");
793 wattroff(conf_w,A_BOLD);
794 #if WIN32
795 mvwprintw(conf_w,3,1, ".qcb files found:");
796 #else
797 mvwprintw(conf_w,3,1, ".qcb files found (in %s/share/qrq/ and ~/.qrq/):",destdir);
798 #endif
799
800 /* populate cblist */
801 find_callbases();
802 /* selection dialog */
803 select_callbase();
804 wrefresh(conf_w);
805
806 nrofcalls = read_callbase();
807
808
809 return; /* back to config menu */
810 }
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825 /* reads a callsign etc. in *win at y/x and writes it to *line */
826
readline(WINDOW * win,int y,int x,char * line,int capitals)827 static int readline(WINDOW *win, int y, int x, char *line, int capitals) {
828 int c; /* character we read */
829 int i=0;
830
831 if (strlen(line) == 0) {p=0;} /* cursor to start if no call in buffer */
832
833 if (mode == 1) {
834 mvwaddstr(win,1,55,"INS");
835 }
836 else {
837 mvwaddstr(win,1,55,"OVR");
838 }
839
840 mvwaddstr(win,y,x,line);
841 wmove(win,y,x+p);
842 wrefresh(win);
843 curs_set(TRUE);
844
845 while (1) {
846 c = wgetch(win);
847 if (c == '\n' && sending_complete)
848 break;
849
850 if (((c > 64 && c < 91) || (c > 96 && c < 123) || (c > 47 && c < 58)
851 || c == '/' || c == ' ') && strlen(line) < 14) {
852
853 line[strlen(line)+1]='\0';
854 if (capitals) {
855 c = toupper(c);
856 }
857 if (mode == 1) { /* insert */
858 for(i=strlen(line);i > p; i--) { /* move all chars by one */
859 line[i] = line[i-1];
860 }
861 }
862 line[p]=c; /* insert into gap */
863 p++;
864 }
865 else if ((c == KEY_BACKSPACE || c == 127 || c == 9 || c == 8)
866 && p != 0) { /* BACKSPACE */
867 for (i=p-1;i < strlen(line); i++) {
868 line[i] = line[i+1];
869 }
870 p--;
871 }
872 else if (c == KEY_DC && strlen(line) != 0) { /* DELETE */
873 p++;
874 for (i=p-1;i < strlen(line); i++) {
875 line[i] = line[i+1];
876 }
877 p--;
878 }
879 else if (c == KEY_LEFT && p != 0) {
880 p--;
881 }
882 else if (c == KEY_RIGHT && p < strlen(line)) {
883 p++;
884 }
885 else if (c == KEY_HOME) {
886 p = 0;
887 }
888 else if (c == KEY_END) {
889 p = strlen(line);
890 }
891 else if (c == KEY_IC) { /* INS/OVR */
892 if (mode == 1) {
893 mode = 0;
894 mvwaddstr(win,1,55,"OVR");
895 }
896 else {
897 mode = 1;
898 mvwaddstr(win,1,55,"INS");
899 }
900 }
901 else if (c == KEY_PPAGE && callnr && !attemptvalid) {
902 speed += 5;
903 update_score();
904 wrefresh(top_w);
905 }
906 else if (c == KEY_NPAGE && callnr && !attemptvalid) {
907 if (speed > 20) speed -= 5;
908 update_score();
909 wrefresh(top_w);
910 }
911 else if (c == KEY_F(5)) {
912 parameter_dialog();
913 }
914 else if (c == KEY_F(6)) {
915 return 6;
916 }
917 else if (c == KEY_F(7)) {
918 return 7;
919 }
920 else if (c == KEY_F(10)) { /* quit */
921 if (callnr) { /* quit attempt only */
922 return 10;
923 }
924 /* else: quit program */
925 endwin();
926 printf("Thanks for using 'qrq'!\nYou can submit your"
927 " highscore to http://fkurz.net/ham/qrqtop.php\n");
928 /* make sure that no more output is running, then send 73 & quit */
929 speed = 200; freq = 800;
930 #ifdef WIN_THREADS
931 WaitForSingleObject(cwthread,INFINITE);
932 CloseHandle(cwthread);
933 cwthread = (HANDLE) _beginthreadex( NULL, 0, morse,"73",0, NULL);
934 WaitForSingleObject(cwthread,INFINITE);
935 CloseHandle(cwthread);
936 #else
937 pthread_join(cwthread, NULL);
938 j = pthread_create(&cwthread, NULL, &morse, (void *) "73");
939 thread_fail(j);
940 /* make sure the cw thread doesn't die with the main thread */
941 /* Exit the whole main thread */
942 pthread_join(cwthread, NULL);
943 #endif
944 exit(0);
945 }
946
947 mvwaddstr(win,y,x," ");
948 mvwaddstr(win,y,x,line);
949 wmove(win,y,x+p);
950 wrefresh(win);
951 }
952 curs_set(FALSE);
953 return 0;
954 }
955
956 /* Read toplist and diplay first 10 entries */
display_toplist()957 static int display_toplist () {
958 FILE * fh;
959 int i=0;
960 char tmp[35]="";
961 if ((fh = fopen(tlfilename, "a+")) == NULL) {
962 endwin();
963 fprintf(stderr, "Couldn't read or create file '%s'!", tlfilename);
964 exit(EXIT_FAILURE);
965 }
966 rewind(fh); /* a+ -> end of file, we want the beginning */
967 (void) fgets(tmp, 34, fh); /* first line not used */
968 while ((feof(fh) == 0) && i < 20) {
969 i++;
970 if (fgets(tmp, 34, fh) != NULL) {
971 tmp[17]='\0';
972 if (strstr(tmp, mycall)) { /* highlight own call */
973 wattron(right_w, A_BOLD);
974 }
975 mvwaddstr(right_w,i+2, 2, tmp);
976 wattroff(right_w, A_BOLD);
977 }
978 }
979 fclose(fh);
980 wrefresh(right_w);
981 return 0;
982 }
983
984 /* calculate score depending on number of errors and speed.
985 * writes the correct call and entered call with highlighted errors to *output
986 * and returns the score for this call
987 *
988 * in training modes (unlimited attempts, f6, fixed speed), no points.
989 * */
calc_score(char * realcall,char * input,int spd,char * output,int f6pressed)990 static int calc_score (char * realcall, char * input, int spd, char * output, int f6pressed) {
991 int i,x,m=0;
992 int score = 0;
993
994 x = strlen(realcall);
995
996 if (strcmp(input, realcall) == 0) { /* exact match! */
997 output[0]='-'; /* * == OK, no mistake */
998 output[1]='\0';
999 if (speed > maxspeed) {maxspeed = speed;}
1000 if (!fixspeed) speed += 10;
1001 if (attemptvalid) {
1002 score = 2*x*spd; /* score */
1003 }
1004 }
1005 else { /* assemble error string */
1006 errornr += 1;
1007 if (strlen(input) >= x) {x = strlen(input);}
1008 for (i=0;i < x;i++) {
1009 if (realcall[i] != input[i]) {
1010 m++; /* mistake! */
1011 output[i] = tolower(input[i]); /* print as lower case */
1012 }
1013 else {
1014 output[i] = input[i];
1015 }
1016 }
1017 output[i]='\0';
1018 if ((speed > 29) && !fixspeed) {speed -= 10;}
1019
1020 /* score when 1-3 mistakes was made */
1021 if ((m < 4) && attemptvalid) {
1022 score = (int) (2*x*spd)/(5*m);
1023 }
1024 }
1025
1026 s_pos += sprintf(summary + s_pos, "%-16s %-16s %-16s %3d %3d %5d %c\r\n", realcall, input, output, spd, spd/5, score, f6pressed ? '*' : ' ');
1027
1028 return score;
1029 }
1030
start_summary_file()1031 static void start_summary_file () {
1032 s_pos = 0;
1033 s_pos += sprintf(summary + s_pos, "QRQ attempt by %s.\r\n\r\n", mycall);
1034 s_pos += sprintf(summary + s_pos, "%-16s %-16s %-16s %-3s %-3s %-5s %s\r\n", "Sent call", "Input", "Difference", "CpM", "WpM", "Score", "F6");
1035 s_pos += sprintf(summary + s_pos, "--------------------------------------------------------------------\r\n");
1036 }
1037
close_summary_file()1038 static void close_summary_file () {
1039 FILE *fh;
1040 time_t t;
1041 struct tm *tmp;
1042 char time_fmt[256];
1043 char filename[PATH_MAX];
1044
1045 t = time(NULL);
1046 tmp = localtime(&t);
1047 if (tmp == NULL) {
1048 return;
1049 }
1050
1051 if (strftime(time_fmt, sizeof(time_fmt), "%Y%m%d_%H%M", tmp) == 0) {
1052 return;
1053 }
1054
1055 s_pos += sprintf(summary + s_pos, "--------------------------------------------------------------------\r\n");
1056 s_pos += sprintf(summary + s_pos, "Score: %d, Max. speed (CpM/WpM): %d / %d\r\nSaved at: %s\r\n", score, maxspeed, maxspeed/5, time_fmt);
1057
1058 snprintf(filename, PATH_MAX, "%s/%s-%s.txt", sumfilepath, mycall, time_fmt);
1059
1060 if ((fh = fopen(filename, "w")) == NULL) {
1061 printf("Unable to open summary file (%s)!\r\n", filename);
1062 exit(EXIT_FAILURE);
1063 }
1064
1065 fwrite(summary, 1, s_pos, fh);
1066 fclose(fh);
1067
1068 for (int i = 12; i <= 15; i++) {
1069 mvwprintw(mid_w,i,2, " ");
1070 }
1071
1072 mvwprintw(mid_w,13,1, " Written detailled summary of this attempt to:");
1073 mvwprintw(mid_w,14,2, filename);
1074 wrefresh(mid_w);
1075
1076 }
1077
1078 /* print score, current speed and max speed to window */
update_score()1079 static int update_score() {
1080 mvwaddstr(top_w,1,20, "Score: ");
1081 mvwaddstr(top_w,2,20, "Speed: CpM/ WpM, Max: / ");
1082 if (attemptvalid) {
1083 mvwprintw(top_w, 1, 27, "%6d", score);
1084 }
1085 else {
1086 mvwprintw(top_w, 1, 27, "[training mode]", score);
1087 }
1088 mvwprintw(top_w, 2, 27, "%3d", speed);
1089 mvwprintw(top_w, 2, 35, "%3d", speed/5);
1090 mvwprintw(top_w, 2, 49, "%3d", maxspeed);
1091 mvwprintw(top_w, 2, 54, "%3d", maxspeed/5);
1092 wrefresh(top_w);
1093 return 0;
1094 }
1095
1096 /* display the correct callsign and what the user entered, with mistakes
1097 * highlighted. */
show_error(char * realcall,char * wrongcall)1098 static int show_error (char * realcall, char * wrongcall) {
1099 int x=2;
1100 int y = errornr;
1101 int i;
1102
1103 /* Screen is full of errors. Remove them and start at the beginning */
1104 if (errornr == 31) {
1105 for (i=1;i<16;i++) {
1106 mvwaddstr(mid_w,i,2," "
1107 " ");
1108 }
1109 errornr = y = 1;
1110 }
1111
1112 /* Move to second column after 15 errors */
1113 if (errornr > 15) {
1114 x=30; y = (errornr % 16)+1;
1115 }
1116
1117 mvwprintw(mid_w,y,x, "%-13s %-13s", realcall, wrongcall);
1118 wrefresh(mid_w);
1119 return 0;
1120 }
1121
1122 /* clear error display */
clear_display()1123 static int clear_display() {
1124 int i;
1125 for (i=1;i<16;i++) {
1126 mvwprintw(mid_w,i,1," "
1127 " ");
1128 }
1129 return 0;
1130 }
1131
1132 /* clear parameter display */
clear_parameter_display()1133 static int clear_parameter_display() {
1134 int i;
1135 for (i=1;i<16;i++) {
1136 mvwprintw(conf_w,i,1," "
1137 " ");
1138 }
1139 return 0;
1140 }
1141
1142
1143 /* write entry into toplist at the right place
1144 * going down from the top of the list until the score in the current line is
1145 * lower than the score made. then */
1146
add_to_toplist(char * mycall,int score,int maxspeed)1147 static int add_to_toplist(char * mycall, int score, int maxspeed) {
1148 FILE *fh;
1149 char *part1, *part2;
1150 char tmp[35]="";
1151 char insertline[35]="DJ1YFK 36666 333 1111111111"; /* example */
1152 /* call pts max timestamp */
1153 int i=0, k=0, j=0;
1154 int pos = 0; /* position where first score < our score appears */
1155 int timestamp = 0;
1156 int len = 32; /* length of a line. 32 for unix, 33 for Win */
1157
1158 /* For the training modes */
1159 if (score == 0) {
1160 return 0;
1161 }
1162
1163 timestamp = (int) time(NULL);
1164 sprintf(insertline, "%-10s%6d %3d %10d", mycall, score, maxspeed, timestamp);
1165
1166 if ((fh = fopen(tlfilename, "rb+")) == NULL) {
1167 printf("Unable to open toplist file %s!\n", tlfilename);
1168 exit(EXIT_FAILURE);
1169 }
1170
1171 /* find out if we use CRLF or just LF */
1172 fgets(tmp, 35, fh);
1173 if (tmp[31] == '\r') { /* CRLF */
1174 len = 33;
1175 strcat(insertline, "\r\n");
1176 }
1177 else {
1178 len = 32;
1179 strcat(insertline, "\n");
1180 }
1181
1182 fseek(fh, 0, SEEK_END);
1183 j = ftell(fh);
1184
1185 part1 = malloc((size_t) j);
1186 part2 = malloc((size_t) j + len); /* one additional entry */
1187
1188 rewind(fh);
1189
1190 /* read whole toplist */
1191 fread(part1, sizeof(char), (size_t) j, fh);
1192
1193 /* find first score below "score"; scores at positions 10 + (i*len) */
1194
1195 do {
1196 for (i = 0 ; i < 6 ; i++) {
1197 tmp[i] = part1[i + (10 + pos*len)];
1198 }
1199 k = atoi(tmp);
1200 pos++;
1201 } while (score < k);
1202
1203 /* Found it! Insert own score here! */
1204 memcpy(part2, part1, len * (pos-1));
1205 memcpy(part2 + len * (pos - 1), insertline, len);
1206 memcpy(part2 + len * pos , part1 + len * (pos -1), j - len * (pos - 1));
1207
1208 rewind(fh);
1209 fwrite(part2, sizeof(char), (size_t) j + len, fh);
1210 fclose(fh);
1211
1212 free(part1);
1213 free(part2);
1214
1215 return 0;
1216
1217 }
1218
1219
1220 /* Read config file
1221 *
1222 * TODO contains too much copypasta. write proper function to parse a key=value
1223 *
1224 * */
1225
read_config()1226 static int read_config () {
1227 FILE *fh;
1228 char tmp[80]="";
1229 int i=0;
1230 int k=0;
1231 int line=0;
1232
1233 if ((fh = fopen(rcfilename, "r")) == NULL) {
1234 endwin();
1235 fprintf(stderr, "Unable to open config file %s!\n", rcfilename);
1236 exit(EXIT_FAILURE);
1237 }
1238
1239 while ((feof(fh) == 0) && (fgets(tmp, 80, fh) != NULL)) {
1240 i=0;
1241 line++;
1242 tmp[strlen(tmp)-1]='\0';
1243
1244 /* find callsign, speed etc.
1245 * only allow if the lines are beginning at zero, so stuff can be
1246 * commented out easily; return value if strstr must point to tmp*/
1247 if(tmp == strstr(tmp,"callsign=")) {
1248 while (isalnum(tmp[i] = toupper(tmp[9+i]))) {
1249 i++;
1250 }
1251 tmp[i]='\0';
1252 if (strlen(tmp) < 8) { /* empty call allowed */
1253 strcpy(mycall,tmp);
1254 printw(" line %2d: callsign: >%s<\n", line, mycall);
1255 }
1256 else {
1257 printw(" line %2d: callsign: >%s< too long. "
1258 "Using default >%s<.\n", line, tmp, mycall);
1259 }
1260 }
1261 else if (tmp == strstr(tmp,"initialspeed=")) {
1262 while (isdigit(tmp[i] = tmp[13+i])) {
1263 i++;
1264 }
1265 tmp[i]='\0';
1266 i = atoi(tmp);
1267 if (i > 9) {
1268 initialspeed = speed = i;
1269 printw(" line %2d: initial speed: %d\n", line, initialspeed);
1270 }
1271 else {
1272 printw(" line %2d: initial speed: %d invalid (range: 10..oo)."
1273 " Using default %d.\n",line, i, initialspeed);
1274 }
1275 }
1276 else if (tmp == strstr(tmp,"mincharspeed=")) {
1277 while (isdigit(tmp[i] = tmp[13+i])) {
1278 i++;
1279 }
1280 tmp[i]='\0';
1281 if ((i = atoi(tmp)) > 0) {
1282 mincharspeed = i;
1283 printw(" line %2d: min.char.speed: %d\n", line, mincharspeed);
1284 } /* else ignore */
1285 }
1286 else if (tmp == strstr(tmp,"dspdevice=")) {
1287 while (isgraph(tmp[i] = tmp[10+i])) {
1288 i++;
1289 }
1290 tmp[i]='\0';
1291 if (strlen(tmp) > 1) {
1292 strcpy(dspdevice,tmp);
1293 printw(" line %2d: dspdevice: >%s<\n", line, dspdevice);
1294 }
1295 else {
1296 printw(" line %2d: dspdevice: >%s< invalid. "
1297 "Using default >%s<.\n", line, tmp, dspdevice);
1298 }
1299 }
1300 else if (tmp == strstr(tmp, "risetime=")) {
1301 while (isdigit(tmp[i] = tmp[9+i]) || ((tmp[i] = tmp[9+i])) == '.') {
1302 i++;
1303 }
1304 tmp[i]='\0';
1305 edge = atof(tmp);
1306 printw(" line %2d: risetime: %f\n", line, edge);
1307 }
1308 else if (tmp == strstr(tmp, "waveform=")) {
1309 if (isdigit(tmp[i] = tmp[9+i])) { /* read 1 char only */
1310 tmp[++i]='\0';
1311 waveform = atoi(tmp);
1312 }
1313 if ((waveform <= 3) && (waveform > 0)) {
1314 printw(" line %2d: waveform: %d\n", line, waveform);
1315 }
1316 else {
1317 printw(" line %2d: waveform: %d invalid. Using default.\n",
1318 line, waveform);
1319 waveform = SINE;
1320 }
1321 }
1322 else if (tmp == strstr(tmp, "constanttone=")) {
1323 while (isdigit(tmp[i] = tmp[13+i])) {
1324 i++;
1325 }
1326 tmp[i]='\0';
1327 k = 0;
1328 k = atoi(tmp); /* constanttone */
1329 if ( (k*k) > 1) {
1330 printw(" line %2d: constanttone: %s invalid. "
1331 "Using default %d.\n", line, tmp, constanttone);
1332 }
1333 else {
1334 constanttone = k ;
1335 printw(" line %2d: constanttone: %d\n", line, constanttone);
1336 }
1337 }
1338 else if (tmp == strstr(tmp, "ctonefreq=")) {
1339 while (isdigit(tmp[i] = tmp[10+i])) {
1340 i++;
1341 }
1342 tmp[i]='\0';
1343 k = 0;
1344 k = atoi(tmp); /* ctonefreq */
1345 if ( (k > 1600) || (k < 100) ) {
1346 printw(" line %2d: ctonefreq: %s invalid. "
1347 "Using default %d.\n", line, tmp, ctonefreq);
1348 }
1349 else {
1350 ctonefreq = k ;
1351 printw(" line %2d: ctonefreq: %d\n", line, ctonefreq);
1352 }
1353 }
1354 else if (tmp == strstr(tmp, "f6=")) {
1355 f6=0;
1356 if (tmp[3] == '1') {
1357 f6 = 1;
1358 }
1359 printw(" line %2d: unlimited f6: %s\n", line, (f6 ? "yes":"no"));
1360 }
1361 else if (tmp == strstr(tmp, "fixspeed=")) {
1362 fixspeed=0;
1363 if (tmp[9] == '1') {
1364 fixspeed = 1;
1365 }
1366 printw(" line %2d: fixed speed: %s\n", line, (fixspeed ? "yes":"no"));
1367 }
1368 else if (tmp == strstr(tmp, "unlimitedattempt=")) {
1369 unlimitedattempt=0;
1370 if (tmp[17] == '1') {
1371 unlimitedattempt= 1;
1372 }
1373 printw(" line %2d: unlim. att.: %s\n", line, (unlimitedattempt ? "yes":"no"));
1374 }
1375 else if (tmp == strstr(tmp,"callbase=")) {
1376 while (isgraph(tmp[i] = tmp[9+i])) {
1377 i++;
1378 }
1379 tmp[i]='\0';
1380 if (strlen(tmp) > 1) {
1381 strcpy(cbfilename,tmp);
1382 printw(" line %2d: callbase: >%s<\n", line, cbfilename);
1383 }
1384 else {
1385 printw(" line %2d: callbase: >%s< invalid. "
1386 "Using default >%s<.\n", line, tmp, cbfilename);
1387 }
1388 }
1389 else if (tmp == strstr(tmp,"samplerate=")) {
1390 while (isdigit(tmp[i] = tmp[11+i])) {
1391 i++;
1392 }
1393 tmp[i]='\0';
1394 samplerate = atoi(tmp);
1395 printw(" line %2d: sample rate: %d\n", line, samplerate);
1396 }
1397 }
1398
1399 fclose(fh);
1400
1401 printw("Finished reading qrqrc.\n");
1402 return 0;
1403 }
1404
1405
morse(void * arg)1406 static void *morse(void *arg) {
1407 char * text = arg;
1408 int i,j;
1409 int c, fulldotlen, dotlen, dashlen, charspeed, farnsworth, fwdotlen;
1410 const char *code;
1411
1412 #if WIN32 /* WinMM simple support by Lukasz Komsta, SP8QED */
1413 HWAVEOUT h;
1414 WAVEFORMATEX wf;
1415 WAVEHDR wh;
1416 HANDLE d;
1417
1418 wf.wFormatTag = WAVE_FORMAT_PCM;
1419 wf.nChannels = 1;
1420 wf.wBitsPerSample = 16;
1421 wf.nSamplesPerSec = samplerate * 2;
1422 wf.nBlockAlign = wf.nChannels * wf.wBitsPerSample / 8;
1423 wf.nAvgBytesPerSec = wf.nSamplesPerSec * wf.nBlockAlign;
1424 wf.cbSize = 0;
1425 d = CreateEvent(0, FALSE, FALSE, 0);
1426 if(waveOutOpen(&h, 0, &wf, (DWORD) d, 0, CALLBACK_EVENT) != MMSYSERR_NOERROR);
1427
1428 #else
1429 /* opening the DSP device */
1430 dsp_fd = open_dsp(dspdevice);
1431 #endif
1432 /* set bufpos to 0 */
1433
1434 full_bufpos = 0;
1435
1436 /* Some silence; otherwise the call starts right after pressing enter */
1437 tonegen(0, samplerate/4, SILENCE);
1438
1439 /* Farnsworth? */
1440 if (speed < mincharspeed) {
1441 charspeed = mincharspeed;
1442 farnsworth = 1;
1443 fwdotlen = (int) (samplerate * 6/speed);
1444 }
1445 else {
1446 charspeed = speed;
1447 farnsworth = 0;
1448 }
1449
1450 /* speed is in LpM now, so we have to calculate the dot-length in
1451 * milliseconds using the well-known formula dotlength= 60/(wpm*50)
1452 * and then to samples */
1453
1454 dotlen = (int) (samplerate * 6/charspeed);
1455 fulldotlen = dotlen;
1456 dashlen = 3*dotlen;
1457
1458 /* edge = length of rise/fall time in ms. ed = in samples */
1459
1460 ed = (int) (samplerate * (edge/1000.0));
1461
1462 /* the signal needs "ed" samples to reach the full amplitude and
1463 * at the end another "ed" samples to reach zero. The dots and
1464 * dashes therefore are becoming longer by "ed" and the pauses
1465 * after them are shortened accordingly by "ed" samples */
1466
1467 for (i = 0; i < strlen(text); i++) {
1468 c = text[i];
1469 if (isalpha(c)) {
1470 code = codetable[c-65];
1471 }
1472 else if (isdigit(c)) {
1473 code = codetable[c-22];
1474 }
1475 else if (c == '/') {
1476 code = "-..-.";
1477 }
1478 else if (c == '+') {
1479 code = ".-.-.";
1480 }
1481 else if (c == ' ') { /* space */
1482 code = " ";
1483 }
1484 else { /* not supposed to happen! */
1485 code = "..--..";
1486 }
1487
1488 /* code is now available as string with - and . */
1489
1490 for (j = 0; j < strlen(code) ; j++) {
1491 c = code[j];
1492 if (c == '.') {
1493 tonegen(freq, dotlen + ed, waveform);
1494 tonegen(0, fulldotlen - ed, SILENCE);
1495 }
1496 else if (c == '-') {
1497 tonegen(freq, dashlen + ed, waveform);
1498 tonegen(0, fulldotlen - ed, SILENCE);
1499 }
1500 else { /* space */
1501 tonegen(0, dotlen, SILENCE);
1502 }
1503 }
1504 if (farnsworth) {
1505 tonegen(0, 3*fwdotlen - fulldotlen, SILENCE);
1506 }
1507 else {
1508 tonegen(0, 2*fulldotlen, SILENCE);
1509 }
1510 }
1511
1512
1513 #if !defined(PA) && !defined(CA)
1514 add_to_buf(buffer, 88200);
1515 #endif
1516
1517 #if WIN32
1518 wh.lpData = (char*) &full_buf[0];
1519 wh.dwBufferLength = full_bufpos - 2;
1520 wh.dwFlags = 0;
1521 wh.dwLoops = 0;
1522 waveOutPrepareHeader(h, &wh, sizeof(wh));
1523 ResetEvent(d);
1524 waveOutWrite(h, &wh, sizeof(wh));
1525 if(WaitForSingleObject(d, INFINITE) != WAIT_OBJECT_0);
1526 waveOutUnprepareHeader(h, &wh, sizeof(wh));
1527 waveOutClose(h);
1528 CloseHandle(d);
1529 #else
1530 write_audio(dsp_fd, &full_buf[0], full_bufpos);
1531 close_audio(dsp_fd);
1532 #endif
1533 sending_complete = 1;
1534 return NULL;
1535 }
1536
add_to_buf(void * data,int size)1537 static int add_to_buf(void* data, int size)
1538 {
1539 memcpy(&full_buf[full_bufpos / sizeof(int)], data, size);
1540 full_bufpos += size;
1541 return 0;
1542 }
1543
1544 /* tonegen generates a sinus tone of frequency 'freq' and length 'len' (samples)
1545 * based on 'samplerate', 'edge' (rise/falltime) */
1546
tonegen(int freq,int len,int waveform)1547 static int tonegen (int freq, int len, int waveform) {
1548 int x=0;
1549 int out;
1550 double val=0;
1551
1552 for (x=0; x < len-1; x++) {
1553 switch (waveform) {
1554 case SINE:
1555 val = sin(2*PI*freq*x/samplerate);
1556 break;
1557 case SAWTOOTH:
1558 val=((1.0*freq*x/samplerate)-floor(1.0*freq*x/samplerate))-0.5;
1559 break;
1560 case SQUARE:
1561 val = ceil(sin(2*PI*freq*x/samplerate))-0.5;
1562 break;
1563 case SILENCE:
1564 val = 0;
1565 }
1566
1567
1568 if (x < ed) { val *= pow(sin(PI*x/(2.0*ed)),2); } /* rising edge */
1569
1570 if (x > (len-ed)) { /* falling edge */
1571 val *= pow(sin(2*PI*(x-(len-ed)+ed)/(4*ed)),2);
1572 }
1573
1574 out = (int) (val * 32500.0);
1575 #ifndef PA
1576 out = out + (out<<16); /* stereo only for OSS & CoreAudio*/
1577 #endif
1578 add_to_buf(&out, sizeof(out));
1579 }
1580 return 0;
1581 }
1582
1583 /* Save config file
1584 *
1585 * Tries to keep the old format (including comments, etc.) and adds
1586 * config options that were not used yet in the file to the end
1587 * */
1588
save_config()1589 static int save_config () {
1590 FILE *fh;
1591 char tmp[4096]="";
1592 char confopts[12][80] = {
1593 "\ncallsign=",
1594 "\ncallbase=",
1595 "\ndspdevice=",
1596 "\ninitialspeed=",
1597 "\nmincharspeed=",
1598 "\nwaveform=",
1599 "\nconstanttone=",
1600 "\nctonefreq=",
1601 "\nfixspeed=",
1602 "\nunlimitedattempt=",
1603 "\nf6=",
1604 "\nrisetime="
1605 };
1606 char *conf1;
1607 char *conf2;
1608 char *find, *findend;
1609 int i, len, conf1len, conf2len;
1610 int j;
1611
1612 conf2 = malloc(1);
1613
1614 if ((fh = fopen(rcfilename, "rb")) == NULL) {
1615 endwin();
1616 fprintf(stderr, "Unable to open config file '%s'!\n", rcfilename);
1617 exit(EXIT_FAILURE);
1618 }
1619
1620 fseek(fh, 0, SEEK_END);
1621 j = (int) ftell(fh);
1622 conf1 = malloc((size_t) j+1);
1623
1624 rewind(fh);
1625 i = fread(conf1, sizeof(char), (size_t) j, fh);
1626 conf1[j] = '\0';
1627 conf1len = j;
1628
1629 fclose(fh);
1630
1631 /* The whole config file is now in conf1
1632 *
1633 * For each config option, search&replace it with the current value.
1634 * Only accept key=value pairs if the key starts on pos 0 of the line
1635 * */
1636
1637 //endwin();
1638 for (i = 0; i < 12; i++) {
1639 /* assemble new string for this conf option*/
1640 switch (i) {
1641 case 0:
1642 sprintf(tmp, "%s%s ", confopts[i], mycall);
1643 break;
1644 case 1:
1645 sprintf(tmp, "%s%s ", confopts[i], cbfilename);
1646 break;
1647 case 2:
1648 sprintf(tmp, "%s%s ", confopts[i], dspdevice);
1649 break;
1650 case 3:
1651 sprintf(tmp, "%s%d ", confopts[i], initialspeed);
1652 break;
1653 case 4:
1654 sprintf(tmp, "%s%d ", confopts[i], mincharspeed);
1655 break;
1656 case 5:
1657 sprintf(tmp, "%s%d ", confopts[i], waveform);
1658 break;
1659 case 6:
1660 sprintf(tmp, "%s%d ", confopts[i], constanttone);
1661 break;
1662 case 7:
1663 sprintf(tmp, "%s%d ", confopts[i], ctonefreq);
1664 break;
1665 case 8:
1666 sprintf(tmp, "%s%d ", confopts[i], fixspeed);
1667 break;
1668 case 9:
1669 sprintf(tmp, "%s%d ", confopts[i], unlimitedattempt);
1670 break;
1671 case 10:
1672 sprintf(tmp, "%s%d ", confopts[i], f6);
1673 break;
1674 case 11:
1675 sprintf(tmp, "%s%f ", confopts[i], edge);
1676 break;
1677 }
1678
1679 /* Conf option already in rc-file? */
1680 if ((find = strstr(conf1, confopts[i])) != NULL) {
1681 /* determine length. */
1682 findend = find;
1683 findend++; /* starts with \n, always skip it */
1684 while (!isspace(*findend++));
1685 len = findend - find;
1686
1687 /* old size of conf1: conf1len (see above)
1688 * new size: conf1len - len + strlen(tmp)*/
1689 conf2len = conf1len - len + strlen(tmp);
1690 conf2 = realloc(conf2, (size_t) conf2len);
1691 memcpy(conf2, conf1, (find - conf1));
1692 memcpy(conf2 + (find - conf1), tmp, strlen(tmp));
1693 memcpy(conf2 + (find - conf1) + strlen(tmp), findend,
1694 (size_t) conf2len - (find - conf1) - strlen(tmp));
1695 }
1696 /* otherwise, add to the end */
1697 else {
1698 /* CR LF or LF only? */
1699 if (strstr(conf1, "\r")) {
1700 strcat(tmp, "\r");
1701 }
1702 strcat(tmp, "\n");
1703 conf2len = conf1len + strlen(tmp) - 1;
1704 conf2 = realloc(conf2, conf2len);
1705 memcpy(conf2, conf1, conf1len-1); // excl. \0
1706 memcpy(conf2 + conf1len - 1, tmp, strlen(tmp)); // (incl. \0)
1707 }
1708 conf1 = realloc(conf1, (size_t) conf2len);
1709 conf1len = conf2len;
1710 if (conf1 == NULL) {
1711 exit(0);
1712 }
1713 memcpy(conf1, conf2, conf1len);
1714 }
1715
1716 if ((fh = fopen(rcfilename, "wb")) == NULL) {
1717 endwin();
1718 fprintf(stderr, "Unable to open config file '%s'!\n", rcfilename);
1719 exit(EXIT_FAILURE);
1720 }
1721
1722 fwrite(conf1, conf1len, sizeof(char), fh);
1723 fclose(fh);
1724
1725 free(conf1);
1726 free(conf2);
1727
1728 return 0;
1729 }
1730
thread_fail(int j)1731 static void thread_fail (int j) {
1732 if (j) {
1733 endwin();
1734 perror("Error: Unable to create cwthread!\n");
1735 exit(EXIT_FAILURE);
1736 }
1737 }
1738
1739 /* Add timestamps to toplist file if not there yet */
check_toplist()1740 static int check_toplist () {
1741 char line[80]="";
1742 char tmp[80]="";
1743 FILE *fh;
1744 FILE *fh2;
1745
1746 if ((fh = fopen(tlfilename, "r+")) == NULL) {
1747 endwin();
1748 perror("Unable to open toplist file 'toplist'!\n");
1749 exit(EXIT_FAILURE);
1750 }
1751
1752 fgets(tmp, 35, fh);
1753
1754 rewind(fh);
1755
1756 if (strlen(tmp) == 21) {
1757 printw("Toplist file in old format. Converting...");
1758 strcpy(tmp, "cp -f ");
1759 strcat(tmp, tlfilename);
1760 strcat(tmp, " /tmp/qrq-toplist");
1761 if (system(tmp)) {
1762 printw("Failed to copy to /tmp/qrq-toplist\n");
1763 getch();
1764 endwin();
1765 exit(EXIT_FAILURE);
1766 }
1767
1768 fh2 = fopen("/tmp/qrq-toplist", "r+"); /* should work ... */
1769
1770 while ((feof(fh2) == 0) && (fgets(line, 35, fh2) != NULL)) {
1771 line[20]=' ';
1772 strcpy(tmp, line);
1773 strcat(tmp, "1181234567\n");
1774 fputs(tmp, fh);
1775 }
1776
1777 printw(" done!\n");
1778
1779 fclose(fh2);
1780
1781 }
1782
1783 fclose(fh);
1784
1785 return 0;
1786 }
1787
1788
1789
1790 /* See where our files are. We need 'callbase.qcb', 'qrqrc' and 'toplist'.
1791 * The can be:
1792 * 1) In the current directory -> use them
1793 * 2) In ~/.qrq/ -> use toplist and qrqrc from there and callbase from
1794 * DESTDIR/share/qrq/
1795 * 3) in DESTDIR/share/qrq/ -> create ~/.qrq/ and copy qrqrc and toplist
1796 * there.
1797 * 4) Nowhere --> Exit.*/
find_files()1798 static int find_files () {
1799
1800 FILE *fh;
1801 const char *homedir = NULL;
1802 char tmp_rcfilename[PATH_MAX] = "";
1803 char tmp_tlfilename[PATH_MAX] = "";
1804 char tmp_cbfilename[PATH_MAX] = "";
1805
1806 printw("\nChecking for necessary files (qrqrc, toplist, callbase)...\n");
1807
1808 if (((fh = fopen("qrqrc", "r")) == NULL) ||
1809 ((fh = fopen("toplist", "r")) == NULL) ||
1810 ((fh = fopen("callbase.qcb", "r")) == NULL)) {
1811
1812 if ((homedir = getenv("HOME")) != NULL) {
1813 printw("... not found in current directory. Checking %s/.qrq/...\n", homedir);
1814 refresh();
1815 strcat(rcfilename, homedir);
1816 }
1817 else {
1818 printw("... not found in current directory. Checking ./.qrq/...\n");
1819 refresh();
1820 strcat(rcfilename, ".");
1821 }
1822
1823 strcat(rcfilename, "/.qrq/qrqrc");
1824
1825 /* check if there is ~/.qrq/qrqrc. If it's there, it's safe to assume
1826 * that toplist also exists at the same place and callbase exists in
1827 * DESTDIR/share/qrq/. */
1828
1829 if ((fh = fopen(rcfilename, "r")) == NULL ) {
1830 printw("... not found in %s/.qrq/. Checking %s/share/qrq..."
1831 "\n", homedir, destdir);
1832 /* check for the files in DESTDIR/share/qrq/. if exists, copy
1833 * qrqrc and toplist to ~/.qrq/ */
1834
1835 strcpy(tmp_rcfilename, destdir);
1836 strcat(tmp_rcfilename, "/share/qrq/qrqrc");
1837 strcpy(tmp_tlfilename, destdir);
1838 strcat(tmp_tlfilename, "/share/qrq/toplist");
1839 strcpy(tmp_cbfilename, destdir);
1840 strcat(tmp_cbfilename, "/share/qrq/callbase.qcb");
1841
1842 if (((fh = fopen(tmp_rcfilename, "r")) == NULL) ||
1843 ((fh = fopen(tmp_tlfilename, "r")) == NULL) ||
1844 ((fh = fopen(tmp_cbfilename, "r")) == NULL)) {
1845 printw("Sorry: Couldn't find 'qrqrc', 'toplist' and"
1846 " 'callbase.qcb' anywhere. Exit.\n");
1847 getch();
1848 endwin();
1849 exit(EXIT_FAILURE);
1850 }
1851 else { /* finally found it in DESTDIR/share/qrq/ ! */
1852 /* abusing rcfilename here for something else temporarily */
1853 printw("Found files in %s/share/qrq/."
1854 "\nCreating directory %s/.qrq/ and copy qrqrc and"
1855 " toplist there.\n", destdir, homedir);
1856 strcpy(rcfilename, homedir);
1857 strcat(rcfilename, "/.qrq/");
1858 #ifdef WIN32
1859 j = mkdir(rcfilename);
1860 #else
1861 j = mkdir(rcfilename, 0777);
1862 #endif
1863 if (j && (errno != EEXIST)) {
1864 printw("Failed to create %s! Exit.\n", rcfilename);
1865 getch();
1866 endwin();
1867 exit(EXIT_FAILURE);
1868 }
1869
1870 /* OK, now we created the directory, we can read in
1871 * DESTDIR/local/, so I assume copying files won't cause any
1872 * problem, with system()... */
1873
1874 strcpy(rcfilename, "install -m 644 ");
1875 strcat(rcfilename, tmp_tlfilename);
1876 strcat(rcfilename, " ");
1877 strcat(rcfilename, homedir);
1878 strcat(rcfilename, "/.qrq/ 2> /dev/null");
1879 if (system(rcfilename)) {
1880 printw("Failed to copy toplist file: %s\n", rcfilename);
1881 getch();
1882 endwin();
1883 exit(EXIT_FAILURE);
1884 }
1885 strcpy(rcfilename, "install -m 644 ");
1886 strcat(rcfilename, tmp_rcfilename);
1887 strcat(rcfilename, " ");
1888 strcat(rcfilename, homedir);
1889 strcat(rcfilename, "/.qrq/ 2> /dev/null");
1890 if (system(rcfilename)) {
1891 printw("Failed to copy qrqrc file: %s\n", rcfilename);
1892 getch();
1893 endwin();
1894 exit(EXIT_FAILURE);
1895 }
1896 printw("Files copied. You might want to edit "
1897 "qrqrc according to your needs.\n", homedir);
1898 strcpy(rcfilename, homedir);
1899 strcat(rcfilename, "/.qrq/qrqrc");
1900 strcpy(tlfilename, homedir);
1901 strcat(tlfilename, "/.qrq/toplist");
1902 strcpy(cbfilename, tmp_cbfilename);
1903 strcpy(sumfilepath, homedir);
1904 strcat(sumfilepath, "/.qrq/Summary");
1905 } /* found in DESTDIR/share/qrq/ */
1906 }
1907 else {
1908 printw("... found files in %s/.qrq/.\n", homedir);
1909 strcpy(tlfilename, homedir);
1910 strcat(tlfilename, "/.qrq/toplist");
1911 strcpy(cbfilename, destdir);
1912 strcat(cbfilename, "/share/qrq/callbase.qcb");
1913 strcpy(sumfilepath, homedir);
1914 strcat(sumfilepath, "/.qrq/Summary");
1915 }
1916 }
1917 else {
1918 printw("... found in current directory.\n");
1919 strcpy(rcfilename, "qrqrc");
1920 strcpy(tlfilename, "toplist");
1921 strcpy(cbfilename, "callbase.qcb");
1922 strcpy(sumfilepath, "Summary");
1923 }
1924 #ifdef WIN32
1925 mkdir(sumfilepath);
1926 #else
1927 mkdir(sumfilepath, 0777);
1928 #endif
1929 refresh();
1930 fclose(fh);
1931 return 0;
1932 }
1933
1934
statistics()1935 static int statistics () {
1936 char line[80]="";
1937
1938 int time = 0;
1939 int score = 0;
1940 int count= 0;
1941
1942 FILE *fh;
1943 FILE *fh2;
1944
1945 if ((fh = fopen(tlfilename, "r")) == NULL) {
1946 fprintf(stderr, "Unable to open toplist.");
1947 exit(0);
1948 }
1949
1950 if ((fh2 = fopen("/tmp/qrq-plot", "w+")) == NULL) {
1951 fprintf(stderr, "Unable to open /tmp/qrq-plot.");
1952 exit(0);
1953 }
1954
1955 fprintf(fh2, "set yrange [0:]\nset xlabel \"Date/Time\"\n"
1956 "set title \"QRQ scores for %s. Press 'q' to "
1957 "close this window.\"\n"
1958 "set ylabel \"Score\"\nset xdata time\nset "
1959 " timefmt \"%%s\"\n "
1960 "plot \"-\" using 1:2 title \"\"\n", mycall);
1961
1962 while ((feof(fh) == 0) && (fgets(line, 80, fh) != NULL)) {
1963 if ((strstr(line, mycall) != NULL)) {
1964 count++;
1965 sscanf(line, "%*s %d %*d %d", &score, &time);
1966 fprintf(fh2, "%d %d\n", time, score);
1967 }
1968 }
1969
1970 if (!count) {
1971 fprintf(fh2, "0 0\n");
1972 }
1973
1974 fprintf(fh2, "end\npause 10000");
1975
1976 fclose(fh);
1977 fclose(fh2);
1978
1979 system("gnuplot -p /tmp/qrq-plot 2> /dev/null &");
1980 return 0;
1981 }
1982
1983
read_callbase()1984 int read_callbase () {
1985 FILE *fh;
1986 int c,i;
1987 int maxlen=0;
1988 char tmp[80] = "";
1989 int nr=0;
1990
1991 if ((fh = fopen(cbfilename, "r")) == NULL) {
1992 endwin();
1993 fprintf(stderr, "Error: Couldn't read callsign database ('%s')!\n",
1994 cbfilename);
1995 exit(EXIT_FAILURE);
1996 }
1997
1998 /* count the lines/calls and lengths */
1999 i=0;
2000 while ((c = getc(fh)) != EOF) {
2001 i++;
2002 if (c == '\n') {
2003 nr++;
2004 maxlen = (i > maxlen) ? i : maxlen;
2005 i = 0;
2006 }
2007 }
2008 maxlen++;
2009
2010 if (!nr) {
2011 endwin();
2012 printf("\nError: Callsign database empty, no calls read. Exiting.\n");
2013 exit(EXIT_FAILURE);
2014 }
2015
2016 /* allocate memory for calls array, free if needed */
2017 if (calls) {
2018 for (i = 0; i < nrofcalls; i++) {
2019 free(calls[i]);
2020 calls[i] = NULL;
2021 }
2022 free(calls);
2023 calls = NULL;
2024 }
2025
2026 if ((calls = (char **) malloc( (size_t) sizeof(char *)*nr )) == NULL) {
2027 fprintf(stderr, "Error: Couldn't allocate %d bytes!\n",
2028 (int) sizeof(char)*nr);
2029 exit(EXIT_FAILURE);
2030 }
2031
2032 /* Allocate each element of the array with size maxlen */
2033 for (c=0; c < nr; c++) {
2034 if ((calls[c] = (char *) malloc (maxlen * sizeof(char))) == NULL) {
2035 fprintf(stderr, "Error: Couldn't allocate %d bytes!\n", maxlen);
2036 exit(EXIT_FAILURE);
2037 }
2038 }
2039
2040 rewind(fh);
2041
2042 nr=0;
2043 while (fgets(tmp,maxlen,fh) != NULL) {
2044 for (i = 0; i < strlen(tmp); i++) {
2045 tmp[i] = toupper(tmp[i]);
2046 }
2047 tmp[i-1]='\0'; /* remove newline */
2048 if (tmp[i-2] == '\r') { /* also for DOS files */
2049 tmp[i-2] = '\0';
2050 }
2051 strcpy(calls[nr],tmp);
2052 nr++;
2053 if (nr == c) /* may happen if call file corrupted */
2054 break;
2055 }
2056 fclose(fh);
2057
2058
2059 return nr;
2060
2061 }
2062
find_callbases()2063 void find_callbases () {
2064 DIR *dir;
2065 struct dirent *dp;
2066 char tmp[PATH_MAX];
2067 char path[3][PATH_MAX];
2068 int i=0,j=0,k=0;
2069
2070 #ifndef WIN32
2071 strcpy(path[0], getenv("PWD"));
2072 strcat(path[0], "/");
2073 strcpy(path[1], getenv("HOME"));
2074 strcat(path[1], "/.qrq/");
2075 strcpy(path[2], destdir);
2076 strcat(path[2], "/share/qrq/");
2077 #else
2078 strcpy(path[0], "./");
2079 strcpy(path[1], getenv("APPDATA"));
2080 strcat(path[1], "/qrq/");
2081 strcpy(path[2], "c:\\");
2082 #endif
2083
2084 for (i=0; i < 100; i++) {
2085 strcpy(cblist[i], "");
2086 }
2087
2088 /* foreach paths... */
2089 for (k = 0; k < 3; k++) {
2090
2091 if (!(dir = opendir(path[k]))) {
2092 continue;
2093 }
2094
2095 while ((dp = readdir(dir))) {
2096 strcpy(tmp, dp->d_name);
2097 i = strlen(tmp);
2098 /* find *.qcb files ... */
2099 if (i>4 && tmp[i-1] == 'b' && tmp[i-2] == 'c' && tmp[i-3] == 'q') {
2100 strcpy(cblist[j], path[k]);
2101 strcat(cblist[j], tmp);
2102 j++;
2103 }
2104 }
2105 } /* for paths */
2106 }
2107
2108
2109
select_callbase()2110 void select_callbase () {
2111 int i = 0, j = 0, k = 0;
2112 int c = 0; /* cursor position */
2113 int p = 0; /* page a 10 entries */
2114 char* cblist_ptr;
2115
2116
2117 curs_set(FALSE);
2118
2119 /* count files */
2120 while (strcmp(cblist[i], "")) i++;
2121
2122 if (!i) {
2123 mvwprintw(conf_w,10,4, "No qcb-files found!");
2124 wrefresh(conf_w);
2125 #ifdef WIN32
2126 Sleep(1000);
2127 #else
2128 sleep(1);
2129 #endif
2130 return;
2131 }
2132
2133 /* loop for key unput */
2134 while (1) {
2135
2136 /* cls */
2137 for (j = 5; j < 16; j++) {
2138 mvwprintw(conf_w,j,2, " ");
2139 }
2140
2141 /* display 10 files, highlight cursor position */
2142 for (j = p*10; j < (p+1)*10; j++) {
2143 if (j <= i) {
2144 cblist_ptr = cblist[j];
2145 mvwprintw(conf_w,5+(j - p*10 ),2, " %s ", cblist_ptr);
2146 }
2147 if (c == j) { /* cursor */
2148 mvwprintw(conf_w,5+(j - p*10),2, ">");
2149 }
2150 }
2151
2152 wrefresh(conf_w);
2153
2154 k = getch();
2155
2156 switch ((int) k) {
2157 case KEY_UP:
2158 c = (c > 0) ? (c-1) : c;
2159 if (!((c+1) % 10)) { /* scroll down */
2160 p = (p > 0) ? (p-1) : p;
2161 }
2162 break;
2163 case KEY_DOWN:
2164 c = (c < i-1) ? (c+1) : c;
2165 if (c && !(c % 10)) { /* scroll down */
2166 p++;
2167 }
2168 break;
2169 case '\n':
2170 strcpy(cbfilename, cblist[c]);
2171 nrofcalls = read_callbase();
2172 return;
2173 break;
2174 }
2175
2176 wrefresh(conf_w);
2177
2178 } /* while 1 */
2179
2180 curs_set(TRUE);
2181
2182 }
2183
2184
2185
help()2186 void help () {
2187 printf("qrq v%s (c) 2006-2019 Fabian Kurz, DJ1YFK. "
2188 "http://fkurz.net/ham/qrq.html\n", VERSION);
2189 printf("High speed morse telegraphy trainer, similar to"
2190 " RUFZ.\n\n");
2191 printf("This is free software, and you are welcome to"
2192 " redistribute it\n");
2193 printf("under certain conditions (see COPYING).\n\n");
2194 printf("Start 'qrq' without any command line arguments for normal"
2195 " operation.\n\n");
2196 #ifdef BUILD_INFO
2197 printf("Build info for this executable:\n%s\n", BUILD_INFO);
2198 #endif
2199 exit(0);
2200 }
2201
2202
2203 /* vim: noai:ts=4:sw=4
2204 */
2205