code:
/*
 This file is part of program wsprd, a detector/demodulator/decoder
 for the Weak Signal Propagation Reporter (WSPR) mode.

 File name: wsprd_utils.c

 Copyright 2001-2015, Joe Taylor, K1JT

 Most of the code is based on work by Steven Franke, K9AN, which
 in turn was based on earlier work by K1JT.

 Copyright 2014-2015, Steven Franke, K9AN

 License: GNU GPL v3

 This program is free software: you can redistribute it and/or modify
 it under the terms of the GNU General Public License as published by
 the Free Software Foundation, either version 3 of the License, or
 (at your option) any later version.

 This program is distributed in the hope that it will be useful,
 but WITHOUT ANY WARRANTY; without even the implied warranty of
 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 GNU General Public License for more details.

 You should have received a copy of the GNU General Public License
 along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
#include "wsprd_utils.h"

#ifndef int32_t
#define int32_t int
#endif

void unpack50( signed char *dat, int32_t *n1, int32_t *n2 )
{
    int32_t i,i4;

    i=dat[0];
    i4=i&255;
    *n1=i4<<20;

    i=dat[1];
    i4=i&255;
    *n1=*n1+(i4<<12);

    i=dat[2];
    i4=i&255;
    *n1=*n1+(i4<<4);

    i=dat[3];
    i4=i&255;
    *n1=*n1+((i4>>4)&15);
    *n2=(i4&15)<<18;

    i=dat[4];
    i4=i&255;
    *n2=*n2+(i4<<10);

    i=dat[5];
    i4=i&255;
    *n2=*n2+(i4<<2);

    i=dat[6];
    i4=i&255;
    *n2=*n2+((i4>>6)&3);
}

int unpackcall( int32_t ncall, char *call )
{
    char c[]={'0','1','2','3','4','5','6','7','8','9','A','B','C','D','E',
        'F','G','H','I','J','K','L','M','N','O','P','Q','R','S','T',
        'U','V','W','X','Y','Z',' '};
    int32_t n;
    int i;
    char tmp[7];

    n=ncall;
    strcpy(call,"......");
    if (n < 262177560 ) {
        i=n%27+10;
        tmp[5]=c[i];
        n=n/27;
        i=n%27+10;
        tmp[4]=c[i];
        n=n/27;
        i=n%27+10;
        tmp[3]=c[i];
        n=n/27;
        i=n%10;
        tmp[2]=c[i];
        n=n/10;
        i=n%36;
        tmp[1]=c[i];
        n=n/36;
        i=n;
        tmp[0]=c[i];
        tmp[6]='\0';
        // remove leading whitespace
        for(i=0; i<5; i++) {
            if( tmp[i] != c[36] )
                break;
        }
        sprintf(call,"%-6s",&tmp[i]);
        // remove trailing whitespace
        for(i=0; i<6; i++) {
            if( call[i] == c[36] ) {
                call[i]='\0';
            }
        }
    } else {
        return 0;
    }
    return 1;
}

int unpackgrid( int32_t ngrid, char *grid)
{
    char c[]={'0','1','2','3','4','5','6','7','8','9','A','B','C','D','E',
        'F','G','H','I','J','K','L','M','N','O','P','Q','R','S','T',
        'U','V','W','X','Y','Z',' '};
    int dlat, dlong;

    ngrid=ngrid>>7;
    if( ngrid < 32400 ) {
        dlat=(ngrid%180)-90;
        dlong=(ngrid/180)*2 - 180 + 2;
        if( dlong < -180 )
            dlong=dlong+360;
        if( dlong > 180 )
            dlong=dlong+360;
        int nlong = 60.0*(180.0-dlong)/5.0;
        int n1 = nlong/240;
        int n2 = (nlong - 240*n1)/24;
        grid[0] = c[10+n1];
        grid[2]=  c[n2];

        int nlat = 60.0*(dlat+90)/2.5;
        n1 = nlat/240;
        n2 = (nlat-240*n1)/24;
        grid[1]=c[10+n1];
        grid[3]=c[n2];
    } else {
        strcpy(grid,"XXXX");
        return 0;
    }
    return 1;
}

int unpackpfx( int32_t nprefix, char *call)
{
    char nc, pfx[4]={'\0'}, tmpcall[7];
    int i;
    int32_t n;

    strcpy(tmpcall,call);
    if( nprefix < 60000 ) {
        // add a prefix of 1 to 3 characters
        n=nprefix;
        for (i=2; i>=0; i--) {
            nc=n%37;
            if( (nc >= 0) & (nc <= 9) ) {
                pfx[i]=nc+48;
            }
            else if( (nc >= 10) & (nc <= 35) ) {
                pfx[i]=nc+55;
            }
            else {
                pfx[i]=' ';
            }
            n=n/37;
        }

        char * p = strrchr(pfx,' ');
        strcpy(call, p ? p + 1 : pfx);
        strncat(call,"/",1);
        strncat(call,tmpcall,strlen(tmpcall));

    } else {
        // add a suffix of 1 or 2 characters
        nc=nprefix-60000;
        if( (nc >= 0) & (nc <= 9) ) {
            pfx[0]=nc+48;
            strcpy(call,tmpcall);
            strncat(call,"/",1);
            strncat(call,pfx,1);
        }
        else if( (nc >= 10) & (nc <= 35) ) {
            pfx[0]=nc+55;
            strcpy(call,tmpcall);
            strncat(call,"/",1);
            strncat(call,pfx,1);
        }
        else if( (nc >= 36) & (nc <= 125) ) {
            pfx[0]=(nc-26)/10+48;
            pfx[1]=(nc-26)%10+48;
            strcpy(call,tmpcall);
            strncat(call,"/",1);
            strncat(call,pfx,2);
        }
        else {
            return 0;
        }
    }
    return 1;
}

void deinterleave(unsigned char *sym)
{
    unsigned char tmp[162];
    unsigned char p, i, j;

    p=0;
    i=0;
    while (p<162) {
        j=((i * 0x80200802ULL) & 0x0884422110ULL) * 0x0101010101ULL >> 32;
        if (j < 162 ) {
            tmp[p]=sym[j];
            p=p+1;
        }
        i=i+1;
    }
    for (i=0; i<162; i++) {
        sym[i]=tmp[i];
    }
}

// used by qsort
int doublecomp(const void* elem1, const void* elem2)
{
    if(*(const double*)elem1 < *(const double*)elem2)
        return -1;
    return *(const double*)elem1 > *(const double*)elem2;
}

int floatcomp(const void* elem1, const void* elem2)
{
    if(*(const float*)elem1 < *(const float*)elem2)
        return -1;
    return *(const float*)elem1 > *(const float*)elem2;
}

int unpk_(signed char *message, char *hashtab, char *call_loc_pow, char *callsign)
{
    int n1,n2,n3,ndbm,ihash,nadd,noprint=0;
    char grid[5],grid6[7],cdbm[3];

    unpack50(message,&n1,&n2);
    if( !unpackcall(n1,callsign) ) return 1;
    if( !unpackgrid(n2, grid) ) return 1;
    int ntype = (n2&127) - 64;
    callsign[12]=0;
    grid[4]=0;

    /*
     Based on the value of ntype, decide whether this is a Type 1, 2, or
     3 message.

     * Type 1: 6 digit call, grid, power - ntype is positive and is a member
     of the set {0,3,7,10,13,17,20...60}

     * Type 2: extended callsign, power - ntype is positive but not
     a member of the set of allowed powers

     * Type 3: hash, 6 digit grid, power - ntype is negative.
     */

    if( (ntype >= 0) && (ntype <= 62) ) {
        int nu=ntype%10;
        if( nu == 0 || nu == 3 || nu == 7 ) {
            ndbm=ntype;
            memset(call_loc_pow,0,sizeof(char)*23);
            sprintf(cdbm,"%2d",ndbm);
            strncat(call_loc_pow,callsign,strlen(callsign));
            strncat(call_loc_pow," ",1);
            strncat(call_loc_pow,grid,4);
            strncat(call_loc_pow," ",1);
            strncat(call_loc_pow,cdbm,2);
            strncat(call_loc_pow,"\0",1);
            ihash=nhash(callsign,strlen(callsign),(uint32_t)146);
            strcpy(hashtab+ihash*13,callsign);
        } else {
            nadd=nu;
            if( nu > 3 ) nadd=nu-3;
            if( nu > 7 ) nadd=nu-7;
            n3=n2/128+32768*(nadd-1);
            if( !unpackpfx(n3,callsign) ) return 1;
            ndbm=ntype-nadd;
            memset(call_loc_pow,0,sizeof(char)*23);
            sprintf(cdbm,"%2d",ndbm);
            strncat(call_loc_pow,callsign,strlen(callsign));
            strncat(call_loc_pow," ",1);
            strncat(call_loc_pow,cdbm,2);
            strncat(call_loc_pow,"\0",1);
            int nu=ndbm%10;
            if( nu == 0 || nu == 3 || nu == 7 || nu == 10 ) { //make sure power is OK
                ihash=nhash(callsign,strlen(callsign),(uint32_t)146);
                strcpy(hashtab+ihash*13,callsign);
            } else noprint=1;
        }
    } else if ( ntype < 0 ) {
        ndbm=-(ntype+1);
        memset(grid6,0,sizeof(char)*7);
//        size_t len=strlen(callsign);
        size_t len=6;
        strncat(grid6,callsign+len-1,1);
        strncat(grid6,callsign,len-1);
        int nu=ndbm%10;
        if ((nu != 0 && nu != 3 && nu != 7 && nu != 10) ||
            !isalpha(grid6[0]) || !isalpha(grid6[1]) ||
            !isdigit(grid6[2]) || !isdigit(grid6[3])) {
               // not testing 4'th and 5'th chars because of this case: <PA0SKT/2> JO33 40
               // grid is only 4 chars even though this is a hashed callsign...
               //         isalpha(grid6[4]) && isalpha(grid6[5]) ) ) {
          noprint=1;
        }

        ihash=(n2-ntype-64)/128;
        if( strncmp(hashtab+ihash*13,"\0",1) != 0 ) {
            sprintf(callsign,"<%s>",hashtab+ihash*13);
        } else {
            sprintf(callsign,"%5s","<...>");
        }

        memset(call_loc_pow,0,sizeof(char)*23);
        sprintf(cdbm,"%2d",ndbm);
        strncat(call_loc_pow,callsign,strlen(callsign));
        strncat(call_loc_pow," ",1);
        strncat(call_loc_pow,grid6,strlen(grid6));
        strncat(call_loc_pow," ",1);
        strncat(call_loc_pow,cdbm,2);
        strncat(call_loc_pow,"\0",1);

        // I don't know what to do with these... They show up as "A000AA" grids.
        if( ntype == -64 ) noprint=1;
    }
    return noprint;
}


code:
#ifndef WSPRD_UTILS_H
#define WSPRD_UTILS_H

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <ctype.h>
#include <string.h>
#include <stdint.h>
#include <time.h>
#include "nhash.h"

void unpack50( signed char *dat, int32_t *n1, int32_t *n2 );

int unpackcall( int32_t ncall, char *call );

int unpackgrid( int32_t ngrid, char *grid);

int unpackpfx( int32_t nprefix, char *call);

void deinterleave(unsigned char *sym);

// used by qsort
int doublecomp(const void* elem1, const void* elem2);
int floatcomp(const void* elem1, const void* elem2);

int unpk_( signed char *message, char* hashtab, char *call_loc_pow, char *callsign);

#endif

license: mit


code:
/*
 File name: wsprsim.c (first committed to wsjtx June 13, 2015)
 */
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <string.h>

#include "wsprsim_utils.h"
#include "wsprd_utils.h"
#include "fano.h"

int printdata=0;

void usage() {
    printf("Usage: wsprsim [options] message\n");
    printf("       message format:   \"K1ABC FN42 33\"\n");
    printf("                         \"PJ4/K1ABC 33\"\n");
    printf("                         \"<PJ4/K1ABC> FK52UD 33\"\n");
    printf("Options:\n");
    printf("       -c   (print channel symbols)\n");
    printf("       -d   (print packed data with zero tail - 11 bytes)\n");
    printf("       -f x (-100 Hz < f < 100 Hz)\n");
    printf("       -o filename (write a c2 file with this name)\n");
    printf("       -s x (x is snr of signal that is written to .c2 file)\n");
    printf("\n");
    printf(" e.g. ./wsprsim -cds -28 -o 150613_1920.c2 \"K1ABC FN42 33\"\n");
    printf(" then ./wsprd 150613_1920.c2\n");
}

