Skip to content

Commit

Permalink
143 cwops cwt nonmember exchange not supported (#173)
Browse files Browse the repository at this point in the history
Fixes #143
CWOPS CWT Contest - adds non-member exchange support.
- allows non-member QTH information including State, Province, or
primary Country Prefix for non-US/Canada stations.
- caller status bar information displays user information, but does not
provide hints for US/Canada callers.
- removed Settings | CWOps Number menu item. User's CWOPS member number
is now set on the main screen's Exchange field.
  • Loading branch information
w7sst authored Feb 4, 2023
2 parents f950318 + 60b296b commit a392784
Show file tree
Hide file tree
Showing 9 changed files with 1,026 additions and 965 deletions.
1,718 changes: 860 additions & 858 deletions CWOPS.LIST

Large diffs are not rendered by default.

131 changes: 103 additions & 28 deletions CWOPS.pas
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,24 @@
interface

uses
Classes, Contest, Contnrs, DxStn, Log;
Classes, Generics.Defaults, Generics.Collections, Contest, Contnrs, DxStn, Log;

type
TCWOPSRec= class
public
call: string;
Name: string;
Number: string;
Call: string; // Station Callsign
Exch1: string; // Operator Name
Exch2: string; // Number/State/Province/Country Prefix
UserText: string; // station location/information string

function IsCWOpsMember: Boolean; // return whether operator is a member
class function compareCall(const left, right: TCWOPSRec) : integer; static;
end;

TCWOPS= class(TContest)
private
CWOPSList: TList;
CWOPSList: TObjectList<TCWOPSRec>;
Comparer: IComparer<TCWOPSRec>;
procedure Delimit(var AStringList: TStringList; const AText: string);

public
Expand All @@ -26,11 +31,10 @@ TCWOPS= class(TContest)
function PickStation(): integer; override;
procedure DropStation(id : integer); override;
function GetCall(id : integer): string; override;
function FindCallRec(out outrec: TCWOPSRec; const ACall: string): Boolean;
procedure GetExchange(id : integer; out station : TDxStation); override;
function GetStationInfo(const ACallsign: string) : string; override;
function ExtractMultiplier(Qso: PQso) : string; override;

function getcwopsname(id:integer): string;
function getcwopsnum(id:integer): integer;
end;

function IsNum(Num: String): Boolean;
Expand All @@ -39,9 +43,15 @@ TCWOPS= class(TContest)
implementation

uses
SysUtils;
SysUtils, ARRL;

function TCWOPS.LoadCallHistory(const AUserCallsign : string) : boolean;
const
// !!Order!!,Call,Name,Exch1,UserText,
CallInx : integer = 0;
NameInx : integer = 1;
ExchInx : integer = 2;
UserTextInx : integer = 3;
var
slst, tl: TStringList;
i: integer;
Expand All @@ -64,19 +74,20 @@ function TCWOPS.LoadCallHistory(const AUserCallsign : string) : boolean;

for i:= 0 to slst.Count-1 do begin
self.Delimit(tl, slst.Strings[i]);
if (tl.Count = 4) then begin
if (tl.Count >= 3) then begin
if CWO = nil then
CWO:= TCWOPSRec.Create;

CWO.Call:= UpperCase(tl.Strings[0]);
CWO.Name:= UpperCase(tl.Strings[1]);
CWO.Number:= tl.Strings[2];
CWO.Call:= UpperCase(tl.Strings[CallInx]);
CWO.Exch1:= UpperCase(tl.Strings[NameInx]);
CWO.Exch2:= UpperCase(tl.Strings[ExchInx]);
if tl.Count > UserTextInx then
CWO.UserText:= tl.Strings[UserTextInx];
if CWO.Call='' then continue;
if CWO.Name='' then continue;
if CWO.Number='' then continue;
if IsNum(CWO.Number) = False then continue;
if length(CWO.Name) > 10 then continue;
if length(CWO.Name) > 12 then continue;
if CWO.Exch1='' then continue;
if CWO.Exch2='' then continue;
if length(CWO.Exch1) > 10 then continue;
if length(CWO.Exch2) > 5 then continue;

CWOPSList.Add(CWO);
CWO := nil;
Expand All @@ -97,7 +108,8 @@ function TCWOPS.LoadCallHistory(const AUserCallsign : string) : boolean;
constructor TCWOPS.Create;
begin
inherited Create;
CWOPSList:= TList.Create;
CWOPSList:= TObjectList<TCWOPSRec>.Create;
Comparer := TComparer<TCWOPSRec>.Construct(TCWOPSRec.compareCall);
end;

destructor TCWOPS.Destroy;
Expand All @@ -122,14 +134,78 @@ procedure TCWOPS.DropStation(id : integer);

function TCWOPS.GetCall(id : integer): string;
begin
result := TCWOPSRec(CWOPSList.Items[id]).Call;
result := CWOPSList[id].Call;
end;


function TCWOPS.FindCallRec(out outrec: TCWOPSRec; const ACall: string): Boolean;
var
rec: TCWOPSRec;
{$ifdef FPC}
index: int64;
{$else}
index: integer;
{$endif}
begin
rec := TCWOPSRec.Create();
rec.Call := ACall;
outrec:= nil;
try
if CWOPSList.BinarySearch(rec, index, Comparer) then
outrec:= CWOPSList.Items[index];
finally
rec.Free;
end;
Result:= outrec <> nil;
end;


procedure TCWOPS.GetExchange(id : integer; out station : TDxStation);
begin
station.OpName := getcwopsname(id);
station.NR := getcwopsnum(id);
station.OpName := CWOPSList.Items[id].Exch1;
station.Exch1 := CWOPSList.Items[id].Exch1;
station.Exch2 := CWOPSList.Items[id].Exch2;
end;


{
return status bar information string from CWOPS call history file.
for members (w/ numeric Exch2), return their QTH string (UserText)
for DX Stations (not USA or Canada), return their Entity/Continent.
this string is used in MainForm.sbar.Caption (status bar).
We are careful not to disclose information that would give hints during
exchange copy (e.g. for non-members in US or Canada, we do not return
their city/state/province).
Format: '<call> [- <user text from CWOPS.LIST>] [- Entity/Continent]'
}
function TCWOPS.GetStationInfo(const ACallsign: string) : string;
var
cwopsrec : TCWOPSRec;
dxrec : TDXCCRec;
userText : string;
begin
cwopsrec := nil;
dxrec := nil;
userText := '';
result:= '';

if FindCallRec(cwopsrec, ACallsign) then
begin
// if caller is a member, include their UserText string.
// if non-member calling from USA or Canada, use either NA/USA or NA/Canada
// (otherwise UserText string gives a hint for State/Province).
// if UserText is empty, always return DXCC Continent/Entity.
userText := cwopsrec.UserText;
if gDXCCList.FindRec(dxrec, ACallsign) then
if userText.IsEmpty or
(not cwopsrec.IsCWOpsMember and
(dxrec.Entity.Equals('United States of America') or
dxrec.Entity.Equals('Canada'))) then
userText:= dxRec.Continent + '/' + dxRec.Entity;

if not userText.IsEmpty then
result:= ACallsign + ' - ' + userText;
end;
end;


Expand All @@ -144,18 +220,17 @@ function TCWOPS.ExtractMultiplier(Qso: PQso) : string;
end;


function TCWOPS.getcwopsname(id:integer): string;

function TCWOPSRec.IsCWOpsMember: Boolean;
begin
result := TCWOPSRec(CWOPSList.Items[id]).Name;
Result := IsNum(Exch2);
end;

function TCWOPS.getcwopsnum(id:integer): integer;

class function TCWOPSRec.compareCall(const left, right: TCWOPSRec) : integer;
begin
result := strtoint(TCWOPSRec(CWOPSList.Items[id]).Number);
Result := CompareStr(left.Call, right.Call);
end;


function IsNum(Num: String): Boolean;
var
X : Integer;
Expand Down
2 changes: 1 addition & 1 deletion DxStn.pas
Original file line number Diff line number Diff line change
Expand Up @@ -206,7 +206,7 @@ procedure TDxStation.DataToLastQso;
// Adding a contest: copy DxStation's Exch2 qso information into log
case SentExchTypes.Exch2 of
etSerialNr: TrueExch2 := IntToStr(Self.NR);
etCwopsNumber: TrueExch2 := IntToStr(Self.NR);
etGenericField: TrueExch2 := Self.Exch2;
etCqZone: TrueExch2 := IntToStr(Self.NR);
etArrlSection: TrueExch2 := Self.Exch2;
etStateProv: TrueExch2 := Self.Exch2;
Expand Down
13 changes: 7 additions & 6 deletions Ini.pas
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ interface
TExchange1Type = (etRST, etOpName, etFdClass);

// Exchange Field #2 Types
TExchange2Type = (etSerialNr, etCwopsNumber, etArrlSection, etStateProv,
TExchange2Type = (etSerialNr, etGenericField, etArrlSection, etStateProv,
etCqZone, etItuZone, etAge, etPower, etJarlOblastCode);

// Contest definition.
Expand All @@ -39,6 +39,7 @@ TContestDefinition = record
Key: PChar; // Identifying key (used in Ini files)
ExchType1: TExchange1Type;
ExchType2: TExchange2Type;
ExchCaptions: array[0..1] of String; // exchange field captions
ExchFieldEditable: Boolean; // whether the Exchange field is editable
ExchDefault: PChar; // contest-specific Exchange default message
Msg: PChar; // Exchange error message
Expand Down Expand Up @@ -76,13 +77,14 @@ TContestDefinition = record
(Name: 'CWOPS CWT';
Key: 'Cwt';
ExchType1: etOpName;
ExchType2: etCwopsNumber;
ExchType2: etGenericField;
ExchCaptions: ('Name', 'Exch');
ExchFieldEditable: True;
ExchDefault: 'David 1';
Msg: '''<op name> <CWOPS number>'' (e.g. DAVID 123)';
Msg: '''<op name> <CWOPS Number|State|Country>'' (e.g. DAVID 123)';
T:scCwt),
// expecting two strings [Name,Number] (e.g. David 123)
// Contest Exchange: <Name> <CW Ops Num>
// Contest Exchange: <Name> <CW Ops Num|State|Country Prefix>

(Name: 'ARRL Field Day';
Key: 'ArrlFd';
Expand Down Expand Up @@ -136,7 +138,6 @@ TContestDefinition = record
var
Call: string = 'VE3NEA';
HamName: string = 'Alex';
CWOPSNum: string = '1';
ArrlClass: string = '3A';
ArrlSection: string = 'GTA';
Wpm: integer = 25;
Expand Down Expand Up @@ -221,7 +222,7 @@ procedure FromIni;
MainForm.ComboBox2.ItemIndex := ReadInteger(SEC_STN, 'BandWidth', 9);

HamName := ReadString(SEC_STN, 'Name', '');
CWOPSNum := ReadString(SEC_STN, 'cwopsnum', CWOPSNum);
DeleteKey(SEC_STN, 'cwopsnum'); // obsolete at v1.83

MainForm.UpdCWMaxRxSpeed(ReadInteger(SEC_STN, 'CWMaxRxSpeed', MaxRxWpm));
MainForm.UpdCWMinRxSpeed(ReadInteger(SEC_STN, 'CWMinRxSpeed', MinRxWpm));
Expand Down
53 changes: 41 additions & 12 deletions Log.pas
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,8 @@ TMultList= class(TStringList)
CallSent: boolean; // msgHisCall has been sent; cleared upon edit.
NrSent: boolean; // msgNR has been sent. Seems to imply exchange sent.
Histo: THisto;
LogColWidths : Array[0..6] of integer; // retain original Log column widths
LogColWidthInitialized : boolean; // initialize LogColWidths on time only
{$ifdef DEBUG}
RunUnitTest : boolean; // run ExtractPrefix unit tests once
{$endif}
Expand Down Expand Up @@ -155,14 +157,32 @@ function FormatScore(const AScore: integer):string;
end;

procedure ScoreTableSetTitle(const ACol1, ACol2, ACol3, ACol4, ACol5, ACol6, ACol7 :string);
var
I: Integer;

// adjust column with for empty table title strings
procedure SetCaption(const I : integer; const ACaption : string);
begin
MainForm.ListView2.Column[I].Width:= IfThen(ACaption.IsEmpty, 0, LogColWidths[I]);
MainForm.ListView2.Column[I].Caption:= ACaption;
end;

begin
MainForm.ListView2.Column[0].Caption:= ACol1;
MainForm.ListView2.Column[1].Caption:= ACol2;
MainForm.ListView2.Column[2].Caption:= ACol3;
MainForm.ListView2.Column[3].Caption:= ACol4;
MainForm.ListView2.Column[4].Caption:= ACol5;
MainForm.ListView2.Column[5].Caption:= ACol6;
MainForm.ListView2.Column[6].Caption:= ACol7;
// retain initial log column widths (used to restore column widths)
if not LogColWidthInitialized then
begin
for I := Low(LogColWidths) to High(LogColWidths) do
LogColWidths[I]:= MainForm.ListView2.Column[I].Width;
LogColWidthInitialized:= true;
end;

SetCaption(0, ACol1);
SetCaption(1, ACol2);
SetCaption(2, ACol3);
SetCaption(3, ACol4);
SetCaption(4, ACol5);
SetCaption(5, ACol6);
SetCaption(6, ACol7);
end;

procedure ScoreTableInsert(const ACol1, ACol2, ACol3, ACol4, ACol5, ACol6, ACol7 :string);
Expand Down Expand Up @@ -222,7 +242,7 @@ procedure Clear;
// Adding a contest: set Score Table titles
case Ini.SimContest of
scCwt:
ScoreTableSetTitle('UTC', 'Call', 'Name', 'NR', 'Pref', 'Chk', 'Wpm');
ScoreTableSetTitle('UTC', 'Call', 'Name', 'Exch', '', 'Chk', 'Wpm');
scFieldDay:
ScoreTableSetTitle('UTC', 'Call', 'Class', 'Section', 'Pref', 'Chk', 'Wpm');
scNaQp:
Expand Down Expand Up @@ -507,7 +527,7 @@ procedure SaveQso;
Result := false;
case Mainform.RecvExchTypes.Exch2 of
etSerialNr: Result := Length(text) > 0;
etCwopsNumber: Result := Length(text) > 0;
etGenericField:Result := Length(text) > 0;
etArrlSection: Result := Length(text) > 1;
etStateProv: Result := Length(text) > 1;
etCqZone: Result := Length(text) > 0;
Expand Down Expand Up @@ -552,7 +572,7 @@ procedure SaveQso;
//save Exchange2 (Edit3)
case Mainform.RecvExchTypes.Exch2 of
etSerialNr: Qso.Nr := StrToInt(Edit3.Text);
etCwopsNumber: Qso.Nr := StrToInt(Edit3.Text);
etGenericField:Qso.Exch2 := Edit3.Text;
etArrlSection: Qso.Exch2 := Edit3.Text;
etStateProv: Qso.Exch2 := Edit3.Text;
etCqZone: Qso.NR := StrToInt(Edit3.Text);
Expand Down Expand Up @@ -627,7 +647,7 @@ procedure LastQsoToScreen;
scCwt:
ScoreTableInsert(FormatDateTime('hh:nn:ss', t), Call
, Exch1
, format('%.d', [Nr])
, Exch2
, Pfx, Err, format('%.2d', [TrueWpm]));
scFieldDay:
ScoreTableInsert(FormatDateTime('hh:nn:ss', t), Call
Expand Down Expand Up @@ -692,7 +712,16 @@ procedure CheckErr;
// Adding a contest: check for contest-specific exchange field 2 errors
case Mainform.RecvExchTypes.Exch2 of
etSerialNr: if TrueNr <> NR then Err := 'NR ';
etCwopsNumber: if TrueNr <> NR then Err := 'NR ';
etGenericField:
// Adding a contest: implement comparison for Generic Field type
case Ini.SimContest of
scCwt:
if TrueExch2 <> Exch2 then
Err := IfThen(IsNum(TrueExch2), 'NR ', 'QTH');
else
if TrueExch2 <> Exch2 then
Err := 'ERR';
end;
etCqZone: if TrueNr <> NR then Err := 'ZN ';
etArrlSection: if TrueExch2 <> Exch2 then Err := 'SEC';
etStateProv: if TrueExch2 <> Exch2 then Err := 'ST ';
Expand Down
6 changes: 1 addition & 5 deletions Main.dfm
Original file line number Diff line number Diff line change
Expand Up @@ -1514,13 +1514,9 @@ object MainForm: TMainForm
end
end
object Operator1: TMenuItem
Caption = 'HST/CWOps Operator'
Caption = 'HST Operator'
OnClick = Operator1Click
end
object N5: TMenuItem
Caption = 'CWOps Number'
OnClick = CWOPSNumberClick
end
object mnuShowCallsignInfo: TMenuItem
Caption = 'Show Callsign Info'
OnClick = mnuShowCallsignInfoClick
Expand Down
Loading

0 comments on commit a392784

Please sign in to comment.