YFKlog Perl Ham Radio logger: https://fkurz.net/ham/yfklog.html
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
6488 lines
244 KiB
6488 lines
244 KiB
#!/usr/bin/perl -w |
|
|
|
# identation looks best with tw=4 |
|
|
|
# Several subroutines for yfklog, a amateur radio logbook software |
|
# |
|
# Copyright (C) 2005-2019 Fabian Kurz, DJ1YFK |
|
# |
|
# 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 2 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, write to the |
|
# Free Software Foundation, Inc., 59 Temple Place - Suite 330, |
|
# Boston, MA 02111-1307, USA. |
|
|
|
package yfksubs; |
|
|
|
require Exporter; |
|
@ISA = qw(Exporter); |
|
|
|
@EXPORT = qw( wpx dxcc makewindow clearinputfields qsotofields saveqso readw |
|
lastqsos callinfo getdate gettime splashscreen choseqso getqso chosepqso |
|
entrymask fkeyline winfomask selectlist askbox toggleqsl onlinelog |
|
preparelabels labeltex emptyqslqueue adifexport ftpupload adifimport getlogs |
|
changemycall newlogtable oldlogtable choseeditqso geteditqso editw updateqso checkdate |
|
awards statistics qslstatistics editdb editdbw savedbedit lotwimport |
|
databaseupgrade xplanet queryrig tableexists changeconfig readsubconfig |
|
connectdb jumpfield receive_qso tqslsign getlotwlocations |
|
getlotwstartdate downloadlotw redraw create_windows rundxc getch2 waitkey |
|
senddxc mycurs_set gridinfo); |
|
|
|
use strict; |
|
use POSIX; # needed for acos in distance/direction calculation |
|
use Curses; |
|
use Net::FTP; |
|
use IO::Socket::Timeout; |
|
use DBI; |
|
use IPC::SysV qw(IPC_PRIVATE IPC_RMID IPC_NOWAIT IPC_CREAT); |
|
use LWP::UserAgent (); |
|
use Net::Telnet (); |
|
|
|
use threads; |
|
use threads::shared; |
|
|
|
my $havehamdb = eval "require Ham::Callsign::DB;"; |
|
my $hamdb; |
|
if ($havehamdb) { |
|
require Ham::Callsign::DB; |
|
$hamdb = new Ham::Callsign::DB(); |
|
$hamdb->initialize_dbs(); |
|
} |
|
|
|
my $haveqrz = eval "require Ham::Reference::QRZ;"; |
|
|
|
|
|
# We load the default values for some variables that can be changed in .yfklog |
|
|
|
my $lidadditions="^QRP\$|^LGT\$"; |
|
my $csadditions="(^P\$)|(^M{1,2}\$)|(^AM\$)"; |
|
our $dbserver = ''; # Standard MySQL server |
|
our $dbport = 3306; # standard MySQL port |
|
our $dbuser = ""; # DB username |
|
our $dbpass = ""; # DB password |
|
our $dbname = ""; # DB name |
|
my $dbh; |
|
our $onlinedata = "`CALL`, `DATE`, round(`BAND`,2), `MODE`"; |
|
# Fields for online search log |
|
our $ftpserver = "127.0.0.1"; # ftp for online log / backup |
|
my $ftpport = "21"; # ftp server port |
|
my $ftpuser = ""; # ftp user |
|
my $ftppass = ""; # ftp passwd |
|
my $ftpdir = "log/"; # ftp directory |
|
our $mycall = "L1D"; # too stupid to set it? :-)) |
|
our $dpwr = "100"; # default PWR |
|
our $dqslsi = "N"; # def. QSL-s for import |
|
our $dqsls = "N"; # def. QSL-s |
|
our $operator = ""; # default OP. |
|
our $lat1 = "52"; # Latitude of own station |
|
our $lon1 = "-8"; # Longitude of own station |
|
our $bands = '160 80 40 30 20 17 15 12 10 2'; # bands for award purposes |
|
our $modes = 'CW SSB'; # modes for award purposes |
|
our $screenlayout=0; # screen layout, 0 or 1 |
|
#our $rigmodel = 0; # for hamlib |
|
#our $rigpath = '/dev/ttyS0'; # for hamlib |
|
my $rig=0; |
|
my $dband = '80'; |
|
my $dmode = 'CW'; |
|
our $checklogs = ''; # add. logs to chk fr prev QSOs |
|
our $lotwdetails='0'; # LOTW import details? |
|
our $autoqueryrig='0'; # Query rig at new QSO? |
|
our $directory='/tmp/'; # where to look for stuff |
|
our $prefix="/usr"; # may be changed by 'make' |
|
my $db=''; # sqlite or mysql? |
|
our $fieldorder= # TAB/Field order. |
|
'CALL DATE TON TOFF BAND MODE QTH NAME QSLS QSLR RSTS RSTR REM PWR'; |
|
my @fieldorder = split(/\s+/, $fieldorder); |
|
our $usehamdb = 0; |
|
our $askme=0; # ask before clearing QSOs etc |
|
our $logsort="N"; # Order of log display |
|
our $prevsort="D"; # Order of prev. QSOs |
|
our $browser='dillo'; |
|
our $hamlibport = 4532; |
|
our $hamlibaddr = '127.0.0.1'; |
|
our $lotwlocation=""; # LoTW station locations in format: CALL:location,CALL:location |
|
our $lotwuser=""; # Username for automatic LoTW download |
|
our $lotwpass=""; # Password for automatic LoTW download |
|
our $dxchost=""; # dx cluster host |
|
our $dxcport=0; # dx cluster telnet port |
|
our $dxccall=""; # dx cluster login callsign |
|
our $dxcmode="N"; # dx cluster mode. N = normal, B = bandmap |
|
|
|
my $db_keepalive = time; |
|
|
|
my @dxspots; # DX cluster thread -> main thread (DX spots) |
|
my @dxlines; # DX cluster thread -> main thread (raw lines) |
|
my @dxinput; # main thread -> DX cluster thread (keyboard input lines) |
|
|
|
my %wkdcalls = (); # worked calls - those will not be highlighted on the bandmap |
|
|
|
our $cursoron = 1; # show cursor all the time? (makes things easy for people with a screen reader) |
|
|
|
our $qrzuser = ""; # QRZ.com username for querying data from QRZ.com |
|
our $qrzpass = ""; # QRZ.com password (NOT the API key) |
|
|
|
share(@dxspots); |
|
share(@dxlines); |
|
share(@dxinput); |
|
share(%wkdcalls); |
|
|
|
sub redraw { |
|
endwin(); |
|
initscr(); |
|
getmaxyx($main::row, $main::col); |
|
$main::row-- if ($main::row % 2); |
|
|
|
&create_windows(); |
|
} |
|
|
|
sub create_windows { |
|
|
|
my $row = $main::row; |
|
my $col = $main::col; |
|
|
|
# DX cluster window. only create this if we have enough space |
|
# (at least 80 + 25 columns for one bandmap column) |
|
if ($col >= 105) { |
|
$main::wdxc = &makewindow($row,$col-80,0,80,5); |
|
} |
|
|
|
# GENERAL WINDOWS, always visible |
|
$main::whead = &makewindow(1,80,0,0,2); # head window |
|
$main::whelp = &makewindow(1,80,$row-1,0,2); # help window |
|
|
|
# LOGGING MODE WINDOWS ($status = 1) |
|
$main::winput = &makewindow(3,80,1,0,1); # Input Window |
|
$main::winfo = &makewindow(3,80,4,0,2); # DXCC/Info Window |
|
|
|
# depending on $screenlayout, the windows for previous QSOs and the recent |
|
# logbook are either next to each other or on top of each other. |
|
if ($screenlayout==0) { # original YFKlog style |
|
$main::wlog = &makewindow(($row-8),30,7,0,3); # Logbook |
|
$main::wqsos = &makewindow(($row-8),50,7,30,4); # prev. QSOs window |
|
} |
|
elsif ($screenlayout==1) { # more info, smaller windows |
|
# 8 lines are used for other stuff, so we have ($row-8)/2 lines left for |
|
# each window |
|
$main::wlog = &makewindow(($row-8)/2,80,7,0,3); # Logbook |
|
$main::wqsos = &makewindow(($row-8)/2,80,7+($row-8)/2,0,4); # prev. QSOs window |
|
} |
|
|
|
# EDIT / SEARCH MODE WINDOWS ($status = 10) |
|
$main::wedit = &makewindow(5,80,1,0,1); # Edit Window |
|
$main::weditlog = &makewindow(($row-7),80,6,0,4); # Search results |
|
|
|
$main::wmain = &makewindow($row-2,80,1,0,4); # general purpose window |
|
|
|
} |
|
|
|
# connect to a DX cluster (if configured) |
|
sub rundxc { |
|
my %bcfh = (); # band-call-> freq hash |
|
my %bcth = (); # band-call-> timestamp hash |
|
|
|
my $rows = $main::row; |
|
|
|
# each column in the bandmap requires 25 characters. from the total number |
|
# of available columns, 80 are already used by the logger, so we can |
|
# calculate the number of bandmap columns as follows: |
|
my $dxccols = int(($main::col - 80) / 25); |
|
|
|
my $maxspots = $rows * $dxccols; |
|
|
|
# DX cluster not configured? Exit thread. |
|
unless ($dxchost =~ /\./ && $dxcport =~ /^\d+$/ && $dxccall ne "") { |
|
return; |
|
} |
|
|
|
while (1) { |
|
@dxspots = (); |
|
push @dxspots, " Connecting to '$dxchost:$dxcport'"; |
|
push @dxspots, " with callsign '$dxccall'."; |
|
|
|
my $t = new Net::Telnet (Timeout => 600, Port => $dxcport, Prompt => '/./'); |
|
$t->open($dxchost); |
|
$t->print("$dxccall\n"); |
|
|
|
sleep(3); |
|
|
|
while (1) { |
|
|
|
# push keyboard input to cluster |
|
foreach my $l (@dxinput) { |
|
$t->print($l); |
|
} |
|
@dxinput = (); |
|
|
|
my $line = $t->getline(); |
|
chomp($line); |
|
|
|
push @dxlines, $line; |
|
if ($#dxlines > $rows) { shift @dxlines; } |
|
|
|
if ($line =~ /CW/ and $line =~ /DX de .*:\s+([0-9.]+)\s+([A-Z0-9\/]+)/) { |
|
my $dxcall = $2; |
|
my $freq = $1; |
|
$freq =~ s/(\.\d)\d$/$1/g; |
|
my $dxband = &freq2band($freq); |
|
|
|
$bcfh{$dxband}{$dxcall} = $freq; |
|
$bcth{$dxband}{$dxcall} = time; |
|
|
|
# update the @dxspots array |
|
&updatedxc(\%bcfh, \%bcth, $maxspots); |
|
} |
|
|
|
} # while 1 (when connected) |
|
} # while(1) outter loop |
|
} |
|
|
|
# push bandmap entries in shared array @dxspots. it will be |
|
# printed on screen by showdxc() from the main thread |
|
sub updatedxc { |
|
my $fr = shift; # reference to %bcfh |
|
my $tr = shift; # reference to %bcth |
|
my $maxspots = shift; |
|
|
|
my $c = 0; |
|
my $timeout = 300; |
|
|
|
@dxspots = (); |
|
|
|
do { |
|
$c = 0; |
|
for my $band ( sort { $b <=> $a } keys %{ $tr } ) { |
|
if ($c) { |
|
push @dxspots, ""; |
|
$c++; |
|
} |
|
for my $call ( sort { $fr->{$band}{$a} <=> $fr->{$band}{$b} } keys %{ $fr->{$band} } ) { |
|
my $age = int((time - $tr->{$band}{$call})/60); |
|
my $flag = defined($wkdcalls{$call}) ? 1 : 0; |
|
push @dxspots, sprintf("$age$flag%7.1f %s", $fr->{$band}{$call}, $call); |
|
$c++; |
|
|
|
# remove spots that are older than 5 minutes |
|
if ((time - $tr->{$band}{$call}) > $timeout) { |
|
delete($fr->{$band}{$call}); |
|
delete($tr->{$band}{$call}); |
|
} |
|
} |
|
} |
|
|
|
# bandmap full? reduce timeout gradually until we have resolved |
|
# the overflow. |
|
$timeout -= 1; |
|
} while ($c >= $maxspots); |
|
|
|
$timeout = 300; |
|
} |
|
|
|
# print dx cluster output or bandmap in wdxc window. |
|
# this is called from the main thread (getch2, on keyboard timeout) |
|
|
|
sub showdxc { |
|
my $win = $main::wdxc; |
|
my $rows = $main::row; |
|
|
|
return unless (defined($win)); |
|
|
|
# fill array of worked calls |
|
unless (keys %wkdcalls) { |
|
my $q = $dbh->prepare("SELECT distinct `call` FROM log_$mycall;"); |
|
$q->execute(); |
|
while (my @r = $q->fetchrow_array()) { |
|
$wkdcalls{$r[0]} = 1; |
|
} |
|
} |
|
|
|
# each column in the bandmap requires 25 characters. from the total number |
|
# of available columns, 80 are already used by the logger, so we can |
|
# calculate the number of bandmap columns as follows: |
|
my $dxccols = int(($main::col - 80) / 25); |
|
addstr($win, 0, 0, " "x($dxccols * 50 * $rows)); |
|
|
|
# "normal" dx cluster mode |
|
if ($dxcmode eq "N") { |
|
my $row = 0; |
|
foreach my $line (@dxlines) { |
|
addstr($win, $row++ , 1, $line); |
|
} |
|
} |
|
elsif ($dxcmode eq "B") { |
|
my $c = 0; |
|
foreach my $line (@dxspots) { |
|
# we split into columns with a width of 25 |
|
my $mrow = $c % $rows; |
|
my $mcol = int($c / $rows); |
|
next if ($mcol >= $dxccols); # don't swap into a non-existing column |
|
next if ($mrow == 0 && $line eq ""); # don't print empty line on top |
|
|
|
# extract age/flag from spot |
|
my $age = substr($line, 0, 1); |
|
my $flag = substr($line, 1, 1); |
|
|
|
if ($age < 1) { |
|
attron($win, A_BOLD); |
|
} |
|
|
|
if ($flag ne "1") { |
|
attron($win, COLOR_PAIR(8)); |
|
} |
|
addstr($win, $mrow , 1 + $mcol*25, substr($line, 2)); |
|
attroff($win, A_BOLD); |
|
attron($win, COLOR_PAIR(5)); |
|
$c++; |
|
} |
|
} |
|
|
|
refresh($win); |
|
|
|
# a little trick to get the cursor back to the entry field |
|
# where it was before: push a character into the keyboard |
|
# input queue that will be ignored |
|
ungetchar("~"); |
|
} |
|
|
|
sub senddxc { |
|
my $line = shift; |
|
push @dxinput, $line; |
|
} |
|
|
|
|
|
# We read the configuration file .yfklog. |
|
|
|
sub readsubconfig { |
|
|
|
unless (-e "$ENV{HOME}/.yfklog/config") { return 0 }; |
|
|
|
open CONFIG, "$ENV{HOME}/.yfklog/config" or die "Cannot open configuration file. Error: $!"; |
|
|
|
while (defined (my $line = <CONFIG>)) { # Read line into $line |
|
if ($line =~ /^lidadditions=(.+)/) { # We read the $lidadditions |
|
$lidadditions = $1; |
|
} |
|
elsif ($line =~ /^csadditions=(.+)/) { # We read the $csadditions |
|
$csadditions = $1; |
|
} |
|
elsif ($line =~ /^dbserver=(.+)/) { # We read the MySQL Server |
|
$dbserver= $1; |
|
} |
|
elsif ($line =~ /^dbport=(.+)/) { # We read the Server's port |
|
$dbport = $1; |
|
} |
|
elsif ($line =~ /^mycall=(.+)/) { # We read the own call |
|
$mycall = "\L$1"; |
|
} |
|
elsif ($line =~ /^dbuser=(.+)/) { # We read the db Username |
|
$dbuser = $1; |
|
} |
|
elsif ($line =~ /^dbpass=(.+)/) { # We read the db passwd |
|
$dbpass = $1; |
|
} |
|
elsif ($line =~ /^dbname=(.+)/) { # We read the db name |
|
$dbname= $1; |
|
} |
|
elsif ($line =~ /^onlinedata=(.+)/) { # We read the columns for |
|
$onlinedata= $1; # the online logbook |
|
} |
|
elsif ($line =~ /^ftpserver=(.+)/) { # We read the ftp server |
|
$ftpserver= $1; |
|
} |
|
elsif ($line =~ /^ftpport=(.+)/) { # We read the ftp port |
|
$ftpport= $1; |
|
} |
|
elsif ($line =~ /^ftpuser=(.+)/) { # We read the ftp username |
|
$ftpuser= $1; |
|
} |
|
elsif ($line =~ /^ftppass=(.+)/) { # We read the ftp password |
|
$ftppass= $1; |
|
} |
|
elsif ($line =~ /^ftpdir=(.+)/) { # We read the ftp directory |
|
$ftpdir= $1; |
|
} |
|
elsif ($line =~ /^dpwr=(.+)/) { # We read the default PWR |
|
$dpwr = $1; |
|
} |
|
elsif ($line =~ /^dqslsi=(.+)/) { # def. QSL-sent fr QSO imp. |
|
$dqslsi= $1; |
|
} |
|
elsif ($line =~ /^dqsls=(.+)/) { # def. QSL-sent |
|
$dqsls= $1; |
|
} |
|
elsif ($line =~ /^lat=(.+)/) { # Own latitude |
|
$lat1= $1; |
|
} |
|
elsif ($line =~ /^lon=(.+)/) { # Own longitude |
|
$lon1= $1; |
|
} |
|
elsif ($line =~ /^awardbands=(.+)/) { # bands for award purposes |
|
$bands= $1; |
|
} |
|
elsif ($line =~ /^awardmodes=(.+)/) { # modes for award purposes |
|
$modes= $1; |
|
} |
|
elsif ($line =~ /^screenlayout=(.+)/) { # screen layout, see doc. |
|
$screenlayout= $1; |
|
} |
|
elsif ($line =~ /^hamlibaddr=(.+)/) { |
|
$hamlibaddr= $1; |
|
} |
|
elsif ($line =~ /^hamlibport=(.+)/) { |
|
$hamlibport= $1; |
|
} |
|
elsif ($line =~ /^checklogs=(.+)/) { |
|
$checklogs = $1; |
|
} |
|
elsif ($line =~ /^lotwdetails=(.+)/) { |
|
$lotwdetails = $1; |
|
} |
|
elsif ($line =~ /^operator=(.+)/) { |
|
$operator = $1; |
|
} |
|
elsif ($line =~ /^autoqueryrig=(.+)/) { |
|
$autoqueryrig= $1; |
|
} |
|
elsif ($line =~ /^directory=(.+)/) { |
|
$directory = $1; |
|
} |
|
elsif ($line =~ /^fieldorder=(.+)/) { |
|
$fieldorder= $1; |
|
@fieldorder = split(/\s+/, $fieldorder); |
|
} |
|
elsif ($line =~ /^askme=(.+)/) { |
|
$askme = $1; |
|
} |
|
elsif ($line =~ /^logsort=(.+)/) { |
|
$logsort= $1; |
|
} |
|
elsif ($line =~ /^prevsort=(.+)/) { |
|
$prevsort = $1; |
|
} |
|
elsif ($line =~ /^browser=(.+)/) { |
|
$browser= $1; |
|
} |
|
elsif ($line =~ /^usehamdb=(.+)/) { |
|
$usehamdb= $1; |
|
} |
|
elsif ($line =~ /^lotwlocation=(.+)/) { |
|
$lotwlocation = $1; |
|
} |
|
elsif ($line =~ /^lotwuser=(.+)/) { |
|
$lotwuser= $1; |
|
} |
|
elsif ($line =~ /^lotwpass=(.+)/) { |
|
$lotwpass = $1; |
|
} |
|
elsif ($line =~ /^dxchost=(.+)/) { |
|
$dxchost = $1; |
|
} |
|
elsif ($line =~ /^dxcport=(.+)/) { |
|
$dxcport = $1; |
|
} |
|
elsif ($line =~ /^dxccall=(.+)/) { |
|
$dxccall = $1; |
|
} |
|
elsif ($line =~ /^dxcmode=(.+)/) { |
|
$dxcmode = $1; |
|
} |
|
elsif ($line =~ /^cursoron=(.+)/) { |
|
$cursoron = $1; |
|
} |
|
elsif ($line =~ /^qrzuser=(.+)/) { |
|
$qrzuser = $1; |
|
} |
|
elsif ($line =~ /^qrzpass=(.+)/) { |
|
$qrzpass = $1; |
|
} |
|
} |
|
close CONFIG; # Configuration read. |
|
|
|
return 1; |
|
|
|
} #readsubconfig |
|
|
|
# Only open Database when config file was read. |
|
if (&readsubconfig()) { |
|
&connectdb; |
|
} |
|
|
|
## We connect to the Database now... |
|
|
|
sub connectdb { |
|
|
|
if ($dbserver eq 'sqlite') { |
|
$db = 'sqlite'; |
|
$dbh = DBI->connect("DBI:SQLite:dbname=$ENV{HOME}/.yfklog/$dbname", |
|
$dbuser, $dbpass) |
|
or die "Could not connect to SQLite database: " . DBI->errstr; |
|
} |
|
else { # MYSQL, only if defined. |
|
$db = 'mysql'; |
|
$dbh = DBI->connect("DBI:mysql:$dbname;host=$dbserver",$dbuser,$dbpass) |
|
or die "Could not connect to MySQL database: " . DBI->errstr; |
|
} |
|
} |
|
|
|
|
|
# Now we read cty.dat or exit when it's not found. |
|
my $ctydat = "$prefix/share/yfklog/cty.dat"; |
|
if (-R "./cty.dat") { |
|
$ctydat = "./cty.dat"; |
|
} |
|
|
|
open CTY, "$ctydat" or die "$ctydat not found.". |
|
"Please download it from http://country-files.com/\n"; |
|
|
|
my %fullcalls; # hash of full calls (=DL1XYZ) |
|
my %prefixes; # hash of arrays main prefix -> (all, prefixes,..) |
|
my %dxcc; # hash of arrays main prefix -> (CQZ, ITUZ, ...) |
|
my $mainprefix; |
|
|
|
while (my $line = <CTY>) { |
|
if (substr($line, 0, 1) ne ' ') { # New DXCC |
|
$line =~ /\s+([*A-Za-z0-9\/]+):\s+$/; |
|
$mainprefix = $1; |
|
$line =~ s/\s{2,}//g; |
|
@{$dxcc{$mainprefix}} = split(/:/, $line); |
|
} |
|
else { # prefix-line |
|
$line =~ s/\s+//g; |
|
|
|
# read full calls into separate hash. this hash only |
|
# contains the information that this is a full call and |
|
# therefore doesn't need to be handled by &wpx even if |
|
# it contains a slash |
|
|
|
if ($line =~ /=/) { |
|
my @matches = ($line =~ /=([A-Z0-9\/]+)(\(\d+\))?(\[\d+\])?[,;]/g); |
|
foreach (@matches) { |
|
$fullcalls{$_} = 1 if $_; |
|
} |
|
} |
|
|
|
# Continue with everything else. Including full calls, which will |
|
# be read as normal prefixes. |
|
|
|
$line =~ s/=//g; |
|
|
|
# handle "normal" prefixes |
|
unless (defined($prefixes{$mainprefix}[0])) { |
|
@{$prefixes{$mainprefix}} = split(/,|;/, $line); |
|
} |
|
else { |
|
push(@{$prefixes{$mainprefix}}, split(/,|;/, $line)); |
|
} |
|
} |
|
} |
|
|
|
close CTY; |
|
|
|
|
|
############################################################################### |
|
# |
|
# &wpx derives the Prefix following WPX rules from a call. These can be found |
|
# at: http://www.cq-amateur-radio.com/wpxrules.html |
|
# e.g. DJ1YFK/TF3 can be counted as both DJ1 or TF3, but this sub does |
|
# not ask for that, always TF3 (= the attached prefix) is returned. If that is |
|
# not want the OP wanted, it can still be modified manually. |
|
# |
|
############################################################################### |
|
|
|
sub wpx { |
|
my ($call, $prefix,$a,$b,$c); |
|
$call = uc(shift); |
|
|
|
# First check if the call is in the proper format, A/B/C where A and C |
|
# are optional (prefix of guest country and P, MM, AM etc) and B is the |
|
# callsign. Only letters, figures and "/" is accepted, no further check if the |
|
# callsign "makes sense". |
|
# 23.Apr.06: Added another "/X" to the regex, for calls like RV0AL/0/P |
|
# as used by RDA-DXpeditions.... |
|
|
|
if ($call =~ |
|
/^((\d|[A-Z])+\/)?((\d|[A-Z]){3,})(\/(\d|[A-Z])+)?(\/(\d|[A-Z])+)?$/) { |
|
|
|
# Now $1 holds A (incl /), $3 holds the callsign B and $5 has C |
|
# We save them to $a, $b and $c respectively to ensure they won't get |
|
# lost in further Regex evaluations. |
|
|
|
($a, $b, $c) = ($1, $3, $5); |
|
if ($a) { chop $a }; # Remove the / at the end |
|
if ($c) { $c = substr($c,1,)}; # Remove the / at the beginning |
|
|
|
# In some cases when there is no part A but B and C, and C is longer than 2 |
|
# letters, it happens that $a and $b get the values that $b and $c should |
|
# have. This often happens with liddish callsign-additions like /QRP and |
|
# /LGT, but also with calls like DJ1YFK/KP5. ~/.yfklog has a line called |
|
# "lidadditions", which has QRP and LGT as defaults. This sorts out half of |
|
# the problem, but not calls like DJ1YFK/KH5. This is tested in a second |
|
# try: $a looks like a call (.\d[A-Z]) and $b doesn't (.\d), they are |
|
# swapped. This still does not properly handle calls like DJ1YFK/KH7K where |
|
# only the OP's experience says that it's DJ1YFK on KH7K. |
|
|
|
if (!$c && $a && $b) { # $a and $b exist, no $c |
|
if ($b =~ /$lidadditions/) { # check if $b is a lid-addition |
|
$b = $a; $a = undef; # $a goes to $b, delete lid-add |
|
} |
|
elsif (($a =~ /\d[A-Z]+$/) && ($b =~ /\d$/)) { # check for call in $a |
|
} |
|
} |
|
|
|
# *** Added later *** The check didn't make sure that the callsign |
|
# contains a letter. there are letter-only callsigns like RAEM, but not |
|
# figure-only calls. |
|
|
|
if ($b =~ /^[0-9]+$/) { # Callsign only consists of numbers. Bad! |
|
return undef; # exit, undef |
|
} |
|
|
|
# Depending on these values we have to determine the prefix. |
|
# Following cases are possible: |
|
# |
|
# 1. $a and $c undef --> only callsign, subcases |
|
# 1.1 $b contains a number -> everything from start to number |
|
# 1.2 $b contains no number -> first two letters plus 0 |
|
# 2. $a undef, subcases: |
|
# 2.1 $c is only a number -> $a with changed number |
|
# 2.2 $c is /P,/M,/MM,/AM -> 1. |
|
# 2.3 $c is something else and will be interpreted as a Prefix |
|
# 3. $a is defined, will be taken as PFX, regardless of $c |
|
|
|
if ((not defined $a) && (not defined $c)) { # Case 1 |
|
if ($b =~ /\d/) { # Case 1.1, contains number |
|
$b =~ /(.+\d)[A-Z]*/; # Prefix is all but the last |
|
$prefix = $1; # Letters |
|
} |
|
else { # Case 1.2, no number |
|
$prefix = substr($b,0,2) . "0"; # first two + 0 |
|
} |
|
} |
|
elsif ((not defined $a) && (defined $c)) { # Case 2, CALL/X |
|
if ($c =~ /^(\d)$/) { # Case 2.1, number |
|
$b =~ /(.+\d)[A-Z]*/; # regular Prefix in $1 |
|
# Here we need to find out how many digits there are in the |
|
# prefix, because for example A45XR/0 is A40. If there are 2 |
|
# numbers, the first is not deleted. If course in exotic cases |
|
# like N66A/7 -> N7 this brings the wrong result of N67, but I |
|
# think that's rather irrelevant cos such calls rarely appear |
|
# and if they do, it's very unlikely for them to have a number |
|
# attached. You can still edit it by hand anyway.. |
|
if ($1 =~ /^([A-Z]\d)\d$/) { # e.g. A45 $c = 0 |
|
$prefix = $1 . $c; # -> A40 |
|
} |
|
else { # Otherwise cut all numbers |
|
$1 =~ /(.*[A-Z])\d+/; # Prefix w/o number in $1 |
|
$prefix = $1 . $c;} # Add attached number |
|
} |
|
elsif ($c =~ /$csadditions/) { |
|
$b =~ /(.+\d)[A-Z]*/; # Known attachment -> like Case 1.1 |
|
$prefix = $1; |
|
} |
|
elsif ($c =~ /^\d\d+$/) { # more than 2 numbers -> ignore |
|
$b =~ /(.+\d)[A-Z]*/; # see above |
|
$prefix = $1; |
|
} |
|
else { # Must be a Prefix! |
|
if ($c =~ /\d$/) { # ends in number -> good prefix |
|
$prefix = $c; |
|
} |
|
else { # Add Zero at the end |
|
$prefix = $c . "0"; |
|
} |
|
} |
|
} |
|
elsif (defined $a) { # $a contains the prefix we want |
|
if ($a =~ /\d$/) { # ends in number -> good prefix |
|
$prefix = $a |
|
} |
|
else { # add zero if no number |
|
$prefix = $a . "0"; |
|
} |
|
} |
|
|
|
# In very rare cases (right now I can only think of KH5K and KH7K and FRxG/T |
|
# etc), the prefix is wrong, for example KH5K/DJ1YFK would be KH5K0. In this |
|
# case, the superfluous part will be cropped. Since this, however, changes the |
|
# DXCC of the prefix, this will NOT happen when invoked from with an |
|
# extra parameter $_[1]; this will happen when invoking it from &dxcc. |
|
|
|
if (($prefix =~ /(\w+\d)[A-Z]+\d/) && (not defined $_[1])) { |
|
$prefix = $1; |
|
} |
|
|
|
return $prefix; |
|
} |
|
else { return undef; } # no proper callsign received. |
|
} # wpx ends here |
|
|
|
|
|
############################################################################## |
|
# |
|
# &dxcc determines the DXCC country of a given callsign using the cty.dat file |
|
# provided by AD1C at https://www.country-files.com/ |
|
# An example entry of the file looks like this: |
|
# |
|
# Portugal: 14: 37: EU: 39.50: 8.00: 0.0: CT: |
|
# CQ,CR,CS,CT,=CR5FB/LH,=CS2HNI/LH,=CS5E/LH,=CT/DJ5AA/LH,=CT1BWW/LH,=CT1GFK/LH,=CT1GPQ/LGT, |
|
# =CT7/ON4LO/LH,=CT7/ON7RU/LH; |
|
# |
|
# The first line contains the name of the country, WAZ, ITU zones, continent, |
|
# latitude, longitude, UTC difference and main Prefix, the second line contains |
|
# possible Prefixes and/or whole callsigns that fit for the country, sometimes |
|
# followed by zones in brackets (WAZ in (), ITU in []). |
|
# |
|
# This sub checks the callsign against this list and the DXCC in which |
|
# the best match (most matching characters) appear. This is needed because for |
|
# example the CTY file specifies only "D" for Germany, "D4" for Cape Verde. |
|
# Also some "unusual" callsigns which appear to be in wrong DXCCs will be |
|
# assigned properly this way, for example Antarctic-Callsigns. |
|
# |
|
# Then the callsign (or what appears to be the part determining the DXCC if |
|
# there is a "/" in the callsign) will be checked against the list of prefixes |
|
# and the best matching one will be taken as DXCC. |
|
# |
|
# The return-value will be an array ("Country Name", "WAZ", "ITU", "Continent", |
|
# "latitude", "longitude", "UTC difference", "DXCC"). |
|
# |
|
############################################################################### |
|
|
|
sub dxcc { |
|
my $testcall = shift; |
|
my $matchchars=0; |
|
my $matchprefix=''; |
|
my $test; |
|
my $zones = ''; # annoying zone exceptions |
|
my $goodzone; |
|
my $letter=''; |
|
|
|
|
|
if ($fullcalls{$testcall}) { # direct match with "=" |
|
# do nothing! don't try to resolve WPX, it's a full |
|
# call and will match correctly even if it contains a / |
|
} |
|
elsif ($testcall =~ /(^OH\/)|(\/OH[1-9]?$)/) { # non-Aland prefix! |
|
$testcall = "OH"; # make callsign OH = finland |
|
} |
|
elsif ($testcall =~ /(^3D2R)|(^3D2.+\/R)/) { # seems to be from Rotuma |
|
$testcall = "3D2RR"; # will match with Rotuma |
|
} |
|
elsif ($testcall =~ /^3D2C/) { # seems to be from Conway Reef |
|
$testcall = "3D2CR"; # will match with Conway |
|
} |
|
elsif ($testcall =~ /(^LZ\/)|(\/LZ[1-9]?$)/) { # LZ/ is LZ0 by DXCC but this is VP8h |
|
$testcall = "LZ"; |
|
} |
|
elsif ($testcall =~ /\//) { # check if the callsign has a "/" |
|
my $prfx = &wpx($testcall,1); |
|
unless (defined($prfx)) { |
|
$prfx = "QQ"; # invalid |
|
} |
|
$testcall = $prfx."AA"; # use the wpx prefix instead, which may |
|
# intentionally be wrong, see &wpx! |
|
} |
|
|
|
$letter = substr($testcall, 0,1); |
|
|
|
foreach $mainprefix (keys %prefixes) { |
|
|
|
foreach $test (@{$prefixes{$mainprefix}}) { |
|
my $len = length($test); |
|
|
|
if ($letter ne substr($test,0,1)) { # gains 20% speed |
|
next; |
|
} |
|
|
|
$zones = ''; |
|
|
|
if (($len > 5) && ((index($test, '(') > -1) # extra zones |
|
|| (index($test, '[') > -1))) { |
|
$test =~ /^([A-Z0-9\/]+)([\[\(].+)/; |
|
$zones .= $2 if defined $2; |
|
$len = length($1); |
|
} |
|
|
|
if ((substr($testcall, 0, $len) eq substr($test,0,$len)) && |
|
($matchchars <= $len)) { |
|
$matchchars = $len; |
|
$matchprefix = $mainprefix; |
|
$goodzone = $zones; |
|
} |
|
} |
|
} |
|
|
|
my @mydxcc; # save typing work |
|
|
|
if (defined($dxcc{$matchprefix})) { |
|
@mydxcc = @{$dxcc{$matchprefix}}; |
|
} |
|
else { |
|
@mydxcc = qw/Unknown 0 0 0 0 0 0 ?/; |
|
} |
|
|
|
# Different zones? |
|
|
|
if ($goodzone) { |
|
if ($goodzone =~ /\((\d+)\)/) { # CQ-Zone in () |
|
$mydxcc[1] = $1; |
|
} |
|
if ($goodzone =~ /\[(\d+)\]/) { # ITU-Zone in [] |
|
$mydxcc[2] = $1; |
|
} |
|
} |
|
|
|
# cty.dat has special entries for WAE countries which are not separate DXCC |
|
# countries. Those start with a "*", for example *TA1. Those have to be changed |
|
# to the proper DXCC. Since there are opnly a few of them, it is hardcoded in |
|
# here. |
|
|
|
if ($mydxcc[7] =~ /^\*/) { # WAE country! |
|
if ($mydxcc[7] eq '*TA1') { $mydxcc[7] = "TA" } # Turkey |
|
if ($mydxcc[7] eq '*4U1V') { $mydxcc[7] = "OE" } # 4U1VIC is in OE.. |
|
if ($mydxcc[7] eq '*GM/s') { $mydxcc[7] = "GM" } # Shetlands |
|
if ($mydxcc[7] eq '*IG9') { $mydxcc[7] = "I" } # African Italy |
|
if ($mydxcc[7] eq '*IT9') { $mydxcc[7] = "I" } # Sicily |
|
if ($mydxcc[7] eq '*JW/b') { $mydxcc[7] = "JW" } # Bear Island |
|
|
|
} |
|
|
|
# CTY.dat uses "/" in some DXCC names, but I prefer to remove them, for example |
|
# VP8/s ==> VP8s etc. |
|
|
|
$mydxcc[7] =~ s/\///g; |
|
|
|
return @mydxcc; |
|
|
|
} # dxcc ends here |
|
|
|
############################################################################### |
|
# &makewindow Creates and refreshes a window with given name and color |
|
# parameters. |
|
# Since a newly initialized window's background color is at the |
|
# default, not at the color specified with attron($win, COLOR_PAIR()) (or I am |
|
# just too stupid to find out how to do it properly), this sub fills the window |
|
# with whitespaces, so it will have the color which was specified with attron. |
|
# |
|
# usage: &makewindow($height, $width, $ypos, $xpos, $color pair); |
|
############################################################################### |
|
|
|
sub makewindow { |
|
my $wind = newwin($_[0], $_[1], $_[2], $_[3]); # create window |
|
attron($wind, COLOR_PAIR($_[4])); # set colors |
|
addstr($wind, 0,0, " " x ($_[0]*$_[1])); # print x*y whitespaces |
|
move($wind, 0,0); # cursor back to start |
|
return $wind; # return window |
|
} |
|
|
|
########################################################################### |
|
# clearinputfields fills inputfields with spaces. |
|
# $_[0] -> window array |
|
# $_[1] -> when 1, clear windows 0..13, when 2 clear windows 0..25 |
|
# This is needed because in LOGGING mode only the first 14 windows are used |
|
########################################################################## |
|
|
|
sub clearinputfields { |
|
my @wi = @{$_[0]}; # Input windows |
|
my $num; # number of QSOs to delete.. |
|
|
|
if ($_[1] == 1) { $num = 14 } |
|
else { $num = 26 } |
|
|
|
for (my $a=0;$a < $num;$a++) { # go through all fields |
|
attron($wi[$a], COLOR_PAIR(5)); # input fields fg white, bg black |
|
addstr($wi[$a], 0,0, " " x 80); # lots of spaces to fill the window |
|
move($wi[$a], 0,0); # move cursor home |
|
refresh($wi[$a]); # refresh |
|
} |
|
} |
|
|
|
|
|
########################################################################### |
|
# qsotofields puts the content of the qso array (referenced by $qso, $_[0]) |
|
# into the input windows $wi, referenced by $_[1] |
|
# When $_[2] is 1, it will update windows 0..13 for Logging mode |
|
# When $_[2] is 2, it will update windows 0..17 for Edit mode |
|
########################################################################## |
|
|
|
sub qsotofields { |
|
my @qso= @{$_[0]}; # reference to QSO |
|
my @wi = @{$_[1]}; # reference to input-windows |
|
my $num; # number of windows to paint |
|
|
|
if ($_[2] == 1) { $num = 14 } |
|
else { $num = 26 } |
|
|
|
for (my $a=0;$a < $num;$a++) { # go through all fields in range |
|
attron($wi[$a], COLOR_PAIR(5)); # input fields fg white, bg black |
|
addstr($wi[$a], 0,0, $qso[$a]. " " x 80); # put QSO value + spaces |
|
move($wi[$a], 0,0); # move cursor home |
|
refresh($wi[$a]); # refresh |
|
} |
|
} |
|
|
|
############################################################################## |
|
# &saveqso Saves the passed array into the table log_$mycall, also adds |
|
# DXCC, Prefix, Continent and QSL-Info fields. |
|
# The QSL-Info is taken from the REMarks field, if it contains "via:<sth>". |
|
# the same applies for ITU, CQZ and IOTA, OPERATOR. Those can be entered in |
|
# the REMarks |
|
# field like OPERATOR:DL1LID ITU:34 CQZ:33 IOTA:EU-038 (with hyphen!). |
|
# These parts will be cut |
|
# out of the field if they represent a valid ITUZ, CQZ or IOTA nr. |
|
# The database is specified in the configfile and so are the server and the |
|
# port of the server. |
|
# If there is another parameter after the QSO-array, it is the number of the |
|
# QSO which is edited. This QSO has to be changed in the database then |
|
############################################################################## |
|
|
|
sub saveqso { |
|
%wkdcalls = (); # bandmap |
|
my $qslinfo = ""; # QSLinfo, IOTA and STATE will be read from the |
|
my $iota= ""; # remarks field, if available. |
|
my $state = ""; |
|
my $grid= ""; |
|
my @qso = (shift,shift,shift,shift,shift,shift,shift,shift,shift,shift, |
|
shift,shift,shift,shift); # get the @qso array |
|
my $editnr = shift; # QSO we edit |
|
|
|
if ($editnr) { # if existing QSO try get qslinfo |
|
my $n = $dbh->prepare("SELECT `QSLINFO` FROM log_$mycall |
|
WHERE `NR`='$editnr';"); |
|
$n->execute(); |
|
my @qslinfo = $n->fetchrow_array(); # local variable for info array |
|
$qslinfo = $qslinfo[0]; |
|
} |
|
|
|
# Cute date/times, just in case. |
|
$qso[1] = substr($qso[1],0,8); |
|
$qso[2] = substr($qso[2],0,4); |
|
$qso[3] = substr($qso[3],0,4); |
|
|
|
# Now we have to check if it is a valid entry |
|
if ((my $pfx = &wpx($qso[0]) ) && # check for a callsign, return PFX |
|
(length($qso[1]) == 8) && # check if date has proper length |
|
(substr($qso[1],0,2) < 32) && # sane day (of course not in all months) |
|
(substr($qso[1],2,2) < 13) && # valid month |
|
(substr($qso[1],4,) > 1900) && # :-) |
|
(length($qso[2]) == 4) && # check length of time on |
|
(substr($qso[2],0,2) < 24) && # valid hour in Time on |
|
(substr($qso[2],3,2) < 60) && # valid minute Time on |
|
($qso[4] ne "") && # band has some info |
|
($qso[5] ne "") && # mode has some info |
|
($qso[8] ne "") && # QSL sent |
|
($qso[9] ne "") # QSL rxed |
|
# RST, PWR not checked, will be 599 / 0 by default in the database, |
|
# Time-OFF can be "", if so, it will be replaced with current time |
|
) { # VALID ENTRY! put into database |
|
|
|
# unless we have a valid time off ... |
|
unless ((length($qso[3]) == 4) && # check length of time off |
|
(substr($qso[3],0,2) < 24) && # valid hour in Time on |
|
(substr($qso[3],2,2) < 60)){ # valid minute Time on |
|
$qso[3] = &gettime; # time off = current time |
|
} # Time off ready |
|
|
|
$qso[1] = # make date in YYYY-MM-DD format |
|
substr($qso[1],4,)."-".substr($qso[1],2,2)."-".substr($qso[1],0,2); |
|
|
|
$qso[2] = substr($qso[2],0,2).":".substr($qso[2],2,2).":00";# add seconds, : |
|
$qso[3] = substr($qso[3],0,2).":".substr($qso[3],2,2).":00";# add seconds, : |
|
|
|
my @dxcc = &dxcc($qso[0]); # get DXCC-array |
|
my $dxcc = $dxcc[7]; # dxcc prefix |
|
my $cont = $dxcc[3]; # dxcc continent |
|
my $ituz = $dxcc[2]; # dxcc itu zone |
|
my $cqz = $dxcc[1]; # dxcc CQ zone |
|
|
|
# searching for QSL-INFO in remarks-field: |
|
if ($qso[12] =~ /(.*)via:(\w+)(.*)/){ # QSL info in remarks field |
|
$qslinfo = $2; # save QSL-info |
|
$qso[12] = $1." ".$3; # cut qsl-info from remarks field |
|
$qslinfo =~ tr/[a-z]/[A-Z]/; # make qsl-info uppercase |
|
} |
|
|
|
# searching for different ITUZ in remarks-field: |
|
# Note: ITU-Zone should be entered as "3" and not "03" e.g.!! |
|
if ($qso[12] =~ /(.*)ITUZ:(\w+)(.*)/){ |
|
my ($a, $b, $c) = ($1, $2, $3); # save regex results |
|
# A valid ITU Zone is 01..90 |
|
if (($b =~ /^\d\d$/) && ($b > 0) && ($b < 91)) { |
|
$ituz = $b; |
|
$qso[12] = $a." ".$c; |
|
} |
|
} |
|
|
|
# searching for different CQZ in remarks-field: |
|
if ($qso[12] =~ /(.*)CQZ:(\w+)(.*)/){ |
|
my ($a, $b, $c) = ($1, $2, $3); # save regex results |
|
# A valid CQ Zone is 01..40 |
|
if (($b =~ /^\d\d$/) && ($b > 0) && ($b < 41)) { |
|
$cqz = $b; |
|
$qso[12] = $a." ".$c; |
|
} |
|
} |
|
|
|
# searching for a STATE in remarks-field: |
|
if ($qso[12] =~ /(.*)STATE:(\w\w)(.*)/){ |
|
$state = $2; |
|
$qso[12] = $1." ".$3; |
|
} |
|
|
|
# searching for a IOTA Nr in remarks-field: |
|
if ($qso[12] =~ /(.*)IOTA:(\w\w-\d\d\d)(.*)/){ |
|
my ($a, $b, $c) = ($1, $2, $3); # save regex results |
|
# A valid IOTA NR starts with a continent. Check this: |
|
if (substr($b,0,2) =~ /(EU|AF|AS|OC|NA|SA|AN)/) { |
|
$iota =$b; |
|
$qso[12] = $a." ".$c; |
|
} |
|
} |
|
|
|
# searching for an OPERATOR in remarks-field: |
|
if ($qso[12] =~ /(.*)OPERATOR:(\w+)(.*)/){ |
|
$operator = $2; |
|
$qso[12] = $1." ".$3; |
|
} |
|
|
|
# searching for a GRID in remarks-field. 4 or 6 letters |
|
if ($qso[12] =~ |
|
/(.*)GRID:([A-Z]{2}[0-9]{2}[A-Z]{2}|[A-Z]{2}[0-9]{2})(.*)/){ |
|
$grid = $2; |
|
$qso[12] = $1." ".$3; |
|
} |
|
|
|
# searching for a GRID in the QTH field |
|
if ($qso[6] =~ /^([A-Z]{2}[0-9]{2}[A-Z]{2}|[A-Z]{2}[0-9]{2})$/i){ |
|
$grid = uc($1); |
|
$qso[6] = uc($1); |
|
} |
|
|
|
# trim remark |
|
$qso[12] =~ s/\s*$//; |
|
|
|
# we are now ready to save the QSO, but we have to check if it's a |
|
# new QSO or if we are changing an existing QSO. |
|
|
|
if ($editnr) { # we change an existing QSO |
|
$dbh->do("UPDATE log_$mycall SET `CALL`='$qso[0]', |
|
`DATE`='$qso[1]', |
|
`T_ON`='$qso[2]', `T_OFF`='$qso[3]', `BAND`='$qso[4]', |
|
`MODE`='$qso[5]', `QTH`=".$dbh->quote($qso[6]).", `NAME`=".$dbh->quote($qso[7]).", |
|
`QSLS`='$qso[8]', `QSLR`='$qso[9]', `RSTS`='$qso[10]', |
|
`RSTR`='$qso[11]', `REM`=".$dbh->quote($qso[12]).", `PWR`='$qso[13]', |
|
`QSLINFO`='$qslinfo' WHERE NR='$editnr';"); |
|
} |
|
else { # new QSO |
|
$dbh->do("INSERT INTO log_$mycall |
|
(`CALL`, `DATE`, `T_ON`, `T_OFF`, `BAND`, `MODE`, `QTH`, |
|
`NAME`, `QSLS`, `QSLR`, `RSTS`, `RSTR`, `REM`, `PWR`, |
|
`DXCC`, `PFX`, `CONT`, `QSLINFO`, |
|
`ITUZ`, `CQZ`, `IOTA`, `STATE`, `QSLRL`, `OPERATOR`, `GRID`) |
|
VALUES ('$qso[0]', '$qso[1]', '$qso[2]', '$qso[3]', |
|
'$qso[4]', '$qso[5]', ".$dbh->quote($qso[6]).", ".$dbh->quote($qso[7]).", |
|
'$qso[8]', '$qso[9]', '$qso[10]', '$qso[11]', |
|
".$dbh->quote($qso[12]).", '$qso[13]', '$dxcc', '$pfx', |
|
'$cont', '$qslinfo', '$ituz', '$cqz', '$iota', |
|
'$state', 'N', '$operator', '$grid');"); |
|
} |
|
|
|
|
|
# voila, we have saved the QSO. Now we check if the callsign's name |
|
# and QTH info is already contained in the "calls"-table; if not, |
|
# we save it there. first we cut the callsign down to the homecall |
|
# only, by splitting it up at every /, then taking the longest |
|
# part. |
|
# |
|
my $call=$qso[0]; # will be the homecall |
|
my @call = split(/\//, $call); # split at every / |
|
my $length=0; # length of splitted part |
|
foreach(@call) { # chose longest part |
|
if (length($_) >= $length) { |
|
$length = length($_); |
|
$call = $_; |
|
} |
|
} |
|
|
|
my $sth = $dbh->prepare("SELECT `CALL` FROM calls WHERE |
|
`CALL`='$call';"); |
|
$sth->execute(); |
|
unless ($sth->fetch()) { # check if callsign not in DB |
|
if (($qso[7] ne "") || ($qso[6] ne "")) { # new things to add |
|
$dbh->do("INSERT INTO `calls` (`CALL`, `NAME`, `QTH`) VALUES |
|
('$call', ".$dbh->quote($qso[7]).", ".$dbh->quote($qso[6]).");"); |
|
} |
|
} |
|
|
|
# until now this only inserts, when both Name and QTH are unknown; |
|
# it doesn't update when only one part is unknown. needed? |
|
return 1; # successfully saved |
|
} |
|
else { # QSO invalid. Check what is wrong, make error msg |
|
&finderror(@qso); |
|
return 0; |
|
} |
|
} |
|
|
|
|
|
|
|
|
|
|
|
############################################################################## |
|
# readw reads what the user types into a window, depending on $_[1], |
|
# only numbers, callsign-characters, only letters or (almost) everything |
|
# is allowed. added 0.2.1: new mode for [0-9.] added (for bands). |
|
# $_[2] contains the windownumber, $_[3] the reference to the |
|
# QSO-array and $_[0] the reference to the Input-window-Array. |
|
# |
|
# $_[4] is the reference to $wlog |
|
# |
|
# $_[5] either contains 0 (normal) or a QSO number. If it's a number, it means |
|
# that we are editing an existing QSO, meaning that we have to call &saveqso |
|
# with the number as additional argument, so it will not save it as a new QSO. |
|
# The variable will be called $editnr. |
|
# |
|
# $_[6] means overwrite mode if nonzero. |
|
# |
|
# $_[7] is the maximum length of the field. |
|
# |
|
# The things you enter via the keyboard will be checked and if they are |
|
# matching the criterias of $_[1], it will be printed into the window and saved |
|
# in @qso. Editing is possible with arrow keys, delete and backspace. |
|
# |
|
# If an F-Key is pressed, following things can happen: |
|
# 1. F2 --> Current QSO is saved into the database, |
|
# read last 16 QSOs from database, write them into $wlog. |
|
# delete @qso and the content of all inputfields. |
|
# return 4. When this is detected, the while-loop where the |
|
# inputs are taken (while ($aw == 1)) will be exited and then entered |
|
# again because $aw is still 1, but then it starts at the callsign |
|
# field again. |
|
# 2. F3 --> clears out the current QSO. |
|
# 3. F4 --> updates date and start time in current QSO |
|
# 4. F5 --> Reads frequency and mode from the rig |
|
# 5. F9 --> return 2 as next active window $aw. --> $wlog. |
|
# 6. F10 --> returns 3 as next active window --> $wqsos |
|
# |
|
# If a regular entry was made, the return value is 1, because we stay in active |
|
# window 1 |
|
############################################################################## |
|
|
|
sub readw { |
|
my $ch; # the getchar() we read |
|
my $win = ${$_[0]}[$_[2]]; # get window to modify |
|
my $input = ${$_[3]}[$_[2]]; # stores what the user entered, |
|
# init from @qso. |
|
my $match = "[a-zA-Z0-9\/]"; # default match expression |
|
my $pos = 0; # cursor position in the field |
|
my $strpos = $pos; # cursor position in the string |
|
my $wlog = ${$_[4]}; # reference to log-windw |
|
my $editnr = ${$_[5]}; # reference to editnr |
|
|
|
my $debug=0; |
|
|
|
my $ovr = $_[6]; # overwrite |
|
my $width = $_[7]; # width is fixed |
|
|
|
# The string length $strlen is used to have entries larger than the width, |
|
# $_[2] is inspected to set the length according to SQL field length. |
|
my $strlen = $width; |
|
if ($_[2] == 0) { $strlen = 15; } # Call |
|
elsif ($_[2] == 5) { $strlen = 6; } # Mode |
|
elsif ($_[2] == 6) { $strlen = 15; } # QTH |
|
elsif ($_[2] == 7) { $strlen = 15; } # Name |
|
elsif ($_[2] == 10) { $strlen = 10; } # RSTs |
|
elsif ($_[2] == 11) { $strlen = 10; } # RSTr |
|
elsif ($_[2] == 12) { $strlen = 60; } # Remarks |
|
elsif ($_[2] == 13) { $strlen = 10; } # PWR |
|
|
|
move($win,0,0); # move cursor to first position |
|
addstr($win,0,0, $input." "x80); # pass $input to window, |
|
refresh($win); |
|
|
|
# For the date, time and band only figures are allowed, |
|
# to achieve this, invoke readw with $_[1] = 1 |
|
if ((defined $_[1]) && ($_[1] == "1")) { # only numbers |
|
$match = '\d'; # set match expression |
|
} |
|
|
|
# For the QSL-status only letters are allowed, |
|
# to achieve this, invoke readw with $_[1] = 2 |
|
if ((defined $_[1]) && ($_[1] == "2")) { # only letters |
|
$match = '[a-zA-Z]'; # set match expression |
|
} |
|
|
|
# For the Name, QTH and Remarks letters, figures and punctuation is allowed |
|
# to achieve this, invoke readw with $_[1] = 3 |
|
if ((defined $_[1]) && ($_[1] == "3")) { |
|
$match = "[\\w\\d!'\"$%&/()=?.,;:\\-@ ]"; # set match expression |
|
} |
|
|
|
# In the BAND-field, numbers and a decimal point are allowed. |
|
if ((defined $_[1]) && ($_[1] == "4")) { |
|
$match = '[0-9.]'; # set match expression |
|
} |
|
|
|
# Now the main loop starts which is waiting for any input from the keyboard |
|
# which is stored in $ch. If it is a valid character that matches $match, |
|
# it will be added to the string $input at the proper place. |
|
# |
|
# If an arrow key LEFT or RIGHT is entered, the position within the string |
|
# $input will be changed, considering that it can only be within |
|
# 0..length($input-1). The position is stored in $pos. |
|
# |
|
# If a control character like a F-Key, Enter or Tab is found, the sub |
|
# exists and $input is written to @qso, with attached information on which |
|
# key was pressed, as ||F1 .. ||F10. This way we can switch to the proper |
|
# window when we get back into the main loop. |
|
|
|
while (1) { # loop infinitely |
|
|
|
$pos-- if ($pos == $width); # keep cursor in field |
|
$strpos-- if ($strpos == $strlen); # stop if string filled |
|
|
|
# If the cursor positions in the field and the string are not the same |
|
# then give only a partial view of the string. |
|
if ($strpos > $pos) { |
|
if (length($input) < $width) { |
|
$pos = $strpos; # perfect, it fits again |
|
} |
|
addstr($win,0,0, substr($input, $strpos-$pos, )." "x80); |
|
} |
|
else { |
|
addstr($win,0,0, $input." "x80); # pass $input to window, |
|
} # delete all after $input. |
|
|
|
move ($win,0,$pos); # move cursor to $pos |
|
refresh($win); # show new window |
|
|
|
$ch = &getch2(); |
|
|
|
# We first check if it is a legal character of the specified $match, |
|
# and if the string will not be too long. |
|
# if so, it will be added to the string (at the proper position!) |
|
if (($ch =~ /^$match$/) && |
|
((length($input) < $strlen) || ($strpos < $strlen && $ovr)) |
|
) { |
|
|
|
unless ($_[1] == 3) { # Unless Name, QTH, Remarks |
|
$ch =~ tr/[a-z]/[A-Z]/; # make letters uppercase |
|
} |
|
# The new character will be added to $input at the right place. |
|
$strpos++; |
|
$pos++; |
|
|
|
if ($ovr) { |
|
$input = substr($input, 0, $strpos-1).$ch.substr($input, |
|
$strpos > length($input) ? $strpos-1 : $strpos, ); |
|
} |
|
else { |
|
$input = substr($input, 0, $strpos-1).$ch.substr($input, |
|
$strpos-1, ); |
|
} |
|
} |
|
|
|
# The l/r arrow keys change the position of the cursor to left or right |
|
# but only within the boundaries of $input. |
|
|
|
elsif ($ch eq KEY_LEFT) { |
|
if ($pos > 0) { $pos-- } |
|
if ($strpos > 0) { $strpos-- } |
|
} |
|
|
|
elsif ($ch eq KEY_RIGHT) { |
|
if (($pos < length($input)) && ($pos < $width)) { $pos++ } |
|
if ($strpos < length($input)) { $strpos++ } |
|
} |
|
|
|
elsif ($ch eq KEY_HOME) { # Pos1 key |
|
$pos = 0; |
|
$strpos = 0; |
|
} |
|
|
|
elsif ($ch eq KEY_END) { # End key |
|
$strpos = length($input); |
|
if ($strpos >= $strlen) {$strpos = $strlen-1;} |
|
$pos = $strpos; |
|
if ($pos >= $width) {$pos = $width-1;} |
|
} |
|
|
|
elsif (($ch eq KEY_DC) && ($strpos < length($input))) { # Delete key |
|
$input = substr($input, 0, $strpos).substr($input, $strpos+1, ); |
|
} |
|
|
|
# BACKSPACE. When pressing backspace, the character left of the cursor |
|
# is deleted, if it exists. For some reason, KEY_BACKSPACE only is true |
|
# when pressing CTL+H on my system (and all the others I tested); the |
|
# other tests lead to success, although it's probably less portable. |
|
# Found this solution in qe.pl by Wilbert Knol, ZL2BSJ. |
|
|
|
elsif ((($ch eq KEY_BACKSPACE) || (ord($ch)==8) || (ord($ch)==0x7F)) |
|
&& ($strpos > 0)) { |
|
$input = substr($input, 0, $strpos-1).substr($input, $strpos, ); |
|
$strpos--; |
|
if ($pos > 0) { $pos--; } |
|
} |
|
|
|
# Space, Tab, keydown and Enter are the keys to go to the next field, |
|
# except in mode $_[1], where it was already caught in the first |
|
# pattern match. If space, tab or newline is found, the sub puts |
|
# $input into the proper place in the @qso array: ${$_[3]}[$_[2]]; |
|
elsif (($ch =~ /^[ \t\n]$/) || $ch eq KEY_DOWN) { |
|
${$_[3]}[$_[2]] = $input; # save to @qso; |
|
return 1; |
|
} |
|
# Arrow-up or Shift-Tab goes to the previous QSO field. Everything |
|
# else same as above |
|
elsif (($ch eq KEY_UP) || ($ch eq '353')) { # Cursor up or Shift-Tab |
|
${$_[3]}[$_[2]] = $input; # save to @qso; |
|
return 7; # 6 -> one field back |
|
} |
|
|
|
# If the pressed key was F2, we will save; that is, when the qso array |
|
# has sufficient information for a good QSO. Then the qso-array |
|
# and the input fields are deleted. |
|
elsif ($ch eq KEY_F(2)) { # pressed F2 -> SAVE |
|
${$_[3]}[$_[2]] = $input; # save field to @qso |
|
if (&saveqso(@{$_[3]}[0..13],$editnr)) { # save @QSO to DB |
|
|
|
&clearinputfields($_[0],1); # clear input fields 0..13 |
|
# Increase serial number in QSO array, clear all other fields |
|
my $snr = ${$_[3]}[14]; |
|
if ($editnr == 0) { |
|
$snr++; |
|
} |
|
@{$_[3]} = ("","","","","","","","","","","","","",""); |
|
${$_[3]}[14] = $snr; |
|
# Now we actualize the display of the last QSOs in the |
|
# window $wlog. |
|
&lastqsos(\$wlog); |
|
${$_[5]} = 0; # we finished editing, if we |
|
# did at all. $editnr = 0 |
|
return 4; # success, leave readw, new Q |
|
} # if no success, we continue in the loop. |
|
} |
|
|
|
# exit to the MAIN MENU |
|
elsif ($ch eq KEY_F(1)) { |
|
my $k = 'y'; |
|
|
|
if ($askme && ${$_[3]}[0] ne '') { |
|
$k = &askconfirmation("Really go back to the menu? [y/N]", |
|
'y|n|\n|\s'); |
|
} |
|
|
|
return 5 if ($k =~ /y/i); # active window = 5 -> MENU |
|
} |
|
|
|
# F3 cancels the current QSO and returns to the CALL input field. |
|
# if $editnr is set (= we edit a QSO), it's set back to 0 |
|
# ask for confirmation if set in config file |
|
elsif ($ch eq KEY_F(3)) { # F3 pressed -> clear QSO |
|
my $k='y'; |
|
|
|
if ($askme) { |
|
$k = &askconfirmation("Really clear this QSO? [y/N]", |
|
'y|n|\n|\s'); |
|
} |
|
|
|
if ($k =~ /y/i) { |
|
for (0 .. 13) { # iterate through windows 0-13 |
|
addstr(@{$_[0]}[$_],0,0," "x80); # clear it |
|
refresh(@{$_[0]}[$_]); |
|
${$_[3]}[$_] = ""; # clear QSO array |
|
} |
|
${$_[5]} = 0; # editqso = 0 |
|
return 4; # return 4 -> to window 0 (call) |
|
} |
|
|
|
} |
|
|
|
# F4 -> update start time of the QSO |
|
elsif ($ch eq KEY_F(4)) { |
|
${$_[3]}[2] = &gettime; |
|
addstr(@{$_[0]}[2],0,0,&gettime); |
|
refresh(@{$_[0]}[2]); |
|
return 4; |
|
} |
|
|
|
# F5 -> get frequency and mode from the transceiver |
|
elsif ($ch eq KEY_F(5)) { # F5 pressed -> freq/mode from rig |
|
|
|
my ($freq, $mode) = ('80', 'CW'); |
|
if (&queryrig(\$freq, \$mode)) { |
|
${$_[3]}[4] = $freq; |
|
${$_[3]}[5] = $mode; |
|
|
|
addstr(@{$_[0]}[4],0,0,$freq." "); |
|
addstr(@{$_[0]}[5],0,0,$mode." "); |
|
refresh(@{$_[0]}[4]); |
|
refresh(@{$_[0]}[5]); |
|
} |
|
|
|
return 4; # return 4 because we want back to |
|
} |
|
|
|
# F6 -> open browser with qrz.com info on callsign |
|
elsif ($ch eq KEY_F(6)) { |
|
my $lookup = ${$_[3]}[0]; |
|
unless ($lookup) { $lookup = $input }; |
|
system("$browser http://www.qrz.com/db/$lookup > /dev/null 2>&1 &"); |
|
} |
|
|
|
# F7 -> go to remote mode for fldigi |
|
elsif ($ch eq KEY_F(7)) { |
|
return 6; |
|
} |
|
|
|
# go to log-window $wlog ($aw = 2) |
|
elsif ($ch eq KEY_F(9)) { |
|
return 2; |
|
} |
|
|
|
# go to prev-QSO-window $wqsos ($aw = 3) |
|
elsif ($ch eq KEY_F(10)) { |
|
return 3; |
|
} |
|
# QUIT YFKlog |
|
elsif ($ch eq KEY_F(12)) { # QUIT |
|
my $k='y'; |
|
|
|
if ($askme && ${$_[3]}[0] ne '') { |
|
$k = &askconfirmation("Really quit YFKlog? [y/N]", |
|
'y|n|\n|\s'); |
|
} |
|
|
|
if ($k =~ /y/i) { |
|
endwin; # Leave curses mode |
|
system ("killall -9 rigctld"); |
|
print "Thanks for using YFKlog!\n"; |
|
exit; |
|
} |
|
} |
|
} |
|
} |
|
|
|
############################################################################## |
|
# &lastqsos Prints the last 16 QSOs into the $wlog window. depending on $_[1], |
|
# 16 or 8 QSOs are displayed, with different layout. |
|
############################################################################## |
|
|
|
sub lastqsos { |
|
my $wlog = ${$_[0]}; # reference to $wlog window |
|
my $nr; # nr of QSOs to display |
|
my $y; # y-position in window |
|
my $by = " `NR` DESC "; |
|
|
|
if ($logsort eq 'C') { |
|
$by = " `DATE` DESC, `T_ON` DESC "; |
|
} |
|
|
|
if ($screenlayout == 0) { # original screen layout, 16 QSOs, small |
|
$nr = $main::row - 8; |
|
$y = $nr - 1; # y-position in $wlog |
|
} |
|
elsif ($screenlayout == 1) { # windows above each other, 8 QSOs |
|
$nr = ($main::row - 8)/2; |
|
$y = $nr - 1; # y-position in $wlog |
|
} |
|
|
|
# Now we fetch the last x QSOs in the database, only CALL, BAND, MODE and |
|
# date needed. |
|
my $l = $dbh->prepare("SELECT `CALL`, `BAND`, `MODE`, `DATE`, `T_ON`, |
|
`NAME`, `QTH`, `RSTS`, `RSTR`, `QSLS`, `QSLR`, `QSLRL` FROM |
|
log_$mycall |
|
ORDER BY $by LIMIT $nr"); |
|
$l->execute(); |
|
# temporary vars |
|
my ($call, $band, $mode, $date, $time, $name, $qth, $rsts, |
|
$rstr,$qsls,$qslr, $qslrl); |
|
$l->bind_columns(\$call, \$band, \$mode, \$date,\$time, \$name,\$qth, |
|
\$rsts,\$rstr,\$qsls,\$qslr, \$qslrl); |
|
while ($l->fetch()) { # while row available |
|
# we put the date into DD-MM-YY format from YYYY-MM-DD |
|
$date = substr($date,8,2).substr($date,4,4).substr($date,2,2); |
|
# cut Call, Name, QTH, RSTR, RSTS, mode, if needed |
|
$call = substr($call,0,12); |
|
$name = substr($name,0,8); |
|
$qth = substr($qth,0,13); |
|
$rstr = substr($rstr,0,3); |
|
$rsts = substr($rsts,0,3); |
|
$mode = substr($mode,0,5); |
|
|
|
if ($screenlayout == 0) { |
|
addstr($wlog,$y,0, sprintf("%-12s%-4s %-5s%-6s", |
|
$call,$band,$mode,$date)); |
|
} |
|
elsif ($screenlayout == 1) { |
|
substr($time,-3,)=''; # remove seconds |
|
addstr($wlog,$y,0, |
|
sprintf("%-12s%-4s %-5s%-4s %-6s %-8s %-13s %-3s %-3s %s %s %s ", |
|
$call,$band,$mode,$time,$date,$name,$qth,$rsts,$rstr, |
|
$qsls, $qslr, $qslrl)); |
|
} |
|
$y--; # move one row up |
|
} |
|
# If there were less than 16 QSOs in the log, the remaining lines have to |
|
# be filled with spaces |
|
if ($y > 0) { |
|
for $y (0 .. $y) { |
|
addstr($wlog,$y,0, " "x30) if ($screenlayout == 0); |
|
addstr($wlog,$y,0, " "x80) if ($screenlayout == 1); |
|
} |
|
} |
|
|
|
refresh($wlog); |
|
} |
|
|
|
############################################################################## |
|
# &gridinfo When a new GRID is entered in the input form, this sub is |
|
# called and it prints |
|
# 1) The distance and heading |
|
# 2) Previous stations from that grid in $wqsos window |
|
############################################################################## |
|
|
|
sub gridinfo { |
|
my $grid = ${$_[0]}[0]; # grid to analyse |
|
my $band = ${$_[0]}[4]; # band of the current QSO |
|
my $dxwin = $_[1]; # window where to print DXCC/Pfx |
|
my @wi = @{$_[2]}; # reference to input-windows |
|
my $wqsos = $_[3]; # qso-b4-window |
|
my $PI=3.14159265; # PI for the distance and bearing |
|
my $RE=6371; # Earth radius |
|
|
|
my $grid4 = substr($grid, 0, 4); |
|
|
|
addstr($dxwin, 1,38, sprintf("%-6d", 1000)); |
|
addstr($dxwin, 1,58, sprintf("%3d", 359)); |
|
|
|
my $nbr; # different layouts |
|
if ($screenlayout == 0) { |
|
$nbr = $main::row - 8; |
|
} |
|
if ($screenlayout == 1) { |
|
$nbr = ($main::row - 8)/2; |
|
} |
|
|
|
addstr($wqsos, 0, 0, " "x(80*$nbr)); |
|
|
|
# cfmed on which bands? |
|
my $q = $dbh->prepare("SELECT distinct(band) FROM log_$mycall WHERE substr(GRID, 1, 4) = '$grid4' and (qslr='Y' or qslrl='Y')"); |
|
$q->execute(); |
|
my %cfmedbands; |
|
while (my @b = $q->fetchrow_array()) { |
|
$cfmedbands{$b[0]} = 1; |
|
} |
|
|
|
# wkd on which bands? |
|
$q = $dbh->prepare("SELECT distinct(band) FROM log_$mycall WHERE substr(GRID, 1, 4) = '$grid4' order by band asc"); |
|
$q->execute(); |
|
my %wkdbands; |
|
my $new = 1; |
|
my $newb = 1; |
|
while (my @b = $q->fetchrow_array()) { |
|
$wkdbands{$b[0]} = defined($cfmedbands{$b[0]}) ? 'C' : 'W'; |
|
$new = 0; |
|
if ($b[0] == $band) { |
|
$newb = 0; |
|
} |
|
} |
|
|
|
if ($new) { |
|
$new = " New Grid!"; |
|
} |
|
elsif ($newb and $band) { |
|
$new = " New Grid on $band!"; |
|
} |
|
else { |
|
$new = ''; |
|
} |
|
|
|
my $line = "$grid4: "; |
|
foreach (sort { $a <=> $b } keys %wkdbands) { |
|
$line .= $_.$wkdbands{$_}." "; |
|
} |
|
|
|
addstr($wqsos, 0, 0, $line." ".$new." "x80); |
|
|
|
# callsigns worked from this exact grid |
|
$q = $dbh->prepare("SELECT distinct(`call`) from log_$mycall where grid like '$grid%'"); |
|
$q->execute(); |
|
my @calls; |
|
while (my @b = $q->fetchrow_array()) { |
|
push(@calls, $b[0]); |
|
} |
|
my $cls = "@calls"; |
|
$cls =~ s/(.{73}[^\s]*)\s+/$1\n/g; |
|
@calls = split(/\n/, $cls); |
|
|
|
addstr($wqsos, 2, 0, "Wkd from $grid:"); |
|
my $y = 3; |
|
foreach (@calls) { |
|
addstr($wqsos, $y++, 0, $_); |
|
} |
|
|
|
# for full grids, also search for calls from the same square |
|
if (length($grid) == 6) { |
|
$q = $dbh->prepare("SELECT distinct(`call`) from log_$mycall where grid like '$grid4%'"); |
|
$q->execute(); |
|
my @calls; |
|
while (my @b = $q->fetchrow_array()) { |
|
push(@calls, $b[0]); |
|
} |
|
|
|
$cls = "@calls"; |
|
$cls =~ s/(.{73}[^\s]*)\s+/$1\n/g; |
|
@calls = split(/\n/, $cls); |
|
$y++; |
|
addstr($wqsos, $y++, 0, "Wkd from $grid4:"); |
|
foreach (@calls) { |
|
addstr($wqsos, $y++, 0, $_); |
|
} |
|
|
|
} |
|
|
|
refresh($wqsos); |
|
refresh($dxwin); |
|
return; |
|
} |
|
|
|
|
|
|
|
############################################################################## |
|
# &callinfo When a new callsign is entered in the input form, this sub is |
|
# called and it prints |
|
# 1) The Name and QTH (from a separate database table), if available. |
|
# 2) The DXCC info, prefix, distance and beam heading. Info if new DXCC. |
|
# 3) The (max.) 16 last QSOs into the $wqsos-window. |
|
# 4) Club info (HSC, etc) |
|
# 5) IF $autoqueryrig = 1, get frequency / band from radio |
|
############################################################################## |
|
|
|
sub callinfo { |
|
my $call = ${$_[0]}[0]; # callsign to analyse |
|
my $band = ${$_[0]}[4]; # band of the current QSO |
|
my $dxwin = $_[1]; # window where to print DXCC/Pfx |
|
my @wi = @{$_[2]}; # reference to input-windows |
|
my $wqsos = $_[3]; # qso-b4-window |
|
my $editnr = $_[4]; # if we edit a QSO, we don't query the RIG |
|
my $prefix = &wpx($call); # determine the Prefix |
|
my $PI=3.14159265; # PI for the distance and bearing |
|
my $RE=6371; # Earth radius |
|
my $z =180/$PI; # Just to reduce typing in formular dist/dir |
|
my $foundlog = 0; |
|
|
|
my $ascdesc = ' ASC '; |
|
|
|
if ($prevsort eq 'D') { |
|
$ascdesc = ' DESC '; |
|
} |
|
|
|
if (defined $prefix) { # &wpx returns undef when callsign is invalid |
|
# Now we print all the fields to their appropriate locations, with |
|
# added whitespaces behind it so any previous entries will be |
|
# overwritten. |
|
my @dxcc = &dxcc($call); # dxcc array gets filled |
|
my $sprefix = substr($prefix, 0, 5); |
|
addstr($dxwin, 0,9, $dxcc[0]." " x (25-length($dxcc[0]))); |
|
addstr($dxwin, 0,40, $dxcc[7]." " x (5-length($dxcc[7]))); |
|
addstr($dxwin, 0,51, $sprefix." " x (5-length($sprefix))); |
|
addstr($dxwin, 0,61, $dxcc[2]." " x (2-length($dxcc[2]))); |
|
addstr($dxwin, 0,69, $dxcc[1]." " x (2-length($dxcc[1]))); |
|
addstr($dxwin, 1,5, $dxcc[4]." " x (7-length($dxcc[4]))); |
|
addstr($dxwin, 1,19, $dxcc[5]." " x (7-length($dxcc[5]))); |
|
|
|
my $lat2 = $dxcc[4]; # to save typing work :-) |
|
my $lon2 = $dxcc[5]; |
|
|
|
# g is the "distance angle", 0 .. pi |
|
my $g = acos(sin($lat1/$z)*sin($lat2/$z)+cos($lat1/$z)*cos($lat2/$z)* |
|
cos(($lon2-$lon1)/$z)); |
|
# The distance is $g * $RE |
|
my $dist = $g * $RE; |
|
|
|
# Direction |
|
my $dir = 0; |
|
|
|
unless ($dist == 0) { |
|
$dir = acos((sin($lat2/$z)-sin($lat1/$z)*cos($g))/ |
|
(cos($lat1/$z)*sin($g)))*360/(2*$PI); |
|
} |
|
|
|
# Shortpath |
|
if (sin(($lon2-$lon1)/$z) < 0) { $dir = 360 - $dir;} |
|
$dir = 360 - $dir; |
|
|
|
addstr($dxwin, 1,38, sprintf("%-6d",$dist)); |
|
addstr($dxwin, 1,58, sprintf("%3d",$dir)); |
|
|
|
# now we have to get the home-call to get the name, previous QSOs any |
|
# maybe (TBD) award data from the station. We split the callsign at |
|
# every / (if any), and then take the longest part as homecall. of |
|
# course such exotic calls as KH5K/K1A would get the wrong result but I |
|
# do not care :) |
|
|
|
my @call = split(/\//, $call); |
|
my $length=0; # length of splitted part |
|
foreach(@call) { # chose longest part |
|
if (length($_) >= $length) { |
|
$length = length($_); |
|
$call = $_; |
|
} |
|
} |
|
|
|
# We fetch the name and the qth (if available) from the database. |
|
|
|
my $nq = $dbh->prepare("SELECT NAME, QTH from calls WHERE |
|
`CALL`='$call'"); |
|
$nq->execute(); |
|
my ($name, $qth); # temporary vars |
|
$nq->bind_columns(\$name, \$qth); # bind references |
|
if ($nq->fetch()) { # if name available |
|
unless (${$_[0]}[7] ne '') { # and no name in $qso |
|
${$_[0]}[7] = $name; # save to @qso |
|
addstr($wi[7],0,0,"$name"); # put into window |
|
} |
|
unless (${$_[0]}[6] ne '') { # and no QTH in $qso |
|
${$_[0]}[6] = $qth; # save to @qso |
|
addstr($wi[6],0,0,"$qth"); # put into window |
|
} |
|
refresh($wi[6]); |
|
refresh($wi[7]); |
|
$foundlog = 1; |
|
} |
|
|
|
# If QTH or name is empty, query qrz.com to add missing details. |
|
|
|
if ($haveqrz && ($qrzuser ne "") && ($qrzpass ne "") && |
|
((${$_[0]}[7] == "") || (${$_[0]}[7] == ""))) { |
|
my $qrz = Ham::Reference::QRZ->new( |
|
callsign => $call, |
|
username => $qrzuser, |
|
password => $qrzpass |
|
); |
|
|
|
my $listing = $qrz->get_listing; |
|
|
|
# If no name has been found in a previous qso, grab name from qrz |
|
if (${$_[0]}[7] == "") { |
|
my $qrzname = $listing->{fname}." ".$listing->{name}; |
|
${$_[0]}[7] = $qrzname; |
|
addstr($wi[7],0,0,"$qrzname"); |
|
refresh($wi[7]); |
|
} |
|
# If no QTH has been found in a previous qso, grab QTH from qrz |
|
if (${$_[0]}[6] == "") { |
|
${$_[0]}[6] = $listing->{addr2}; |
|
addstr($wi[6],0,0,"$listing->{addr2}"); |
|
refresh($wi[6]); |
|
} |
|
} |
|
|
|
|
|
# Now the previous QSOs with the station will be displayed. A database |
|
# query is made for: CALL (because it might have been something |
|
# different than the homecall, like PA/DJ1YFK/p, DATE, time, band, |
|
# mode, QSL sent and QSL-rx. |
|
# (TBD maybe it would be worth thinking about adding an additional |
|
# column for the own call and then specify a list of logs to search in |
|
# the config file) |
|
|
|
# Select all QSOs where the base-callsign is $call (which is the base |
|
# call of the current QSO) |
|
|
|
my $nbr; # different layouts |
|
if ($screenlayout == 0) { |
|
$nbr = $main::row - 8; |
|
} |
|
if ($screenlayout == 1) { |
|
$nbr = ($main::row - 8)/2; |
|
} |
|
|
|
# First count... |
|
my $lqcount = $dbh->prepare("SELECT count(*) FROM log_$mycall WHERE |
|
`CALL` = '$call' OR `CALL` LIKE '\%/$call' OR |
|
`CALL` LIKE '\%/$call/\%' OR `CALL` LIKE '$call/\%';"); |
|
$lqcount->execute(); |
|
|
|
my $count = $lqcount->fetchrow_array(); |
|
|
|
my $lq = $dbh->prepare("SELECT `CALL`, `DATE`, `T_ON`, `BAND`, `MODE`, |
|
`QSLS`, `QSLR`, `NAME`, `QTH`, `RSTS`, `RSTR`, `QSLRL` from |
|
log_$mycall |
|
WHERE `CALL` = '$call' OR |
|
`CALL` LIKE '\%/$call' OR |
|
`CALL` LIKE '\%/$call/\%' OR |
|
`CALL` LIKE '$call/\%' |
|
ORDER BY `DATE` $ascdesc, `T_ON` $ascdesc;"); |
|
$lq->execute(); |
|
my ($lcall, $ldate, $ltime, $lband, $lmode, $lqsls, $lqslr, $lname, |
|
$lqth, $lrsts, $lrstr, $lqslrl); |
|
$lq->bind_columns(\$lcall, \$ldate, \$ltime, \$lband, \$lmode, \$lqsls, |
|
\$lqslr, \$lname, \$lqth, \$lrsts, \$lrstr, \$lqslrl); |
|
my $y = 0; |
|
while ($lq->fetch()) { # more QSOs available |
|
$ltime = substr($ltime, 0,5); # cut seconds from time |
|
$ldate = substr($ldate,8,2).substr($ldate,4,4).substr($ldate,2,2); |
|
# cut Call, Name, QTH, RSTR, RSTS, Mode |
|
$lcall = substr($lcall,0,12); |
|
$lname = substr($lname,0,8); |
|
$lqth = substr($lqth,0,13); |
|
$lrstr = substr($lrstr,0,3); |
|
$lrsts = substr($lrsts,0,3); |
|
$lmode = substr($lmode,0,5); |
|
|
|
my $line; |
|
if ($screenlayout == 0) { |
|
$line = sprintf("%-14s %-8s %-5s %4s %-4s %1s %1s %1s ", |
|
$lcall, $ldate, $ltime, $lband, $lmode, $lqsls, $lqslr,$lqslrl); |
|
} |
|
elsif ($screenlayout ==1) { |
|
$line = sprintf("%-12s%-4s %-5s%-4s %-6s %-8s %-13s %-3s %-3s %s %s %s ", |
|
$lcall,$lband,$lmode,$ltime,$ldate,$lname,$lqth,$lrsts, |
|
$lrstr, $lqsls, $lqslr, $lqslrl); |
|
} |
|
|
|
addstr($wqsos, $y, 0, $line); |
|
($y < $nbr) ? $y++ : last; # prints first 16 rows |
|
} # all QSOs printed |
|
for (;$y < $nbr;$y++) { # for the remaining rows |
|
addstr($wqsos, $y, 0, " "x80); # fill with whitespace |
|
} |
|
if ($count > ($nbr-1)) { # more QSOs than fit in window |
|
my $x; # x-position of msg, depending on width |
|
if ($screenlayout == 0) { |
|
$x = 47; # TODO maybe with getxy? |
|
} |
|
elsif ($screenlayout == 1) { |
|
$x=77; |
|
} |
|
|
|
addstr($wqsos, ($nbr-2), $x, ($count-$nbr)); |
|
addstr($wqsos, ($nbr-1), $x-1, "more"); |
|
} |
|
refresh($wqsos); |
|
|
|
# We fetch club membership information from the database ... |
|
# As of version 0.2.3: Also check other logbooks for the callsign |
|
# as given in .yfklog for previous QSOs. See .yfktest or MANUAL. |
|
|
|
my $clubline=''; # We will store the club infos here |
|
|
|
my $clubs = $dbh->prepare("SELECT `CLUB`, `NR` FROM clubs WHERE |
|
`CALL`='$call'"); |
|
$clubs->execute(); |
|
|
|
while (my @a = $clubs->fetchrow_array()) { # fetch row |
|
$clubline .= $a[0].":".$a[1]." "; # assemble string |
|
} |
|
# Output will be something like: AGCW:2666 HSC:1754 ... |
|
|
|
# now previous QSOs: |
|
|
|
my $qsoinotherlogs=''; |
|
|
|
$checklogs =~ s#/#_#g; |
|
my @calls = split(/\s+/, "\L$checklogs"); |
|
|
|
foreach my $callsign (@calls) { |
|
my $sth = $dbh->prepare("SELECT `CALL` FROM log_$callsign WHERE |
|
`CALL` = '$call' OR |
|
`CALL` LIKE '\%\/$call' OR |
|
`CALL` LIKE '\%\/$call\/\%' OR |
|
`CALL` LIKE '$call\/\%' |
|
"); # No more regex with SQlite.. |
|
$sth->execute(); |
|
if ($sth->fetch()) { |
|
$qsoinotherlogs.= "\U$callsign " unless ($callsign eq $mycall); |
|
} |
|
|
|
} |
|
|
|
if ($qsoinotherlogs ne '') { |
|
$qsoinotherlogs =~ s#_#/#g; |
|
$clubline .= 'Wkd as: '.$qsoinotherlogs; |
|
} |
|
|
|
########################################## |
|
# Show DXCC bandpoints for the $call, also add to club-line. if new |
|
# DXCC or bandpoint, give extra notice. |
|
|
|
my $dx = $dbh->prepare("SELECT count(*) from log_$mycall WHERE |
|
DXCC='$dxcc[7]';"); |
|
$dx->execute(); |
|
|
|
my $newdxcc = $dx->fetchrow_array(); |
|
|
|
if ($newdxcc) { # DXCC already wkd, show bands |
|
$dx = $dbh->prepare("SELECT `band`, `qslr`, `QSLRL` from |
|
log_$mycall WHERE |
|
DXCC='$dxcc[7]';"); |
|
|
|
$dx->execute(); |
|
|
|
my %bandhash; |
|
my @i; |
|
|
|
while (@i = $dx->fetchrow_array()) { |
|
if ($i[2] eq 'Y') { $i[1] = 'Y' } # LOTW = paper |
|
unless(defined($bandhash{$i[0]}) && $bandhash{$i[0]} ne 'N') { |
|
$bandhash{$i[0]} = $i[1]; |
|
} |
|
} |
|
|
|
my $j; |
|
my $string=''; |
|
|
|
foreach $j (sort {$a <=> $b} keys %bandhash) { |
|
$string .= "$j$bandhash{$j} "; |
|
} |
|
|
|
$string =~ s/Y/C/g; |
|
$string =~ s/N/W/g; |
|
|
|
$clubline .= $string; |
|
|
|
# bandpoint? |
|
|
|
unless ($string =~ /\b$band()[A-Z]\b/) { |
|
addstr($dxwin, 1, 65, "New Band!"); |
|
} |
|
else { |
|
addstr($dxwin, 1, 65, " "); |
|
} |
|
} |
|
else { # NEW DXCC |
|
addstr($dxwin, 1, 65, "New DXCC!"); |
|
} |
|
|
|
addstr($dxwin, 2, 0, sprintf("%-80s", $clubline)); |
|
refresh($dxwin); |
|
} |
|
|
|
########################################################## |
|
# Query rig if autoqueryrig = 1 and NO QSO being edited. |
|
########################################################## |
|
if ($autoqueryrig && !$editnr) { |
|
|
|
my ($band, $mode) = (${$_[0]}[4] , ${$_[0]}[5]); |
|
|
|
&queryrig(\$band, \$mode); |
|
|
|
${$_[0]}[4] = $band; |
|
${$_[0]}[5] = $mode; |
|
|
|
addstr($wi[4],0,0,$band." "); |
|
addstr($wi[5],0,0,$mode." "); |
|
refresh($wi[4]); |
|
refresh($wi[5]); |
|
} |
|
|
|
if ($usehamdb && $hamdb) { |
|
my $results = $hamdb->lookup(uc($call)); |
|
if ($results && $#$results > -1) { |
|
my $result = $results->[0]; # just get the first |
|
|
|
# assume that if we previously logged them the previous logged name |
|
# is right. |
|
if (!$foundlog) { |
|
my $nm = $result->{'first_name'} . " " . $result->{'last_name'}; |
|
${$_[0]}[7] = $nm; |
|
addstr($wi[7],0,0,$nm); |
|
refresh($wi[7]); |
|
} |
|
|
|
# assume the QTH may have moved though, so use the new one |
|
my $qth = $result->{'qth'}; |
|
${$_[0]}[6] = $qth; |
|
addstr($wi[6],0,0,$qth); |
|
refresh($wi[6]); |
|
|
|
my $remarks = ""; |
|
|
|
# remarks |
|
|
|
# class |
|
if (defined($result->{'operator_class'})) { |
|
$remarks .= "Cl: $result->{'operator_class'}"; |
|
} |
|
|
|
# GRID |
|
if (defined($result->{'Grid'})) { |
|
$remarks .= " GRID:$result->{'Grid'}"; |
|
} |
|
|
|
if (defined($result->{'State'})) { |
|
$remarks .= " STATE:$result->{'State'}"; |
|
} elsif ($result->{'Addr2'} =~ /[^,],\s*([^,]+)/) { |
|
$remarks .= " STATE:$1"; |
|
} |
|
|
|
if ($remarks ne '') { |
|
${$_[0]}[12] = $remarks; |
|
addstr($wi[12],0,0,$remarks); |
|
refresh($wi[12]); |
|
} |
|
} |
|
} |
|
} |
|
|
|
|
|
|
|
############################################################################## |
|
# &getdate; Uses gmtime() to get the current date in DDMMYYYY |
|
############################################################################## |
|
|
|
sub getdate { |
|
my @date = gmtime(); # $date[3] has day, 4 month, 5 year |
|
|
|
# The year is in years from 1900, month is counting from 0 from january. |
|
# Thus month++ and year += 1900; |
|
$date[4] += 1; |
|
if ($date[3] < 10) { $date[3] = "0".$date[3]; } # add leading zero |
|
if ($date[4] < 10) { $date[4] = "0".$date[4]; } |
|
my $date = $date[3].$date[4].($date[5] + 1900); |
|
|
|
return $date; |
|
} |
|
|
|
############################################################################## |
|
# &gettime; Uses gmtime() to get the current UTC / GMT format HHMM |
|
############################################################################## |
|
|
|
sub gettime { |
|
my @date = gmtime(); # $date[2] has hour, 1 has minutes |
|
if ($date[1] < 10) { $date[1] = "0".$date[1]; } # Add 0 if neccessary |
|
if ($date[2] < 10) { $date[2] = "0".$date[2]; } |
|
return $date[2].$date[1]; |
|
} |
|
|
|
############################################################################## |
|
# splashscreen returns the splash screen |
|
############################################################################## |
|
|
|
sub splashscreen { |
|
my $yfkver = $_[0]; |
|
return "YFKlog v$yfkver - a general purpose ham radio logbook |
|
|
|
Copyright (C) 2005-2019 Fabian Kurz, DJ1YFK |
|
|
|
This is free software, and you are welcome to redistribute it |
|
under certain conditions (see COPYING). |
|
|
|
YFKlog website: https://fkurz.net/ham/yfklog.html |
|
Your feedback is appreciated."; |
|
} |
|
return 1; |
|
|
|
############################################################################## |
|
# &choseqso This sub lets the OP chose a QSO from the logbook. It displays 16 |
|
# QSOs as usual in the $wlog window, the user can select a QSO with the cursor |
|
# keys. The list automatically scrolls up and down after the last or first QSO |
|
# in the window. PgUp and PgDwn jump a page up or down. |
|
# The return value is the NR of the selected QSO, as in the database column NR |
|
############################################################################## |
|
|
|
sub choseqso { |
|
my $wlog = ${$_[0]}; # reference to $wlog window |
|
my $offset=0; # offset for DB query. |
|
my $aline; # active line, cursor position. |
|
my $ch; # character we get from keyboard |
|
my $ret=0; # return value. saves the NR from the |
|
# database which suits in $aline |
|
my $goon=1; # "go on" in the do .. while loop |
|
my $nbr; # nr of lines/qsos |
|
my $y; # y-position for printing in $wlog |
|
my $totalcalls=0; # might be 0, then return |
|
|
|
my $by = " `NR` DESC "; |
|
|
|
if ($logsort eq 'C') { |
|
$by = " `DATE` DESC, `T_ON` DESC "; |
|
} |
|
|
|
# set active (highlighted) line according to screen layout |
|
if ($screenlayout == 0) { |
|
$nbr = $main::row-8; |
|
$aline = $nbr - 1; |
|
} |
|
elsif ($screenlayout == 1) { |
|
$nbr = ($main::row-8)/2; |
|
$aline=$nbr-1; |
|
} |
|
|
|
|
|
# Now we fetch 16/8 QSOs from the database, eventually with an offset when we |
|
# scrolled. only NR, CALL, BAND, MODE and DATE needed. |
|
# a do {..} while construct is used because we need a highlighted line right at |
|
# the start, without any extra key pressed |
|
|
|
|
|
do { # loop and get keyboard input |
|
|
|
# after every keystroke the database query is done again and the active |
|
# line displayed in another color. unfortunately chgat() does not work on |
|
# things that have already been sent to the display with refresh(), so only |
|
# colouring one line while scrolling is not possible. since I was too lazy |
|
# to save the 16/8 QSOs into some kind of array, I decided to do the query |
|
# every time again. no performance problems even on my old K6-300. |
|
|
|
my $cq = $dbh->prepare("SELECT `NR`, `CALL`, `BAND`, `MODE`, `DATE`, |
|
`T_ON`, `NAME`, `QTH`, `RSTS`, `RSTR`, `QSLS`, `QSLR`, `QSLRL` FROM |
|
log_$mycall ORDER BY $by LIMIT $offset, $nbr"); |
|
$cq->execute(); |
|
|
|
# my $nrofrows = $cq->execute(); |
|
|
|
# if ($nrofrows eq "0E0") { return "i"; } # nothing, back to log input |
|
|
|
# temporary vars |
|
my ($nr, $call, $band, $mode, $date, $time, $name, $qth, $rsts, |
|
$rstr,$qsls,$qslr, $qslrl); |
|
$cq->bind_columns(\$nr, \$call, \$band, \$mode, \$date,\$time, \$name, |
|
\$qth,\$rsts,\$rstr,\$qsls,\$qslr, \$qslrl); |
|
$y = ($nbr-1); |
|
my $callsthispage=0; # calls displayed on this page |
|
while ($cq->fetch()) { # while row available |
|
$callsthispage++; |
|
$totalcalls++; |
|
# we put the date into DD-MM-YY format from YYYY-MM-DD |
|
$date = substr($date,8,2).substr($date,4,4).substr($date,2,2); |
|
# cut Call, Name, QTH, RSTR, RSTS, Mode |
|
$call = substr($call,0,12); |
|
$name = substr($name,0,8); |
|
$qth = substr($qth,0,13); |
|
$rstr = substr($rstr,0,3); |
|
$rsts = substr($rsts,0,3); |
|
$mode = substr($mode,0,5); |
|
|
|
if ($y == $aline) { # highlight line? |
|
attron($wlog, COLOR_PAIR(1)); |
|
$ret = $nr; # remember the NR |
|
} |
|
if ($screenlayout == 0) { |
|
addstr($wlog,$y,0, sprintf("%-12s%-4s %-5s%-6s", |
|
$call,$band,$mode,$date)); # print formatted |
|
} |
|
elsif ($screenlayout ==1) { |
|
substr($time,-3,)=''; # remove seconds |
|
addstr($wlog,$y,0, |
|
sprintf("%-12s%-4s %-5s%-4s %-6s %-8s %-13s %-3s %-3s %s %s %s ", |
|
$call,$band,$mode,$time,$date,$name,$qth,$rsts,$rstr, |
|
$qsls, $qslr, $qslrl)); |
|
} |
|
|
|
attron($wlog, COLOR_PAIR(3)); |
|
$y--; # move one row up |
|
} |
|
while ($y > -1) { # fill remaining lines |
|
my $width=30; |
|
if ($screenlayout==1) {$width=80;} |
|
addstr($wlog,$y,0," "x$width); |
|
$y--; |
|
} |
|
|
|
move($wlog, $aline, 0); # move cursor to highlighted line |
|
refresh($wlog); |
|
|
|
return "i" unless ($totalcalls); # no QSOs! |
|
|
|
$ch = &getch2(); # get character from keyboard |
|
|
|
if ($ch eq KEY_DOWN || $ch eq 'j') { # key down was pressed |
|
if ($aline < ($nbr-1)) { # no scrolling needed |
|
$aline++; |
|
} |
|
elsif ($offset != 0) { # scroll down, when possible (=offset) |
|
# (when there is an offset, it means we have scrolled back, so we can |
|
# safely scroll forth again) |
|
$offset -= $nbr; # next $nr (16 or 8) |
|
$aline = 0; # cursor to highest line |
|
} |
|
} |
|
|
|
if ($ch eq KEY_UP || $ch eq 'k') { # key up was pressed |
|
if (($aline > -1) && |
|
($callsthispage>($nbr-$aline))) { # no scrolling needed |
|
$aline--; |
|
} |
|
elsif ($callsthispage > ($nbr-1)) { |
|
$offset += $nbr; # earlier 16/8 |
|
$aline = ($nbr-1); # cursor to lowest line |
|
} |
|
} |
|
|
|
if (($ch eq KEY_NPAGE) && ($offset != 0)) { # scroll down 16/8 QSOs |
|
$aline = 0; # first line |
|
$offset -= $nbr; # next 16/8 QSOs |
|
flushinp(); # avoid excessive scrolling |
|
} |
|
|
|
elsif (($ch eq KEY_PPAGE) && $callsthispage>7) {# scroll up 16/8 QSOs |
|
$aline = ($nbr-1); # last line |
|
$offset += $nbr; # prev 8/16 QSOs |
|
flushinp(); # avoid excessive scrolling |
|
} |
|
|
|
elsif ($ch eq KEY_F(1)) { # go to the MAIN MENU |
|
$goon = 0; # do not go on! |
|
$ret = "m"; # return value m = Menu |
|
} |
|
|
|
elsif ($ch eq KEY_F(8)) { # back to inp-window without any action |
|
$goon = 0; # do not go on! |
|
$ret = "i"; # return value i = Input Window |
|
} |
|
|
|
elsif ($ch eq KEY_F(10)) { # to QSO b4-window without any action |
|
$goon = 0; |
|
$ret = "q"; # return value q = QSO Window |
|
} |
|
|
|
elsif ($ch =~ /\s/) { # we selected a QSO! |
|
$goon=0; # get out of the do .. while loop |
|
} |
|
|
|
elsif ($ch eq KEY_F(12)) { # QUIT |
|
endwin; |
|
exit; |
|
} |
|
|
|
} while ($goon); # as long as goon is true, we loop |
|
return $ret; |
|
} # &choseqso ends here |
|
|
|
############################################################################## |
|
# &getqso Gets a number as parameter and returns the @qso array matching to |
|
# the number from the database. Also updates the content of the Inputfields to |
|
# the QSO. This works for fields 0..13 and is designed for the LOG INPUT mode. |
|
# (There is also geteditqso for the Search/Edit mode). |
|
############################################################################## |
|
|
|
sub getqso { |
|
my @qso; # QSO array |
|
my $q = $dbh->prepare("SELECT `CALL`, `DATE`, `T_ON`, `T_OFF`, `BAND`, `MODE`, |
|
`QTH`, `NAME`, `QSLS`, `QSLR`, `RSTS`, `RSTR`, `REM`, `PWR` FROM |
|
log_$mycall WHERE `NR`='$_[0]'"); |
|
$q->execute; |
|
@qso = $q->fetchrow_array; |
|
# proper format for the date (yyyy-mm-dd -> ddmmyyyy) |
|
$qso[1] = substr($qso[1],8,2).substr($qso[1],5,2).substr($qso[1],0,4); |
|
# proper format for the times. hh:mm:ss -> hhmm |
|
$qso[2] = substr($qso[2],0,2).substr($qso[2],3,2); |
|
$qso[3] = substr($qso[3],0,2).substr($qso[3],3,2); |
|
|
|
for (my $x=0;$x < 14;$x++) { # iterate through all input windows |
|
addstr(${$_[1]}[$x],0,0,$qso[$x]); # add new value from @qso. |
|
refresh(${$_[1]}[$x]); |
|
} |
|
|
|
return @qso; |
|
} |
|
|
|