int add_signal_vector(float f0, float t0, float amp, unsigned char* symbols
                      , double* isig, double* qsig)
{
    int i, j, ii, idelay;
    double phi=0.0, twopidt, df, dt, dphi;
    twopidt=8.0*atan(1.0)/375.0;
    df=375.0/256.0;
    dt=1/375.0;
    idelay=t0/dt;

    for (i=0; i<162; i++) {
        dphi=twopidt*(f0 + ( (double)symbols[i]-1.5)*df );
        for ( j=0; j<256; j++ ) {
            ii=idelay+256*i+j;
            isig[ii]=isig[ii]+amp*cos(phi);
            qsig[ii]=qsig[ii]+amp*sin(phi);
            phi=phi+dphi;
        }
    }
    return 1;
}

char* tobinary(int x)
{
    static char b[33];
    b[0] = '\0';

    long unsigned int z;
    for (z = 0x80000000; z > 0; z >>= 1)
    {
        strcat(b, ((x & z) == z) ? "1" : "0");
    }

    return b;
}

double gaussrand()
{
    static double V1, V2, S;
    static int phase = 0;
    double X;

    if(phase == 0) {
        do {
            double U1 = (double)rand() / RAND_MAX;
            double U2 = (double)rand() / RAND_MAX;

            V1 = 2 * U1 - 1;
            V2 = 2 * U2 - 1;
            S = V1 * V1 + V2 * V2;
        } while(S >= 1 || S == 0);

        X = V1 * sqrt(-2 * log(S) / S);
    } else
        X = V2 * sqrt(-2 * log(S) / S);

    phase = 1 - phase;

    return X;
}

unsigned long writec2file(char *c2filename, int trmin, double freq
                          , double *idat, double *qdat)
{
    int i;
    float buffer[2*45000];
    memset(buffer,0,sizeof(float)*2*45000);
    FILE *fp;

    fp = fopen(c2filename,"wb");
    if( fp == NULL ) {
        fprintf(stderr, "Could not open c2 file '%s'\n", c2filename);
        return 0;
    }
    unsigned long nwrite = fwrite(c2filename,sizeof(char),14,fp);
    nwrite = fwrite(&trmin, sizeof(int), 1, fp);
    nwrite = fwrite(&freq, sizeof(double), 1, fp);

    for(i=0; i<45000; i++) {
        buffer[2*i]=idat[i];
        buffer[2*i+1]=-qdat[i];
    }

    nwrite = fwrite(buffer, sizeof(float), 2*45000, fp);
    if( nwrite == 2*45000 ) {
        return nwrite;
    } else {
        return 0;
    }
}

//********************************************************************
int main(int argc, char *argv[])
{
    extern char *optarg;
    extern int optind;
    int i, c, printchannel=0, writec2=0;
    float snr=50.0;
    float f0=0.0, t0=1.0;
    char *message, *c2filename, *hashtab;
    c2filename=malloc(sizeof(char)*15);
    hashtab=malloc(sizeof(char)*32768*13);
    memset(hashtab,0,sizeof(char)*32768*13);

    // message length is 22 characters
    message=malloc(sizeof(char)*23);

    strcpy(c2filename,"000000_0001.c2");

    srand(getpid());

    while ( (c = getopt(argc, argv, "cdf:o:s:")) !=-1 ) {
        switch (c) {
            case 'c':
                printchannel=1;
                break;
            case 'd':
                printdata=1;
                break;
            case 'f':
                f0 = atof(optarg);
            case 'o':
                c2filename = optarg;
                writec2=1;
                break;
            case 's':
//                snr = (float)atoi(optarg);
                snr = atof(optarg);
                break;
        }
    }

    if( optind+1 > argc ) {
        usage();
        return 0;
    } else {
        message=argv[optind];
    }

    unsigned char channel_symbols[162];
    get_wspr_channel_symbols(message, hashtab, channel_symbols);

    if( printchannel ) {
        printf("Channel symbols:\n");
        for (i=0; i<162; i++) {
            printf("%d ",channel_symbols[i]);
        }
        printf("\n");
    }

    // add noise, then signal
    double isig[45000], qsig[45000];
    memset(isig,0,sizeof(double)*45000);
    memset(qsig,0,sizeof(double)*45000);

    if( snr < 40 ) {
        // snr in 375Hz is 8.2 dB higher than in 2500 Hz.
        snr=snr+8.2;
        snr=pow(10,snr/20.0)*pow(2,0.5);

        for (i = 0; i<45000; i++) {
            isig[i]=isig[i]+gaussrand();
            qsig[i]=qsig[i]+gaussrand();
        }
    } else {
        snr=1.0;
    }

    add_signal_vector(f0, t0, snr, channel_symbols, isig, qsig);
    if( writec2) {
        // write a .c2 file
        double carrierfreq=10.1387;
        int wsprtype=2;
        printf("Writing %s\n",c2filename);
        writec2file(c2filename, wsprtype, carrierfreq, isig, qsig);
    }
    return 1;
}


code:
/*
 Functions used by wsprsim
 */
#include "wsprsim_utils.h"
#include "wsprd_utils.h"
#include "nhash.h"
#include "fano.h"

static char get_locator_character_code(char ch);
static char get_callsign_character_code(char ch);
static long unsigned int pack_grid4_power(char const *grid4, int power);
static long unsigned int pack_call(char const *callsign);
static void pack_prefix(char *callsign, int32_t *n, int32_t *m, int32_t *nadd );
static void interleave(unsigned char *sym);

char get_locator_character_code(char ch) {
    if( ch >=48 && ch <=57 ) { //0-9
        return ch-48;
    }
    if( ch == 32 ) {  //space
        return 36;
    }
    if( ch >= 65 && ch <= 82 ) { //A-Z
        return ch-65;
    }
    return -1;
}

char get_callsign_character_code(char ch) {
    if( ch >=48 && ch <=57 ) { //0-9
        return ch-48;
    }
    if( ch == 32 ) {  //space
        return 36;
    }
    if( ch >= 65 && ch <= 90 ) { //A-Z
        return ch-55;
    }
    return -1;
}

long unsigned int pack_grid4_power(char const *grid4, int power) {
    long unsigned int m;

    m=(179-10*grid4[0]-grid4[2])*180+10*grid4[1]+grid4[3];
    m=m*128+power+64;
    return m;
}

long unsigned int pack_call(char const *callsign) {
    unsigned int i;
    long unsigned int n;
    char call6[6];
    memset(call6,' ',sizeof(call6));
    // callsign is 6 characters in length. Exactly.
    size_t call_len = strlen(callsign);
    if( call_len > 6 ) {
        return 0;
    }
    if( isdigit(callsign[2]) ) {
        for (i=0; i<call_len; i++) {
            call6[i]=callsign[i];
        }
    } else if( isdigit(callsign[1]) ) {
        for (i=1; i<call_len+1; i++) {
            call6[i]=callsign[i-1];
        }
    }
    for (i=0; i<6; i++) {
        call6[i]=get_callsign_character_code(call6[i]);
    }
    n = call6[0];
    n = n*36+call6[1];
    n = n*10+call6[2];
    n = n*27+call6[3]-10;
    n = n*27+call6[4]-10;
    n = n*27+call6[5]-10;
    return n;
}

void pack_prefix(char *callsign, int32_t *n, int32_t *m, int32_t *nadd ) {
    size_t i;
    char * call6 = calloc(7,sizeof (char));
    size_t i1=strcspn(callsign,"/");

    if( callsign[i1+2] == 0 ) {
        //single char suffix
        for (i=0; i<i1; i++) {
            call6[i]=callsign[i];
        }
        call6[i] = '\0';
        *n=pack_call(call6);
        *nadd=1;
        int nc = callsign[i1+1];
        if( nc >= 48 && nc <= 57 ) {
            *m=nc-48;
        } else if ( nc >= 65 && nc <= 90 ) {
            *m=nc-65+10;
        } else {
            *m=38;
        }
        *m=60000-32768+*m;
    } else if( callsign[i1+3]==0 ) {
        //two char suffix
        for (i=0; i<i1; i++) {
            call6[i]=callsign[i];
        }
        *n=pack_call(call6);
        *nadd=1;
        *m=10*(callsign[i1+1]-48)+(callsign[i1+2]-48);
        *m=60000 + 26 + *m;
    } else {
        char const * pfx = strtok (callsign,"/");
        char const * call = strtok(NULL," ");
        *n = pack_call (call);
        size_t plen=strlen (pfx);
        if( plen ==1 ) {
            *m=36;
            *m=37*(*m)+36;
        } else if( plen == 2 ) {
            *m=36;
        } else {
            *m=0;
        }
        for (i=0; i<plen; i++) {
            int nc = callsign[i];
            if( nc >= 48 && nc <= 57 ) {
                nc=nc-48;
            } else if ( nc >= 65 && nc <= 90 ) {
                nc=nc-65+10;
            } else {
                nc=36;
            }
            *m=37*(*m)+nc;
        }
        *nadd=0;
        if( *m > 32768 ) {
            *m=*m-32768;
            *nadd=1;
        }
    }
    free (call6);
}

void interleave(unsigned char *sym)
{
    unsigned char tmp[162];
    unsigned char p, i, j;

    p=0;
    i=0;
    while (p<162) {
        j=((i * 0x80200802ULL) & 0x0884422110ULL) * 0x0101010101ULL >> 32;
        if (j < 162 ) {
            tmp[j]=sym[p];
            p=p+1;
        }
        i=i+1;
    }
    for (i=0; i<162; i++) {
        sym[i]=tmp[i];
    }
}

int get_wspr_channel_symbols(char* rawmessage, char* hashtab, unsigned char* symbols) {
    int m=0, ntype=0;
    long unsigned int n=0;
    int i, j, ihash;
    unsigned char pr3[162]=
    {1,1,0,0,0,0,0,0,1,0,0,0,1,1,1,0,0,0,1,0,
        0,1,0,1,1,1,1,0,0,0,0,0,0,0,1,0,0,1,0,1,
        0,0,0,0,0,0,1,0,1,1,0,0,1,1,0,1,0,0,0,1,
        1,0,1,0,0,0,0,1,1,0,1,0,1,0,1,0,1,0,0,1,
        0,0,1,0,1,1,0,0,0,1,1,0,1,0,1,0,0,0,1,0,
        0,0,0,0,1,0,0,1,0,0,1,1,1,0,1,1,0,0,1,1,
        0,1,0,0,0,1,1,1,0,0,0,0,0,1,0,1,0,0,1,1,
        0,0,0,0,0,0,0,1,1,0,1,0,1,1,0,0,0,1,1,0,
        0,0};
    int nu[10]={0,-1,1,0,-1,2,1,0,-1,1};
    char *callsign, *grid, *powstr;
    char grid4[5], message[23];

    memset(message,0,sizeof(char)*23);
    i=0;
    while ( rawmessage[i] != 0 && i<23 ) {
        message[i]=rawmessage[i];
        i++;
    }

    size_t i1=strcspn(message," ");
    size_t i2=strcspn(message,"/");
    size_t i3=strcspn(message,"<");
    size_t i4=strcspn(message,">");
    size_t mlen=strlen(message);

    // Use the presence and/or absence of "<" and "/" to decide what
    // type of message. No sanity checks! Beware!

    if( i1 > 3 && i1 < 7 && i2 == mlen && i3 == mlen ) {
        // Type 1 message: K9AN EN50 33
        //                 xxnxxxx xxnn nn
        callsign = strtok(message," ");
        grid = strtok(NULL," ");
        powstr = strtok(NULL," ");
        int power = atoi(powstr);
        n = pack_call(callsign);

        for (i=0; i<4; i++) {
            grid4[i]=get_locator_character_code(*(grid+i));
        }
        m = pack_grid4_power(grid4,power);

    } else if ( i3 == 0 && i4 < mlen ) {
        // Type 3:      <K1ABC> EN50WC 33
        //          <PJ4/K1ABC> FK52UD 37
        // send hash instead of callsign to make room for 6 char grid.
        // if 4-digit locator is specified, 2 spaces are added to the end.
        callsign=strtok(message,"<> ");
        grid=strtok(NULL," ");
        powstr=strtok(NULL," ");
        int power = atoi(powstr);
        if( power < 0 ) power=0;
        if( power > 60 ) power=60;
        power=power+nu[power%10];
        ntype=-(power+1);
        ihash=nhash(callsign,strlen(callsign),(uint32_t)146);
        m=128*ihash + ntype + 64;

        char grid6[7];
        memset(grid6,0,sizeof(char)*7);
        j=strlen(grid);
        for(i=0; i<j-1; i++) {
            grid6[i]=grid[i+1];
        }
        grid6[5]=grid[0];
        n = pack_call(grid6);
    } else if ( i2 < mlen ) {  // just looks for a right slash
        // Type 2: PJ4/K1ABC 37
        callsign = strtok (message," ");
        if( i2==0 || i2>strlen(callsign) ) return 0; //guards against pathological case
        powstr = strtok (NULL," ");
        int power = atoi (powstr);
        if( power < 0 ) power=0;
        if( power > 60 ) power=60;
        power=power+nu[power%10];
        int n1, ng, nadd;
        pack_prefix(callsign, &n1, &ng, &nadd);
        ntype=power + 1 + nadd;
        m=128*ng+ntype+64;
        n=n1;
    } else {
        return 0;
    }

    // pack 50 bits + 31 (0) tail bits into 11 bytes
    unsigned char it, data[11];
    memset(data,0,sizeof(char)*11);
    it=0xFF & (n>>20);
    data[0]=it;
    it=0xFF & (n>>12);
    data[1]=it;
    it=0xFF & (n>>4);
    data[2]=it;
    it= ((n&(0x0F))<<4) + ((m>>18)&(0x0F));
    data[3]=it;
    it=0xFF & (m>>10);
    data[4]=it;
    it=0xFF & (m>>2);
    data[5]=it;
    it=(m & 0x03)<<6 ;
    data[6]=it;
    data[7]=0;
    data[8]=0;
    data[9]=0;
    data[10]=0;

    if( printdata ) {
        printf("Data is :");
        for (i=0; i<11; i++) {
            printf("%02X ",data[i]);
        }
        printf("\n");
    }

    // make sure that the 11-byte data vector is unpackable
    // unpack it with the routine that the decoder will use and display
    // the result. let the operator decide whether it worked.

    char *check_call_loc_pow, *check_callsign;
    check_call_loc_pow=malloc(sizeof(char)*23);
    check_callsign=malloc(sizeof(char)*13);
    signed char check_data[11];
    memcpy(check_data,data,sizeof(char)*11);

    unpk_(check_data,hashtab,check_call_loc_pow,check_callsign);
//    printf("Will decode as: %s\n",check_call_loc_pow);

    unsigned int nbytes=11; // The message with tail is packed into almost 11 bytes.
    unsigned char channelbits[nbytes*8*2]; /* 162 rounded up */
    memset(channelbits,0,sizeof(char)*nbytes*8*2);

    encode(channelbits,data,nbytes);

    interleave(channelbits);

    for (i=0; i<162; i++) {
        symbols[i]=2*channelbits[i]+pr3[i];
    }
    free(check_call_loc_pow);
    free(check_callsign);
    return 1;
}


code:
#ifndef WSPRSIM_UTILS_H
#define WSPRSIM_UTILS_H

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <ctype.h>
#include <string.h>
#include <stdint.h>
#include <time.h>

extern int printdata;

int get_wspr_channel_symbols(char* message, char* hashtab, unsigned char* symbols);

#endif


code:
2E0DGP       IO83
5P6MJ        JO54
AA2UK        FM29
AA4HV        EM55
AA4QE        EM78
AA7UN        DN32
AB6BT        CM88
AB9QZ        EN41
AC4VM        EM78
AC9QI        EN52
AD0PL        EM48
AD9H         EN61
AF5NP        EM10
AF7M         DM42
AG4QX        EL87
AH0U         CM97
AK3Q         EM79
CC6IJM       FO03
CE4SFG       FF45
CF7GEM       CN89
CO2DC        EL83
CO2ER        EL83
CO2YQ        EL83
CO3LT        EL93
CT1FIU       IN50
CT7AEL       IM69
CU2DX        HM77
DL5UZ        JN49
DL6BEN       JO44
EA1AAE       IN81
EA5IGV       IM99
EA5ISK       IM99
EA5WO        IM99
EA5YI        IM99
EA7OM        IM87
EB3ENW       JN11
F6CAX        JO00
F6GCP        JN18
G4IUA        IO91
GI4FUE       IO74
HK2PMR       FJ29
HK4SAN       FJ26
HK6JCF       FJ25
I3FGX        JN55
IK2DJV       JN45
IK5BSC       JN53
IK8IJN       JM78
IN3BJS       JN55
IS0FWY       JM49
IU8CNE       JN71
IW4EJK       JN54
JA9AVA       PM86
JA9LJS       PM86
JR3IIR       PM74
K0AY         FN20
K1BMW        CM99
K1GG         EM97
K1HTV        FM18
K1VOI        FN32
K2AK         DM41
K2RMA        EM90
K2SST        EM85
K2TE         EL98
K3DBD        FN20
K3ZGA        EL98
K4DL         EM63
K4ELI        EM74
K4OP         EM77
K4SHQ        EM64
K5CIA        EM12
K5EJ         EM45
K5KLA        EL49
K5RCD        EL09
K5RWD        EL09
K5WP         EM71
K6JQ         CM88
K6MKF        CM97
K6RIM        CM87
K6SJT        DM13
K7CAH        CN87
K7NN         DM42
K7VNE        DM43
K9LRE        EM57
K9PY         EN61
KA1AQP       FN42
KA4RSZ       EM73
KA5JTM       EL29
KA6A         EM13
KA8TBW       EN61
KA9KQH       EM59
KB2M         EL99
KB5IKR       EM70
KC6AWX       CM88
KC9H         EN62
KC9UPE       EN53
KC9WIB       EN61
KC9WNZ       EM48
KD9E         EM58
KE0HQZ       EN12
KE0N         EN34
KE7HHW       DN13
KF4ZLO       EM78
KG5GCC       EM50
KG6TT        CM88
KG7GPM       DN17
KI7JFH       CN85
KI7MT        DN46
KK0M         CM97
KK6PTO       DM14
KM2J         EL87
KM4DDJ       EM76
KM4PJJ       EM72
KN1SIX       EM90
KN4CRD       EM73
KN9TVE       EN52
KO4PU        EM67
KP2L         EM93
KP4KD        EL95
KP4PR        FK68
KP4SX        FK78
KR6EN        EM59
KS0CW        FM16
KS4OT        EM83
KW4HT        EM64
LA5SJA       KQ50
LU6UBM       FF84
LU8AFR       GF05
LU8DQS       GF05
LU8EKC       GF05
LV7QFH       EM95
LZ1OI        KN22
M0LVL        IO84
M0OIC        IO92
M6RUG        IO83
MI0KOA       IO74
MM0LGS       IO85
N0BAK        EN34
N1SER        EM90
N2BJ         EN61
N2PPI        FN30
N3HI         DM03
N3MK         FM27
N4HYK        EL87
N4LAG        EM85
N4MKA        EM84
N5AYB        EL17
N5BCA        EM12
N5BSA        EM12
N5SLY        EM13
N6GP         DM13
N6OJ         CM88
N6PM         DM13
N6QQ         DM03
N6UK         DM14
N7IP         DN26
N7IY         CN84
N7NT         DM43
N7ZO         CN85
N8EHW        EN81
N8HMG        EN91
N8JAF        EM89
N9MUF        EN51
N9NTC        EN53
NA6L         DM12
NE6I         DM12
NN3V         DM13
NQ6F         DM12
NS2X         EM76
NU4T         EM74
NV8B         EN63
NX8Y         EN80
NY4I         EL87
ON3BZ        JO20
ON3EA        JO21
ON3LMA       JO20
ON7ZV        JO20
OT1V         JO20
OX6EYS       LB60
OZ1FHU       JO55
PA2GP        JO33
PD0LH        JO22
PD9BG        JO21
PE2K         JO22
PR7MB        HI22
PY2LK        GG66
PY2RJ        GG66
PY2XIZ       GG66
PY2YZB       GG66
PY4OY        GG78
PY7BC        HI21
RW0SR        OO22
SM6THE       JO68
SV2WT        KN10
TI4DJ        EK70
UT7QF        KN77
VA3DAZ       EN82
VA3LU        EN58
VE7AHT       CN89
VE9GJ        FN77
W0OGH        DM52
W0TW         EN74
W0YF         EN11
W1PFZ        FN42
W2HRO        FN20
W2PKY        EL88
W3BI         FN20
W3BS         EM55
W3KM         FN20
W3MR         FM18
W4EIS        EM13
W4GE         FM02
W4JSI        EM64
W4SFG        EM74
W4WYI        EM83
W5JPT        EM11
W5ZTX        EL29
W6AER        CM87
W6AUN        DM79
W6NWS        FM05
W6RYO        DM08
W7AUF        CN85
W7CT         DN41
W7DMC        CN85
W7SUR        DM49
W7VP         CN87
W8AKS        EM97
W8FHF        EN90
W8HC         EM98
W8MSC        EN82
W8OI         EM88
W8RES        EM79
W8WEJ        EM99
W8WKE        EL16
W9CTH        EM69
W9EO         DM04
W9WB         EN51
W9YSX        EM79
WA0LIF       EN35
WA1SXK       EM95
WA2HIP       FN54
WA4MIT       EM63
WA4VJK       EM66
WA6PHR       CM94
WA7DVD       DN40
WA9DU        EM69
WA9EIC       EN60
WA9JWL       EN70
WA9NNN       EN61
WB2JEP       DM33
WB2KSP       FN31
WB2REM       EL97
WB3FSR       FN20
WB5OZA       EM30
WB5TKI       EL29
WB5UDI       EM20
WB5XX        EM33
WB7UZO       CN78
WD4KAV       CN87
WD5IQR       EM10
WE5EE        EL49
WL7CG        BP61
WP4NSE       FK68
WS5W         EM13
WV5Y         EL29
WV8DOH       EM99
WV9L         EM59
WW7B         DM33
YV5BM        FK60


code:
!subroutine xcor(ss,ipk,nsteps,nsym,lag1,lag2,ccf,ccf0,lagpk,flip,fdot,nrobust)
subroutine xcor(ipk,nsteps,nsym,lag1,lag2,ccf,ccf0,lagpk,flip,fdot,nrobust)

! Computes ccf of a row of ss and the pseudo-random array pr.  Returns
! peak of the CCF and the lag at which peak occurs.  For JT65, the
! CCF peak may be either positive or negative, with negative implying
! the "OOO" message.

  use jt65_mod
  parameter (NHMAX=3413)           !Max length of power spectra
  parameter (NSMAX=552)            !Max number of quarter-symbol steps
  real ss(NSMAX,NHMAX)             !2d spectrum, stepped by half-symbols
  real a(NSMAX)
!  real ccf(-44:118)
  real ccf(lag1:lag2)
  data lagmin/0/                              !Silence g77 warning
!  save
  common/sync/ss

  df=12000.0/8192.
!  dtstep=0.5/df
  dtstep=0.25/df
  fac=dtstep/(60.0*df)
  do j=1,nsteps
     ii=nint((j-nsteps/2)*fdot*fac)+ipk
     if( (ii.ge.1) .and. (ii.le.NHMAX) ) then
       a(j)=ss(j,ii)
     endif
  enddo

  if(nrobust.eq.1) then
! use robust correlation estimator to mitigate AGC attack spikes at beginning
! this reduces the number of spurious candidates overall
    call pctile(a,nsteps,50,xmed)
    do j=1,nsteps
      if( a(j).ge.xmed ) then
        a(j)=1
      else
        a(j)=-1
      endif
    enddo
  endif

  ccfmax=0.
  ccfmin=0.
  do lag=lag1,lag2
     x=0.
     do i=1,nsym
        j=4*i-3+lag
        if(j.ge.1 .and. j.le.nsteps) x=x+a(j)*pr(i)
     enddo
     ccf(lag)=2*x                        !The 2 is for plotting scale
     if(ccf(lag).gt.ccfmax) then
        ccfmax=ccf(lag)
        lagpk=lag
     endif

     if(ccf(lag).lt.ccfmin) then
        ccfmin=ccf(lag)
        lagmin=lag
     endif
  enddo

  ccf0=ccfmax
  flip=1.0
  if(-ccfmin.gt.ccfmax) then
     do lag=lag1,lag2
        ccf(lag)=-ccf(lag)
     enddo
     lagpk=lagmin
     ccf0=-ccfmin
     flip=-1.0
  endif

  return
end subroutine xcor


code:
subroutine xcor4(s2,ipk,nsteps,nsym,lag1,lag2,ich,mode4,ccf,ccf0,   &
     lagpk,flip)

! Computes ccf of the 4_FSK spectral array s2 and the pseudo-random
! array pr2.  Returns peak of CCF and the lag at which peak occurs.
! The CCF peak may be either positive or negative, with negative
! implying the "OOO" message.

  parameter (NHMAX=1260)           !Max length of power spectra
  parameter (NSMAX=525)            !Max number of half-symbol steps
  real s2(NHMAX,NSMAX)             !2d spectrum, stepped by half-symbols
  real a(NSMAX)
  real ccf(-5:540)
  integer nch(7)
  integer npr2(207)
  real pr2(207)
  logical first
  data lagmin/0/                    !Silence compiler warning
  data first/.true./
  data npr2/                                                        &
       0,0,0,0,1,1,0,0,0,1,1,0,1,1,0,0,1,0,1,0,0,0,0,0,0,0,1,1,0,0, &
       0,0,0,0,0,0,0,0,0,0,1,0,1,1,0,1,1,0,1,0,1,1,1,1,1,0,1,0,0,0, &
       1,0,0,1,0,0,1,1,1,1,1,0,0,0,1,0,1,0,0,0,1,1,1,1,0,1,1,0,0,1, &
       0,0,0,1,1,0,1,0,1,0,1,0,1,0,1,1,1,1,1,0,1,0,1,0,1,1,0,1,0,1, &
       0,1,1,1,0,0,1,0,1,1,0,1,1,1,1,0,0,0,0,1,1,0,1,1,0,0,0,1,1,1, &
       0,1,1,1,0,1,1,1,0,0,1,0,0,0,1,1,0,1,1,0,0,1,0,0,0,1,1,1,1,1, &
       1,0,0,1,1,0,0,0,0,1,1,0,0,0,1,0,1,1,0,1,1,1,1,0,1,0,1/
  data nch/1,2,4,9,18,36,72/
  save

  if(first) then
     do i=1,207
        pr2(i)=2*npr2(i)-1
     enddo
     first=.false.
  endif

  ccfmax=0.
  ccfmin=0.
  nw=nch(ich)

  do j=1,nsteps
     n=2*mode4
     if(mode4.eq.1) then
        a(j)=max(s2(ipk+n,j),s2(ipk+3*n,j)) - max(s2(ipk  ,j),s2(ipk+2*n,j))
     else
        kz=max(1,nw/2)
        ss0=0.
        ss1=0.
        ss2=0.
        ss3=0.
        wsum=0.
        do k=-kz+1,kz-1
           w=float(kz-iabs(k))/nw
           wsum=wsum+w
           ss0=ss0 + w*s2(ipk    +k,j)
           ss1=ss1 + w*s2(ipk+  n+k,j)
           ss2=ss2 + w*s2(ipk+2*n+k,j)
           ss3=ss3 + w*s2(ipk+3*n+k,j)
        enddo
        a(j)=(max(ss1,ss3) - max(ss0,ss2))/sqrt(wsum)
     endif
  enddo

  do lag=lag1,lag2
     x=0.
     do i=1,nsym
        j=2*i-1+lag
        if(j.ge.1 .and. j.le.nsteps) x=x+a(j)*pr2(i)
     enddo
     ccf(lag)=2*x                        !The 2 is for plotting scale
     if(ccf(lag).gt.ccfmax) then
        ccfmax=ccf(lag)
        lagpk=lag
     endif

     if(ccf(lag).lt.ccfmin) then
        ccfmin=ccf(lag)
        lagmin=lag
     endif
  enddo

  ccf0=ccfmax
  flip=1.0
  if(-ccfmin.gt.ccfmax) then
     do lag=lag1,lag2
        ccf(lag)=-ccf(lag)
     enddo
     lagpk=lagmin
     ccf0=-ccfmin
     flip=-1.0
  endif

  return
end subroutine xcor4


code:
subroutine zplot9(s,freq,drift)

  real s(0:8,85)
  character*1 line(85),mark(0:6)
  data mark/' ',' ','.','-','+','X','$'/
  include 'jt9sync.f90'

  write(32,1000) freq,drift
1000 format('Freq:',f7.1,'   Drift:',f5.1,'  ',60('-'))
  do j=8,0,-1
     do i=1,85
        n=(s(j,i))
        if(n.lt.0) n=0
        if(n.gt.6) n=6
        line(i)=mark(n)
     enddo
     write(32,1010) j,line
1010 format(i1,1x,85a1)
  enddo
  do i=1,85
     line(i)=' '
     if(isync(i).eq.1) line(i)='@'
  enddo
  write(32,1015)
1015 format(87('-'))
  write(32,1020) line
1020 format(2x,85a1)
  call flush(32)

  return
end subroutine zplot9


code:
subroutine zplt(z,iplt,sync,dtx,nfreq,flip,sync2,nplot,emedelay,dttol,   &
  nfqso,ntol)

  real z(458,65)
  real zz(458,65)
  integer ij(2)
  character*4 lab

  call pctile(z,458*65,84,rms)
  fac=0.05/rms
  z=fac*z
  dtq=0.114286
  df=11025.0/(2.0*2520.0)

  ia=nint((nfqso-ntol)/df) - 273
  if(ia.lt.1) ia=1
  ib=nint((nfqso+ntol)/df) - 273
  if(ib.gt.458) ib=458
  ja=(emedelay+0.8-dttol)/dtq
  if(ja.lt.1) ja=1
  jb=(emedelay+0.8+dttol)/dtq
  if(jb.gt.65) jb=65

  zz=0.
  zz(ia:ib,ja:jb)=z(ia:ib,ja:jb)

  zmin=minval(zz)
  zmax=maxval(zz)
  flip=1.0
  if(abs(zmin).gt.abs(zmax)) flip=-1.0

  ij=maxloc(zz)
  if(flip.lt.0.0) ij=minloc(zz)
  i0=ij(1)
  j0=ij(2)
  nfreq=nint((i0+273)*df)
  dtx=j0*dtq-0.8
!  write(69,3101) ia,ib,ja,jb,ij,dtx,nfreq
!3101 format(6i5,f8.2,i6)

  ia=max(1,i0-72)
  ib=min(458,i0+72)
  sync=16.33*flip*(z(i0,j0) - 0.5*(z(ia,j0)+z(ib,j0)))
  sync2=20.0*flip*z(i0,j0)

  if(nplot.eq.0) go to 900

  zmax=max(abs(zmin),abs(zmax),1.0)
  zmin=-zmax

  do j=1,65
     write(61,1100) j*dtq-0.8,z(i0,j)
1100 format(2f10.3)
  enddo

  do i=1,458
     write(62,1100) (i+273)*df,flip*z(i,j0)
  enddo

  xx=1.5
  yy=7.5 - 3.0*iplt
  width=6.0
  height=2.0
  IP=458
  JP=65
  imax=IP
  jmax=JP

  if(iplt.eq.0) then
     call imopen("testjt4.ps")
     call imfont("Helvetica",16)
     call impalette("BlueRed.pal")
  endif

  call imr4mat_color(z,IP,JP,imax,jmax,zmin,zmax,xx,yy,   &
       width,height,1)
  call imstring("Frequency (Hz)",xx+0.5*width,yy-0.5,2,0)
  dy=0.1
  do i=1,9
     x=xx + 0.1*i*width
     call imyline(x,yy,dy)
     call imyline(x,yy+height,-dy)
  enddo
  do i=1,6
     nf=(i-1)*200 + 600
     write(lab,1020) nf
1020  format(i4)
     x=xx + (i-1)*0.2*width
     call imstring(lab,x,yy-0.25,2,0)
  enddo

  dx=0.1
  do i=0,6
     y=yy + height*(0.8+i)/(65.0*0.114286)
     call imxline(xx,y,dx)
     call imxline(xx+width,y,-dx)
  enddo

  do i=0,6,2
     y=yy + height*(0.8+i)/(65.0*0.114286)
     write(lab,1020) i
     call imstring(lab(4:4),xx-0.15,y-0.08,2,0)
  enddo

  y=yy + height*(3.8)/(65.0*0.114286)
  call imstring("DT", xx-0.5,y     ,2,0)
  call imstring("(s)",xx-0.5,y-0.25,2,0)

  if(iplt.eq.2) call imclose

900 return
end subroutine zplt


code:
#include "adif.h"

#include <QFile>
#include <QTextStream>
#include <QDateTime>
#include <QDebug>

/*
<CALL:4>W1XT<BAND:3>20m<FREQ:6>14.076<GRIDSQUARE:4>DM33<MODE:4>JT65<RST_RCVD:3>-21<RST_SENT:3>-14<QSO_DATE:8>20110422<TIME_ON:6>041712<TIME_OFF:6>042435<TX_PWR:1>4<COMMENT:34>1st JT65A QSO.   Him: mag loop 20W<STATION_CALLSIGN:6>VK3ACF<MY_GRIDSQUARE:6>qf22lb<eor>
<CALL:6>IK1SOW<BAND:3>20m<FREQ:6>14.076<GRIDSQUARE:4>JN35<MODE:4>JT65<RST_RCVD:3>-19<RST_SENT:3>-11<QSO_DATE:8>20110422<TIME_ON:6>052501<TIME_OFF:6>053359<TX_PWR:1>3<STATION_CALLSIGN:6>VK3ACF<MY_GRIDSQUARE:6>qf22lb<eor>
<CALL:6:S>W4ABC> ...
*/

void ADIF::init(QString const& filename)
{
    _filename = filename;
    _data.clear();
}

QString ADIF::extractField(QString const& record, QString const& fieldName) const
{
    int fieldNameIndex = record.indexOf ('<' + fieldName + ':', 0, Qt::CaseInsensitive);
    if (fieldNameIndex >=0)
    {
        int closingBracketIndex = record.indexOf('>',fieldNameIndex);
        int fieldLengthIndex = record.indexOf(':',fieldNameIndex);  // find the size delimiter
        int dataTypeIndex = -1;
        if (fieldLengthIndex >= 0)
        {
          dataTypeIndex = record.indexOf(':',fieldLengthIndex+1);  // check for a second : indicating there is a data type
          if (dataTypeIndex > closingBracketIndex)
            dataTypeIndex = -1; // second : was found but it was beyond the closing >
        }

        if ((closingBracketIndex > fieldNameIndex) && (fieldLengthIndex > fieldNameIndex) && (fieldLengthIndex< closingBracketIndex))
        {
            int fieldLengthCharCount = closingBracketIndex - fieldLengthIndex -1;
            if (dataTypeIndex >= 0)
              fieldLengthCharCount -= 2; // data type indicator is always a colon followed by a single character
            QString fieldLengthString = record.mid(fieldLengthIndex+1,fieldLengthCharCount);
            int fieldLength = fieldLengthString.toInt();
            if (fieldLength > 0)
            {
              QString field = record.mid(closingBracketIndex+1,fieldLength);
              return field;
            }
       }
    }
    return "";
}

void ADIF::load()
{
    _data.clear();
    QFile inputFile(_filename);
    if (inputFile.open(QIODevice::ReadOnly))
    {
      QTextStream in(&inputFile);
      QString buffer;
      bool pre_read {false};
      int end_position {-1};

      // skip optional header record
      do
        {
          buffer += in.readLine () + '\n';
          if (buffer.startsWith (QChar {'<'})) // denotes no header
            {
              pre_read = true;
            }
          else
            {
              end_position = buffer.indexOf ("<EOH>", 0, Qt::CaseInsensitive);
            }
        }
      while (!in.atEnd () && !pre_read && end_position < 0);
      if (!pre_read)            // found header
        {
          buffer.remove (0, end_position + 5);
        }
      while (buffer.size () || !in.atEnd ())
        {
          do
            {
              end_position = buffer.indexOf ("<EOR>", 0, Qt::CaseInsensitive);
              if (!in.atEnd () && end_position < 0)
                {
                  buffer += in.readLine () + '\n';
                }
            }
          while (!in.atEnd () && end_position < 0);
          int record_length {end_position >= 0 ? end_position + 5 : -1};
          auto record = buffer.left (record_length).trimmed ();
          auto next_record = buffer.indexOf (QChar {'<'}, record_length);
          buffer.remove (0, next_record >=0 ? next_record : buffer.size ());
          record = record.mid (record.indexOf (QChar {'<'}));
          add (extractField (record, "CALL")
               , extractField (record, "BAND")
               , extractField (record, "MODE")
               , extractField (record, "QSO_DATE"));
        }
        inputFile.close ();
    }
}

void ADIF::add(QString const& call, QString const& band, QString const& mode, QString const& date)
{
    QSO q;
    q.call = call;
    q.band = band;
    q.mode = mode;
    q.date = date;
    if (q.call.size ())
      {
        _data.insert(q.call,q);
        // qDebug() << "Added as worked:" << call << band << mode << date;
      }
}

// return true if in the log same band and mode (where JT65 == JT9)
bool ADIF::match(QString const& call, QString const& band, QString const& mode) const
{
    QList<QSO> qsos = _data.values(call);
    if (qsos.size()>0)
    {
        QSO q;
        foreach(q,qsos)
        {
            if (     (band.compare(q.band,Qt::CaseInsensitive) == 0)
                  || (band=="")
                  || (q.band==""))
            {
                if (
                     (
                       ((mode.compare("JT65",Qt::CaseInsensitive)==0) ||
                        (mode.compare("JT9",Qt::CaseInsensitive)==0)  ||
                        (mode.compare("FT8",Qt::CaseInsensitive)==0))
                       &&
                       ((q.mode.compare("JT65",Qt::CaseInsensitive)==0) ||
                        (q.mode.compare("JT9",Qt::CaseInsensitive)==0)  ||
                        (q.mode.compare("FT8",Qt::CaseInsensitive)==0))
                     )
                        || (mode.compare(q.mode,Qt::CaseInsensitive)==0)
                        || (mode=="")
                        || (q.mode=="")
                    )
                return true;
            }
        }
    }
    return false;
}

QList<QString> ADIF::getCallList() const
{
    QList<QString> p;
    QMultiHash<QString,QSO>::const_iterator i = _data.constBegin();
     while (i != _data.constEnd())
     {
         p << i.key();
         ++i;
     }
    return p;
}

int ADIF::getCount() const
{
    return _data.size();
}

QByteArray ADIF::QSOToADIF(QString const& hisCall, QString const& hisGrid, QString const& mode
                           , QString const& rptSent, QString const& rptRcvd, QDateTime const& dateTimeOn
                           , QDateTime const& dateTimeOff, QString const& band, QString const& comments
                           , QString const& name, QString const& strDialFreq, QString const& m_myCall
                           , QString const& m_myGrid, QString const& m_txPower, QString const& operator_call)
{
  QString t;
  t = "<call:" + QString::number(hisCall.length()) + ">" + hisCall;
  t += " <gridsquare:" + QString::number(hisGrid.length()) + ">" + hisGrid;
  t += " <mode:" + QString::number(mode.length()) + ">" + mode;
  t += " <rst_sent:" + QString::number(rptSent.length()) + ">" + rptSent;
  t += " <rst_rcvd:" + QString::number(rptRcvd.length()) + ">" + rptRcvd;
  t += " <qso_date:8>" + dateTimeOn.date().toString("yyyyMMdd");
  t += " <time_on:6>" + dateTimeOn.time().toString("hhmmss");
  t += " <qso_date_off:8>" + dateTimeOff.date().toString("yyyyMMdd");
  t += " <time_off:6>" + dateTimeOff.time().toString("hhmmss");
  t += " <band:" + QString::number(band.length()) + ">" + band;
  t += " <freq:" + QString::number(strDialFreq.length()) + ">" + strDialFreq;
  t += " <station_callsign:" + QString::number(m_myCall.length()) + ">" +
      m_myCall;
  t += " <my_gridsquare:" + QString::number(m_myGrid.length()) + ">" +
      m_myGrid;
  if (m_txPower != "")
    t += " <tx_pwr:" + QString::number(m_txPower.length()) +
        ">" + m_txPower;
  if (comments != "")
    t += " <comment:" + QString::number(comments.length()) +
        ">" + comments;
  if (name != "")
    t += " <name:" + QString::number(name.length()) +
        ">" + name;
  if (operator_call!="")
      t+=" <operator:" + QString::number(operator_call.length()) +
              ">" + operator_call;
  return t.toLatin1 ();
}

// open ADIF file and append the QSO details. Return true on success
bool ADIF::addQSOToFile(QByteArray const& ADIF_record)
{
    QFile f2(_filename);
    if (!f2.open(QIODevice::Text | QIODevice::Append))
        return false;
    else
    {
        QTextStream out(&f2);
        if (f2.size()==0)
            out << "WSJT-X ADIF Export<eoh>" << endl;  // new file

        out << ADIF_record << " <eor>" << endl;
        f2.close();
    }
    return true;
}


code:
/*
 * Reads an ADIF log file into memory
 * Searches log for call, band and mode
 * VK3ACF July 2013
 */

#ifndef __ADIF_H
#define __ADIF_H

#if defined (QT5)
#include <QList>
#include <QString>
#include <QMultiHash>
#else
#include <QtGui>
#endif

class QDateTime;

class ADIF
{
	public:
	void init(QString const& filename);
	void load();
	void add(QString const& call, QString const& band, QString const& mode, QString const& date);
	bool match(QString const& call, QString const& band, QString const& mode) const;
	QList<QString> getCallList() const;
	int getCount() const;

        // open ADIF file and append the QSO details. Return true on success
	bool addQSOToFile(QByteArray const& ADIF_record);

  QByteArray QSOToADIF(QString const& hisCall, QString const& hisGrid, QString const& mode, QString const& rptSent
											 , QString const& rptRcvd, QDateTime const& dateTimeOn, QDateTime const& dateTimeOff
											 , QString const& band, QString const& comments, QString const& name
											 , QString const& strDialFreq, QString const& m_myCall, QString const& m_myGrid
											 , QString const& m_txPower, QString const& operator_call);

private:
		struct QSO
		{
		  QString call,band,mode,date;
		};

		QMultiHash<QString, QSO> _data;
		QString _filename;

		QString extractField(QString const& line, QString const& fieldName) const;
};

#endif


code:
#include "countriesworked.h"

void CountriesWorked::init(const QStringList countryNames)
{
    _data.clear();
    foreach(QString name,countryNames)
      _data.insert(name,false);
}

void CountriesWorked::setAsWorked(const QString countryName)
{
    if (_data.contains(countryName))
      _data.insert(countryName,true);
}

bool CountriesWorked::getHasWorked(const QString countryName) const
{
    if (_data.contains(countryName))
      return _data.value(countryName);

    return false;
}

int CountriesWorked::getWorkedCount() const
{
    int count = 0;
	foreach (bool value,_data)
		if (value)
			count += 1;
    return count;
}

int CountriesWorked::getSize() const
{
    return _data.count();
}


code:
/*
 * maintains a list of country names that have been worked
 * VK3ACF July 2013
 */

#ifndef __COUNTRIESWORKDED_H
#define __COUNTRIESWORKDED_H

#include <QList>
#include <QString>
#include <QStringList>
#include <QHash>

class CountriesWorked
{
 public:
	void init(const QStringList countryNames);
	void setAsWorked(const QString countryName);
	bool getHasWorked(const QString countryName) const;
	int getWorkedCount() const;
	int getSize() const;

 private:
	QHash<QString, bool> _data;
};

#endif


code:
/*
#Sov Mil Order of Malta:   15:  28:  EU:   41.90:   -12.43:    -1.0:  1A:
    #1A;
#Spratly Islands:          26:  50:  AS:    9.88:  -114.23:    -8.0:  1S:
    #1S,9M0,BV9S;
#Monaco:                   14:  27:  EU:   43.73:    -7.40:    -1.0:  3A:
    #3A;
#Heard Island:             39:  68:  AF:  -53.08:   -73.50:    -5.0:  VK0H:
    #=VK0IR;
#Macquarie Island:         30:  60:  OC:  -54.60:  -158.88:   -10.0:  VK0M:
    #=VK0KEV;
#Cocos-Keeling:            29:  54:  OC:  -12.15:   -96.82:    -6.5:  VK9C:
    #AX9C,AX9Y,VH9C,VH9Y,VI9C,VI9Y,VJ9C,VJ9Y,VK9C,VK9Y,VL9C,VL9Y,VM9C,VM9Y,
    #VN9C,VN9Y,VZ9C,VZ9Y,=VK9AA;
*/

#include "countrydat.h"
#include <QFile>
#include <QTextStream>
#include <QDebug>
#include "Radio.hpp"

void CountryDat::init(const QString filename)
{
    _filename = filename;
    _data.clear();
}

QString CountryDat::_extractName(const QString line) const
{
    int s1 = line.indexOf(':');
    if (s1>=0)
    {
        QString name = line.mid(0,s1);
        return name;
    }
    return "";
}

void CountryDat::_removeBrackets(QString &line, const QString a, const QString b) const
{
    int s1 = line.indexOf(a);
    while (s1 >= 0)
    {
      int s2 = line.indexOf(b);
      line = line.mid(0,s1) + line.mid(s2+1,-1);
      s1 = line.indexOf(a);
    }
}

QStringList CountryDat::_extractPrefix(QString &line, bool &more) const
{
    line = line.remove(" \n");
    line = line.replace(" ","");

    _removeBrackets(line,"(",")");
    _removeBrackets(line,"[","]");
    _removeBrackets(line,"<",">");
    _removeBrackets(line,"~","~");

    int s1 = line.indexOf(';');
    more = true;
    if (s1 >= 0)
    {
      line = line.mid(0,s1);
      more = false;
    }

    QStringList r = line.split(',');

    return r;
}

void CountryDat::load()
{
    _data.clear();
    _countryNames.clear(); //used by countriesWorked

    QFile inputFile(_filename);
    if (inputFile.open(QIODevice::ReadOnly))
    {
       QTextStream in(&inputFile);
       while ( !in.atEnd() )
       {
          QString line1 = in.readLine();
          if ( !in.atEnd() )
          {
            QString line2 = in.readLine();

            QString name = _extractName(line1);
            if (name.length()>0)
            {
              QString continent=line1.mid(36,2);
              QString principalPrefix=line1.mid(69,4);
              int i1=principalPrefix.indexOf(":");
              if(i1>0) principalPrefix=principalPrefix.mid(0,i1);
              name += "; " + principalPrefix + "; " + continent;
                _countryNames << name;
                bool more = true;
                QStringList prefixs;
                while (more)
                {
                    QStringList p = _extractPrefix(line2,more);
                    prefixs += p;
                    if (more)
                        line2 = in.readLine();
                }

                QString p;
                foreach(p,prefixs)
                {
                    if (p.length() > 0)
                        _data.insert(p,name);
                }
            }
          }
       }
    inputFile.close();
    }
}

// return country name else ""
QString CountryDat::find(QString call) const
{
  call = call.toUpper ();

  // check for exact match first
  if (_data.contains ("=" + call))
    {
      return fixup (_data.value ("=" + call), call);
    }

  auto prefix = Radio::effective_prefix (call);
  auto match_candidate = prefix;
  while (match_candidate.size () >= 1)
    {
      if (_data.contains (match_candidate))
        {
          return fixup (_data.value (match_candidate), prefix);
        }
      match_candidate = match_candidate.left (match_candidate.size () - 1);
    }
  return QString {};
}

QString CountryDat::fixup (QString country, QString const& call) const
{
  //
  // deal with special rules that cty.dat does not cope with
  //

  // KG4 2x1 and 2x3 calls that map to Gitmo are mainland US not Gitmo
  if (call.startsWith ("KG4") && call.size () != 5 && call.size () != 3)
    {
      country.replace ("Guantanamo Bay; KG4; NA", "United States; K; NA");
    }
  return country;
}


code:
/*
 * Reads cty.dat file
 * Establishes a map between prefixes and their country names
 * VK3ACF July 2013
 */

#ifndef __COUNTRYDAT_H
#define __COUNTRYDAT_H

#include <QString>
#include <QStringList>
#include <QHash>

class CountryDat
{
public:
  void init(const QString filename);
  void load();
  QString find(QString prefix) const; // return country name or ""
  QStringList  getCountryNames() const { return _countryNames; };

private:
  QString _extractName(const QString line) const;
  void _removeBrackets(QString &line, const QString a, const QString b) const;
  QStringList _extractPrefix(QString &line, bool &more) const;
  QString fixup (QString country, QString const& call) const;

  QString _filename;
  QStringList _countryNames;
  QHash<QString, QString> _data;
};

#endif


code:
#include "logbook.h"
#include <QDebug>
#include <QFontMetrics>
#include <QStandardPaths>
#include <QDir>

namespace
{
  auto logFileName = "js8call_log.adi";
  auto countryFileName = "cty.dat";
}

void LogBook::init()
{
  QDir dataPath {QStandardPaths::writableLocation (QStandardPaths::DataLocation)};
  QString countryDataFilename;
  if (dataPath.exists (countryFileName))
    {
      // User override
      countryDataFilename = dataPath.absoluteFilePath (countryFileName);
    }
  else
    {
      countryDataFilename = QString {":/"} + countryFileName;
    }

  _countries.init(countryDataFilename);
  _countries.load();

  _worked.init(_countries.getCountryNames());

  _log.init(dataPath.absoluteFilePath (logFileName));
  _log.load();

  _setAlreadyWorkedFromLog();

  /*
    int QSOcount = _log.getCount();
    int count = _worked.getWorkedCount();
    qDebug() << QSOcount << "QSOs and" << count << "countries worked in file" << logFilename;
  */

  //    QString call = "ok1ct";
  //    QString countryName;
  //    bool callWorkedBefore,countryWorkedBefore;
  //    match(/*in*/call, /*out*/ countryName,callWorkedBefore,countryWorkedBefore);
  //    qDebug() << countryName;

}

void LogBook::_setAlreadyWorkedFromLog()
{
  QList<QString> calls = _log.getCallList();
  QString c;
  foreach(c,calls)
    {
      QString countryName = _countries.find(c);
      if (countryName.length() > 0)
        {
          _worked.setAsWorked(countryName);
          //qDebug() << countryName << " worked " << c;
        }
    }
}

void LogBook::match(/*in*/const QString call,
                    /*out*/ QString &countryName,
                    bool &callWorkedBefore,
                    bool &countryWorkedBefore) const
{
  if (call.length() > 0)
    {
      QString currentMode = "JT9"; // JT65 == JT9 in ADIF::match()
      QString currentBand = "";  // match any band
      callWorkedBefore = _log.match(call,currentBand,currentMode);
      countryName = _countries.find(call);
//      qDebug() << "B" << countryName;

      if (countryName.length() > 0)  //  country was found
        countryWorkedBefore = _worked.getHasWorked(countryName);
      else
        {
          countryName = "where?"; //error: prefix not found
          countryWorkedBefore = false;
        }
    }
  //qDebug() << "Logbook:" << call << ":" << countryName << "Cty B4:" << countryWorkedBefore << "call B4:" << callWorkedBefore;
}

void LogBook::addAsWorked(const QString call, const QString band, const QString mode, const QString date)
{
  //qDebug() << "adding " << call << " as worked";
  _log.add(call,band,mode,date);
  QString countryName = _countries.find(call);
  if (countryName.length() > 0)
    _worked.setAsWorked(countryName);
}


code:
/*
 * From an ADIF file and cty.dat, get a call's DXCC entity and its worked before status
 * VK3ACF July 2013
 */

#ifndef LOGBOOK_H
#define LOGBOOK_H

#include <QString>
#include <QFont>

#include "countrydat.h"
#include "countriesworked.h"
#include "adif.h"

class QDir;

class LogBook
{
public:
    void init();
    void match(/*in*/ const QString call,
              /*out*/ QString &countryName,
                      bool &callWorkedBefore,
                      bool &countryWorkedBefore) const;
    void addAsWorked(const QString call, const QString band, const QString mode, const QString date);

private:
   CountryDat _countries;
   CountriesWorked _worked;
   ADIF _log;

   void _setAlreadyWorkedFromLog();

};

#endif // LOGBOOK_H


code:
#include "logqso.h"

#include <QString>
#include <QSettings>
#include <QStandardPaths>
#include <QDir>
#include <QDebug>
#include <QUdpSocket>

#include "logbook/adif.h"
#include "MessageBox.hpp"
#include "Configuration.hpp"
#include "Bands.hpp"
#include "MaidenheadLocatorValidator.hpp"

#include "ui_logqso.h"
#include "moc_logqso.cpp"

LogQSO::LogQSO(QString const& programTitle, QSettings * settings
               , Configuration const * config, QWidget *parent)
  : QDialog {parent, Qt::WindowStaysOnTopHint | Qt::WindowTitleHint | Qt::WindowSystemMenuHint}
  , ui(new Ui::LogQSO)
  , m_settings (settings)
  , m_config {config}
{
  ui->setupUi(this);
  setWindowTitle(programTitle + " - Log QSO");
  loadSettings ();
  ui->grid->setValidator (new MaidenheadLocatorValidator {this});
}

LogQSO::~LogQSO ()
{
}

void LogQSO::loadSettings ()
{
  m_settings->beginGroup ("LogQSO");
  restoreGeometry (m_settings->value ("geometry", saveGeometry ()).toByteArray ());
  ui->cbTxPower->setChecked (m_settings->value ("SaveTxPower", false).toBool ());
  ui->cbComments->setChecked (m_settings->value ("SaveComments", false).toBool ());
  m_txPower = m_settings->value ("TxPower", "").toString ();
  m_comments = m_settings->value ("LogComments", "").toString();
  m_settings->endGroup ();
}

void LogQSO::storeSettings () const
{
  m_settings->beginGroup ("LogQSO");
  m_settings->setValue ("geometry", saveGeometry ());
  m_settings->setValue ("SaveTxPower", ui->cbTxPower->isChecked ());
  m_settings->setValue ("SaveComments", ui->cbComments->isChecked ());
  m_settings->setValue ("TxPower", m_txPower);
  m_settings->setValue ("LogComments", m_comments);
  m_settings->endGroup ();
}

void LogQSO::initLogQSO(QString const& hisCall, QString const& hisGrid, QString mode,
                        QString const& rptSent, QString const& rptRcvd,
                        QDateTime const& dateTimeOn, QDateTime const& dateTimeOff,
                        Radio::Frequency dialFreq, QString const& myCall, QString const& myGrid,
                        bool toRTTY, bool dBtoComments, bool bFox, QString const& opCall)
{
  if(!isHidden()) return;
  ui->call->setText(hisCall);
  ui->grid->setText(hisGrid);
  ui->name->setText("");
  ui->txPower->setText("");
  ui->comments->setText("");
  if (ui->cbTxPower->isChecked ()) ui->txPower->setText(m_txPower);
  if (ui->cbComments->isChecked ()) ui->comments->setText(m_comments);
  if(dBtoComments) {
    QString t=mode;
    if(rptSent!="") t+="  Sent: " + rptSent;
    if(rptRcvd!="") t+="  Rcvd: " + rptRcvd;
    ui->comments->setText(t);
  }
  if(toRTTY) mode="DATA";
  ui->mode->setText(mode);
  ui->sent->setText(rptSent);
  ui->rcvd->setText(rptRcvd);
  ui->start_date_time->setDateTime (dateTimeOn);
  ui->end_date_time->setDateTime (dateTimeOff);
  m_dialFreq=dialFreq;
  m_myCall=myCall;
  m_myGrid=myGrid;
  ui->band->setText (m_config->bands ()->find (dialFreq));
  ui->loggedOperator->setText(opCall);
  if(bFox) {
    accept();
  } else {
    show ();
  }
}

void LogQSO::accept()
{
  QString hisCall,hisGrid,mode,rptSent,rptRcvd,dateOn,dateOff,timeOn,timeOff,band,operator_call;
  QString comments,name;

  hisCall=ui->call->text();
  hisGrid=ui->grid->text();
  mode=ui->mode->text();
  rptSent=ui->sent->text();
  rptRcvd=ui->rcvd->text();
  m_dateTimeOn = ui->start_date_time->dateTime ();
  m_dateTimeOff = ui->end_date_time->dateTime ();
  band=ui->band->text();
  name=ui->name->text();
  m_txPower=ui->txPower->text();
  comments=ui->comments->text();
  m_comments=comments;
  QString strDialFreq(QString::number(m_dialFreq / 1.e6,'f',6));
  operator_call = ui->loggedOperator->text();
  //Log this QSO to ADIF file "js8call_log.adi"
  QString filename = "js8call_log.adi";  // TODO allow user to set
  ADIF adifile;
  auto adifilePath = QDir {QStandardPaths::writableLocation (QStandardPaths::DataLocation)}.absoluteFilePath ("js8call_log.adi");
  adifile.init(adifilePath);

  QByteArray ADIF {adifile.QSOToADIF (hisCall, hisGrid, mode, rptSent, rptRcvd, m_dateTimeOn, m_dateTimeOff, band
                                      , comments, name, strDialFreq, m_myCall, m_myGrid, m_txPower, operator_call)};
  if (!adifile.addQSOToFile (ADIF))
  {
    MessageBox::warning_message (this, tr ("Log file error"),
                                 tr ("Cannot open \"%1\"").arg (adifilePath));
  }

  // Log to N1MM Logger
  if (m_config->broadcast_to_n1mm() && m_config->valid_n1mm_info())  {
    const QHostAddress n1mmhost = QHostAddress(m_config->n1mm_server_name());
    QUdpSocket _sock;
    auto rzult = _sock.writeDatagram (ADIF + " <eor>", n1mmhost, quint16(m_config->n1mm_server_port()));
    if (rzult == -1) {
      MessageBox::warning_message (this, tr ("Error sending log to N1MM"),
                                   tr ("Write returned \"%1\"").arg (rzult));
    }
  }

//Log this QSO to file "js8call.log"
  static QFile f {QDir {QStandardPaths::writableLocation (QStandardPaths::DataLocation)}.absoluteFilePath ("js8call.log")};
  if(!f.open(QIODevice::Text | QIODevice::Append)) {
    MessageBox::warning_message (this, tr ("Log file error"),
                                 tr ("Cannot open \"%1\" for append").arg (f.fileName ()),
                                 tr ("Error: %1").arg (f.errorString ()));
  } else {
    QString logEntry=m_dateTimeOn.date().toString("yyyy-MM-dd,") +
      m_dateTimeOn.time().toString("hh:mm:ss,") +
      m_dateTimeOff.date().toString("yyyy-MM-dd,") +
      m_dateTimeOff.time().toString("hh:mm:ss,") + hisCall + "," +
      hisGrid + "," + strDialFreq + "," + mode +
      "," + rptSent + "," + rptRcvd + "," + m_txPower +
      "," + comments + "," + name;
    QTextStream out(&f);
    out << logEntry << endl;
    f.close();
  }

//Clean up and finish logging
  Q_EMIT acceptQSO (m_dateTimeOff, hisCall, hisGrid, m_dialFreq, mode, rptSent, rptRcvd, m_txPower, comments, name,m_dateTimeOn, operator_call, m_myCall, m_myGrid, ADIF);
  QDialog::accept();
}

// closeEvent is only called from the system menu close widget for a
// modeless dialog so we use the hideEvent override to store the
// window settings
void LogQSO::hideEvent (QHideEvent * e)
{
  storeSettings ();
  QDialog::hideEvent (e);
}


code:
// -*- Mode: C++ -*-
#ifndef LogQSO_H
#define LogQSO_H

#ifdef QT5
#include <QtWidgets>
#else
#include <QtGui>
#endif

#include <QString>
#include <QScopedPointer>
#include <QDateTime>

#include "Radio.hpp"

namespace Ui {
  class LogQSO;
}

class QSettings;
class Configuration;
class QByteArray;

class LogQSO : public QDialog
{
  Q_OBJECT

public:
  explicit LogQSO(QString const& programTitle, QSettings *, Configuration const *, QWidget *parent = 0);
  ~LogQSO();
  void initLogQSO(QString const& hisCall, QString const& hisGrid, QString mode,
                  QString const& rptSent, QString const& rptRcvd, QDateTime const& dateTimeOn,
                  QDateTime const& dateTimeOff,
                  Radio::Frequency dialFreq, QString const& myCall, QString const& myGrid,
                  bool toRTTY, bool dBtoComments, bool bFox, QString const& opCall);

public slots:
  void accept();

signals:
  void acceptQSO (QDateTime const& QSO_date_off, QString const& call, QString const& grid
                  , Radio::Frequency dial_freq, QString const& mode
                  , QString const& rpt_sent, QString const& rpt_received
                  , QString const& tx_power, QString const& comments
                  , QString const& name, QDateTime const& QSO_date_on,  QString const& operator_call
                  , QString const& my_call, QString const& my_grid, QByteArray const& ADIF);

protected:
  void hideEvent (QHideEvent *);

private:
  void loadSettings ();
  void storeSettings () const;

  QScopedPointer<Ui::LogQSO> ui;
  QSettings * m_settings;
  Configuration const * m_config;
  QString m_txPower;
  QString m_comments;
  Radio::Frequency m_dialFreq;
  QString m_myCall;
  QString m_myGrid;
  QDateTime m_dateTimeOn;
  QDateTime m_dateTimeOff;
};

#endif // LogQSO_H


code:
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
 <class>LogQSO</class>
 <widget class="QDialog" name="LogQSO">
  <property name="geometry">
   <rect>
    <x>0</x>
    <y>0</y>
    <width>600</width>
    <height>285</height>
   </rect>
  </property>
  <property name="sizePolicy">
   <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
    <horstretch>0</horstretch>
    <verstretch>0</verstretch>
   </sizepolicy>
  </property>
  <property name="minimumSize">
   <size>
    <width>600</width>
    <height>0</height>
   </size>
  </property>
  <layout class="QVBoxLayout" name="verticalLayout_11">
   <item>
    <widget class="QLabel" name="label">
     <property name="font">

       <pointsize>10</pointsize>

     </property>
     <property name="text">
      <string>Click OK to confirm the following QSO:</string>
     </property>
    </widget>
   </item>
   <item>
    <layout class="QHBoxLayout" name="horizontalLayout_2">
     <item>
      <layout class="QVBoxLayout" name="verticalLayout">
       <item>
        <widget class="QLabel" name="lab1">
         <property name="font">

           <pointsize>10</pointsize>

         </property>
         <property name="text">
          <string>Call</string>
         </property>
         <property name="alignment">
          <set>Qt::AlignCenter</set>
         </property>
        </widget>
       </item>
       <item>
        <widget class="QLineEdit" name="call"/>
       </item>
      </layout>
     </item>
     <item>
      <layout class="QVBoxLayout" name="verticalLayout_2">
       <item>
        <widget class="QLabel" name="lab2">
         <property name="font">

           <pointsize>10</pointsize>

         </property>
         <property name="text">
          <string>Start</string>
         </property>
         <property name="alignment">
          <set>Qt::AlignCenter</set>
         </property>
        </widget>
       </item>
       <item>
        <widget class="QDateTimeEdit" name="start_date_time">
         <property name="sizePolicy">
          <sizepolicy hsizetype="Maximum" vsizetype="Fixed">
           <horstretch>0</horstretch>
           <verstretch>0</verstretch>
          </sizepolicy>
         </property>
         <property name="displayFormat">
          <string>dd/MM/yyyy HH:mm:ss</string>
         </property>
        </widget>
       </item>
      </layout>
     </item>
     <item>
      <layout class="QVBoxLayout" name="verticalLayout_3">
       <item>
        <widget class="QLabel" name="lab2_3">
         <property name="font">

           <pointsize>10</pointsize>

         </property>
         <property name="text">
          <string>End</string>
         </property>
         <property name="alignment">
          <set>Qt::AlignCenter</set>
         </property>
        </widget>
       </item>
       <item>
        <widget class="QDateTimeEdit" name="end_date_time">
         <property name="sizePolicy">
          <sizepolicy hsizetype="Maximum" vsizetype="Fixed">
           <horstretch>0</horstretch>
           <verstretch>0</verstretch>
          </sizepolicy>
         </property>
         <property name="displayFormat">
          <string>dd/MM/yyyy HH:mm:ss</string>
         </property>
        </widget>
       </item>
      </layout>
     </item>
    </layout>
   </item>
   <item>
    <layout class="QHBoxLayout" name="horizontalLayout_4">
     <item>
      <layout class="QVBoxLayout" name="verticalLayout_4">
       <item>
        <widget class="QLabel" name="lab4">
         <property name="font">

           <pointsize>10</pointsize>

         </property>
         <property name="text">
          <string>Mode</string>
         </property>
         <property name="alignment">
          <set>Qt::AlignCenter</set>
         </property>
        </widget>
       </item>
       <item>
        <widget class="QLineEdit" name="mode">
         <property name="maximumSize">
          <size>
           <width>70</width>
           <height>16777215</height>
          </size>
         </property>
        </widget>
       </item>
      </layout>
     </item>
     <item>
      <layout class="QVBoxLayout" name="verticalLayout_6">
       <item>
        <widget class="QLabel" name="lab5">
         <property name="font">

           <pointsize>10</pointsize>

         </property>
         <property name="text">
          <string>Band</string>
         </property>
         <property name="alignment">
          <set>Qt::AlignCenter</set>
         </property>
        </widget>
       </item>
       <item>
        <widget class="QLineEdit" name="band">
         <property name="maximumSize">
          <size>
           <width>50</width>
           <height>16777215</height>
          </size>
         </property>
        </widget>
       </item>
      </layout>
     </item>
     <item>
      <layout class="QVBoxLayout" name="verticalLayout_7">
       <item>
        <widget class="QLabel" name="lab6">
         <property name="font">

           <pointsize>10</pointsize>

         </property>
         <property name="text">
          <string>Rpt Sent</string>
         </property>
         <property name="alignment">
          <set>Qt::AlignCenter</set>
         </property>
        </widget>
       </item>
       <item>
        <widget class="QLineEdit" name="sent">
         <property name="maximumSize">
          <size>
           <width>50</width>
           <height>16777215</height>
          </size>
         </property>
        </widget>
       </item>
      </layout>
     </item>
     <item>
      <layout class="QVBoxLayout" name="verticalLayout_8">
       <item>
        <widget class="QLabel" name="lab7">
         <property name="font">

           <pointsize>10</pointsize>

         </property>
         <property name="text">
          <string>Rpt Rcvd</string>
         </property>
         <property name="alignment">
          <set>Qt::AlignCenter</set>
         </property>
        </widget>
       </item>
       <item>
        <widget class="QLineEdit" name="rcvd">
         <property name="maximumSize">
          <size>
           <width>50</width>
           <height>16777215</height>
          </size>
         </property>
        </widget>
       </item>
      </layout>
     </item>
     <item>
      <layout class="QVBoxLayout" name="verticalLayout_9">
       <item>
        <widget class="QLabel" name="lab8">
         <property name="font">

           <pointsize>10</pointsize>

         </property>
         <property name="text">
          <string>Grid</string>
         </property>
         <property name="alignment">
          <set>Qt::AlignCenter</set>
         </property>
        </widget>
       </item>
       <item>
        <widget class="QLineEdit" name="grid">
         <property name="maximumSize">
          <size>
           <width>70</width>
           <height>16777215</height>
          </size>
         </property>
        </widget>
       </item>
      </layout>
     </item>
     <item>
      <layout class="QVBoxLayout" name="verticalLayout_10">
       <item>
        <widget class="QLabel" name="lab9">
         <property name="enabled">
          <bool>true</bool>
         </property>
         <property name="font">

           <pointsize>10</pointsize>

         </property>
         <property name="text">
          <string>Name</string>
         </property>
         <property name="alignment">
          <set>Qt::AlignCenter</set>
         </property>
        </widget>
       </item>
       <item>
        <widget class="QLineEdit" name="name">
         <property name="enabled">
          <bool>true</bool>
         </property>
        </widget>
       </item>
      </layout>
     </item>
    </layout>
   </item>
   <item>
    <layout class="QHBoxLayout" name="horizontalLayout_6">
     <item>
      <widget class="QLabel" name="label_2">
       <property name="text">
        <string>Tx power</string>
       </property>
      </widget>
     </item>
     <item>
      <widget class="QLineEdit" name="txPower"/>
     </item>
     <item>
      <widget class="QCheckBox" name="cbTxPower">
       <property name="text">
        <string>Retain</string>
       </property>
      </widget>
     </item>
    </layout>
   </item>
   <item>
    <layout class="QHBoxLayout" name="horizontalLayout">
     <item>
      <widget class="QLabel" name="lab10">
       <property name="enabled">
        <bool>true</bool>
       </property>
       <property name="font">

         <pointsize>10</pointsize>

       </property>
       <property name="text">
        <string>Comments</string>
       </property>
      </widget>
     </item>
     <item>
      <widget class="QLineEdit" name="comments">
       <property name="enabled">
        <bool>true</bool>
       </property>
      </widget>
     </item>
     <item>
      <widget class="QCheckBox" name="cbComments">
       <property name="text">
        <string>Retain</string>
       </property>
      </widget>
     </item>
    </layout>
   </item>
   <item>
    <layout class="QHBoxLayout" name="horizontalLayout_5">
     <item>
      <widget class="QLabel" name="operatorLabel">
       <property name="font">

         <pointsize>10</pointsize>

       </property>
       <property name="text">
        <string>Operator</string>
       </property>
      </widget>
     </item>
     <item>
      <widget class="QLineEdit" name="loggedOperator">
       <property name="maximumSize">
        <size>
         <width>300</width>
         <height>16777215</height>
        </size>
       </property>
      </widget>
     </item>
     <item>
      <spacer name="horizontalSpacer">
       <property name="orientation">
        <enum>Qt::Horizontal</enum>
       </property>
       <property name="sizeHint" stdset="0">
        <size>
         <width>40</width>
         <height>20</height>
        </size>
       </property>
      </spacer>
     </item>
    </layout>
   </item>
   <item>
    <spacer name="verticalSpacer">
     <property name="orientation">
      <enum>Qt::Vertical</enum>
     </property>
     <property name="sizeHint" stdset="0">
      <size>
       <width>0</width>
       <height>0</height>
      </size>
     </property>
    </spacer>
   </item>
   <item>
    <widget class="QDialogButtonBox" name="buttonBox">
     <property name="orientation">
      <enum>Qt::Horizontal</enum>
     </property>
     <property name="standardButtons">
      <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
     </property>
    </widget>
   </item>
  </layout>
 </widget>
 <tabstops>
  <tabstop>call</tabstop>
  <tabstop>start_date_time</tabstop>
  <tabstop>end_date_time</tabstop>
  <tabstop>mode</tabstop>
  <tabstop>band</tabstop>
  <tabstop>sent</tabstop>
  <tabstop>rcvd</tabstop>
  <tabstop>grid</tabstop>
  <tabstop>name</tabstop>
  <tabstop>txPower</tabstop>
  <tabstop>cbTxPower</tabstop>
  <tabstop>comments</tabstop>
  <tabstop>cbComments</tabstop>
 </tabstops>
 <resources/>
 <connections>
  <connection>
   <sender>buttonBox</sender>
   <signal>accepted()</signal>
   <receiver>LogQSO</receiver>
   <slot>accept()</slot>
   <hints>
    <hint type="sourcelabel">
     <x>248</x>
     <y>254</y>
    </hint>
    <hint type="destinationlabel">
     <x>157</x>
     <y>274</y>
    </hint>
   </hints>
  </connection>
  <connection>
   <sender>buttonBox</sender>
   <signal>rejected()</signal>
   <receiver>LogQSO</receiver>
   <slot>reject()</slot>
   <hints>
    <hint type="sourcelabel">
     <x>316</x>
     <y>260</y>
    </hint>
    <hint type="destinationlabel">
     <x>286</x>
     <y>274</y>
    </hint>
   </hints>
  </connection>
 </connections>
</ui>


code:
#include <iostream>
#include <exception>
#include <stdexcept>
#include <string>

#include <locale.h>
#include <fftw3.h>

#include <QDateTime>
#include <QApplication>
#include <QRegularExpression>
#include <QObject>
#include <QSettings>
#include <QLibraryInfo>
#include <QSysInfo>
#include <QDir>
#include <QStandardPaths>
#include <QStringList>
#include <QLockFile>
#include <QStack>
#include <QSplashScreen>

#if QT_VERSION >= 0x050200
#include <QCommandLineParser>
#include <QCommandLineOption>
#endif

#include "revision_utils.hpp"
#include "MetaDataRegistry.hpp"
#include "SettingsGroup.hpp"
#include "TraceFile.hpp"
#include "MultiSettings.hpp"
#include "mainwindow.h"
#include "commons.h"
#include "lib/init_random_seed.h"
#include "Radio.hpp"
#include "FrequencyList.hpp"
#include "SplashScreen.hpp"
#include "MessageBox.hpp"       // last to avoid nasty MS macro definitions

#include "DriftingDateTime.h"

extern "C" {
  // Fortran procedures we need
  void four2a_(_Complex float *, int * nfft, int * ndim, int * isign, int * iform, int len);
}

namespace
{
  struct RNGSetup
  {
    RNGSetup ()
    {
      // one time seed of pseudo RNGs from current time
      auto seed = DriftingDateTime::currentMSecsSinceEpoch ();
      qsrand (seed);            // this is good for rand() as well
    }
  } seeding;

  class MessageTimestamper
  {
  public:
    MessageTimestamper ()
    {
      prior_handlers_.push (qInstallMessageHandler (message_handler));
    }
    ~MessageTimestamper ()
    {
      if (prior_handlers_.size ()) qInstallMessageHandler (prior_handlers_.pop ());
    }

  private:
    static void message_handler (QtMsgType type, QMessageLogContext const& context, QString const& msg)
    {
      QtMessageHandler handler {prior_handlers_.top ()};
      if (handler)
        {
          handler (type, context,
                   DriftingDateTime::currentDateTimeUtc ().toString ("yy-MM-ddTHH:mm:ss.zzzZ: ") + msg);
        }
    }
    static QStack<QtMessageHandler> prior_handlers_;
  };
  QStack<QtMessageHandler> MessageTimestamper::prior_handlers_;
}

int main(int argc, char *argv[])
{
  // Add timestamps to all debug messages
  MessageTimestamper message_timestamper;

  init_random_seed ();

  // make the Qt type magic happen
  Radio::register_types ();
  register_types ();

  // Multiple instances communicate with jt9 via this
  QSharedMemory mem_jt9;

  QApplication a(argc, argv);
  try
    {
      setlocale (LC_NUMERIC, "C"); // ensure number forms are in
                                   // consistent format, do this after
                                   // instantiating QApplication so
                                   // that GUI has correct l18n

      // Override programs executable basename as application name.
      //a.setApplicationName ("WSJT-X");
      a.setApplicationName("JS8Call");
      a.setApplicationVersion (version ());

#if QT_VERSION >= 0x050200
      QCommandLineParser parser;
      parser.setApplicationDescription ("\n" PROJECT_SUMMARY_DESCRIPTION);
      auto help_option = parser.addHelpOption ();
      auto version_option = parser.addVersionOption ();

      // support for multiple instances running from a single installation
      QCommandLineOption rig_option (QStringList {} << "r" << "rig-name"
                                     , a.translate ("main", "Where <rig-name> is for multi-instance support.")
                                     , a.translate ("main", "rig-name"));
      parser.addOption (rig_option);

      // support for start up configuration
      QCommandLineOption cfg_option (QStringList {} << "c" << "config"
                                     , a.translate ("main", "Where <configuration> is an existing one.")
                                     , a.translate ("main", "configuration"));
      parser.addOption (cfg_option);

      QCommandLineOption test_option (QStringList {} << "test-mode"
                                      , a.translate ("main", "Writable files in test location.  Use with caution, for testing only."));
      parser.addOption (test_option);

      if (!parser.parse (a.arguments ()))
        {
          MessageBox::critical_message (nullptr, a.translate ("main", "Command line error"), parser.errorText ());
          return -1;
        }
      else
        {
          if (parser.isSet (help_option))
            {
              MessageBox::information_message (nullptr, a.translate ("main", "Command line help"), parser.helpText ());
              return 0;
            }
          else if (parser.isSet (version_option))
            {
              MessageBox::information_message (nullptr, a.translate ("main", "Application version"), a.applicationVersion ());
              return 0;
            }
        }

      QStandardPaths::setTestModeEnabled (parser.isSet (test_option));

      // support for multiple instances running from a single installation
      bool multiple {false};
      if (parser.isSet (rig_option) || parser.isSet (test_option))
        {
          auto temp_name = parser.value (rig_option);
          if (!temp_name.isEmpty ())
            {
              if (temp_name.contains (QRegularExpression {R"([\\/,])"}))
                {
                  std::cerr << QObject::tr ("Invalid rig name - \\ & / not allowed").toLocal8Bit ().data () << std::endl;
                  parser.showHelp (-1);
                }

              a.setApplicationName (a.applicationName () + " - " + temp_name);
            }

          if (parser.isSet (test_option))
            {
              a.setApplicationName (a.applicationName () + " - test");
            }

          multiple = true;
        }

      // now we have the application name we can open the settings
      MultiSettings multi_settings {parser.value (cfg_option)};

      // find the temporary files path
      QDir temp_dir {QStandardPaths::writableLocation (QStandardPaths::TempLocation)};
      Q_ASSERT (temp_dir.exists ()); // sanity check

      // disallow multiple instances with same instance key
      QLockFile instance_lock {temp_dir.absoluteFilePath (a.applicationName () + ".lock")};
      instance_lock.setStaleLockTime (0);
      bool lock_ok {false};
      while (!(lock_ok = instance_lock.tryLock ()))
        {
          if (QLockFile::LockFailedError == instance_lock.error ())
            {
              auto button = MessageBox::query_message (nullptr
                                                       , a.translate ("main", "Another instance may be running")
                                                       , a.translate ("main", "try to remove stale lock file?")
                                                       , QString {}
                                                       , MessageBox::Yes | MessageBox::Retry | MessageBox::No
                                                       , MessageBox::Yes);
              switch (button)
                {
                case MessageBox::Yes:
                  instance_lock.removeStaleLockFile ();
                  break;

                case MessageBox::Retry:
                  break;

                default:
                  throw std::runtime_error {"Multiple instances must have unique rig names"};
                }
            }
        }
#endif

#if WSJT_QDEBUG_TO_FILE
      // Open a trace file
      TraceFile trace_file {temp_dir.absoluteFilePath (a.applicationName () + "_trace.log")};
      qDebug () << program_title (revision ()) + " - Program startup";
#endif

      // Create a unique writeable temporary directory in a suitable location
      bool temp_ok {false};
      QString unique_directory {QApplication::applicationName ()};
      do
        {
          if (!temp_dir.mkpath (unique_directory)
              || !temp_dir.cd (unique_directory))
            {
              MessageBox::critical_message (nullptr,
                                            a.translate ("main", "Failed to create a temporary directory"),
                                            a.translate ("main", "Path: \"%1\"").arg (temp_dir.absolutePath ()));
              throw std::runtime_error {"Failed to create a temporary directory"};
            }
          if (!temp_dir.isReadable () || !(temp_ok = QTemporaryFile {temp_dir.absoluteFilePath ("test")}.open ()))
            {
              auto button =  MessageBox::critical_message (nullptr,
                                                           a.translate ("main", "Failed to create a usable temporary directory"),
                                                           a.translate ("main", "Another application may be locking the directory"),
                                                           a.translate ("main", "Path: \"%1\"").arg (temp_dir.absolutePath ()),
                                                           MessageBox::Retry | MessageBox::Cancel);
              if (MessageBox::Cancel == button)
                {
                  throw std::runtime_error {"Failed to create a usable temporary directory"};
                }
              temp_dir.cdUp ();  // revert to parent as this one is no good
            }
        }
      while (!temp_ok);

      SplashScreen splash;
      {
        // change this key if you want to force a new splash screen
        // for a new version, the user will be able to re-disable it
        // if they wish
        QString splash_flag_name {"Splash_v1.7"};
        if (multi_settings.common_value (splash_flag_name, true).toBool ())
          {
            QObject::connect (&splash, &SplashScreen::disabled, [&, splash_flag_name] {
                multi_settings.set_common_value (splash_flag_name, false);
                splash.close ();
              });
            //splash.show ();
            a.processEvents ();
          }
      }

      int result;
      do
        {
#if WSJT_QDEBUG_TO_FILE
          // announce to trace file and dump settings
          qDebug () << "++++++++++++++++++++++++++++ Settings ++++++++++++++++++++++++++++";
          for (auto const& key: multi_settings.settings ()->allKeys ())
            {
              auto const& value = multi_settings.settings ()->value (key);
              if (value.canConvert<QVariantList> ())
                {
                  auto const sequence = value.value<QSequentialIterable> ();
                  qDebug ().nospace () << key << ": ";
                  for (auto const& item: sequence)
                    {
                      qDebug ().nospace () << '\t' << item;
                    }
                }
              else
                {
                  qDebug ().nospace () << key << ": " << value;
                }
            }
          qDebug () << "---------------------------- Settings ----------------------------";
#endif

          // Create and initialize shared memory segment
          // Multiple instances: use rig_name as shared memory key
          mem_jt9.setKey(a.applicationName ());

          if(!mem_jt9.attach()) {
            std::cerr << QString("memory attach error: %1").arg(mem_jt9.error()).toLocal8Bit ().data () << std::endl;

            if (!mem_jt9.create(sizeof(struct dec_data))) {
              splash.hide ();
              std::cerr << QString("memory create error: %1").arg(mem_jt9.error()).toLocal8Bit ().data () << std::endl;

              MessageBox::critical_message (nullptr, a.translate ("main", "Shared memory error"),
                                            a.translate ("main", "Unable to create shared memory segment"));
              throw std::runtime_error {"Shared memory error"};
            }
          }
          memset(mem_jt9.data(),0,sizeof(struct dec_data)); //Zero all decoding params in shared memory

          unsigned downSampleFactor;
          {
            SettingsGroup {multi_settings.settings (), "Tune"};

            // deal with Windows Vista and earlier input audio rate
            // converter problems
            downSampleFactor = multi_settings.settings ()->value ("Audio/DisableInputResampling",
#if defined (Q_OS_WIN)
                                                                  // default to true for
                                                                  // Windows Vista and older
                                                                  QSysInfo::WV_VISTA >= QSysInfo::WindowsVersion ? true : false
#else
                                                                  false
#endif
                                                                  ).toBool () ? 1u : 4u;
          }

          // run the application UI
          MainWindow w(temp_dir, multiple, &multi_settings, &mem_jt9, downSampleFactor, &splash);
          w.show();
          //splash.raise ();
          QObject::connect (&a, SIGNAL (lastWindowClosed()), &a, SLOT (quit()));
          result = a.exec();
        }
      while (!result && !multi_settings.exit ());

      // clean up lazily initialized resources
      {
        int nfft {-1};
        int ndim {1};
        int isign {1};
        int iform {1};
        // free FFT plan resources
        four2a_ (nullptr, &nfft, &ndim, &isign, &iform, 0);
      }
      fftwf_forget_wisdom ();
      fftwf_cleanup ();

      temp_dir.removeRecursively (); // clean up temp files
      return result;
    }
  catch (std::exception const& e)
    {
      MessageBox::critical_message (nullptr, a.translate ("main", "Fatal error"), e.what ());
      std::cerr << "Error: " << e.what () << '\n';
    }
  catch (...)
    {
      MessageBox::critical_message (nullptr, a.translate ("main", "Unexpected fatal error"));
      std::cerr << "Unexpected fatal error\n";
      throw;			// hoping the runtime might tell us more about the exception
    }
  return -1;
}