Skip to content

Commit

Permalink
ARRL SS - Display missing/invalid exchange error when missing
Browse files Browse the repository at this point in the history
- When exchange contains missing or invalid elements, display an
  error message before sending '?' to notify user of what information
  is needed.
- Improve exchange error message processing
- Report callsign length error before sending 'TU'
- display '?' for missing exchange fields in Log report
- Fixes Issue #352

Minor Changes
- fix crash when missing numeric exchange
- minor change to SSExchParserTest unit test
- remove assertion in release mode
  • Loading branch information
w7sst committed Sep 21, 2024
1 parent fcea837 commit 2c905ec
Show file tree
Hide file tree
Showing 7 changed files with 199 additions and 6 deletions.
82 changes: 82 additions & 0 deletions ArrlSS.pas
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,11 @@ TSweepstakes = class(TContest)

function FindCallRec(out ssrec: TSweepstakesCallRec; const ACall: string): Boolean;
procedure SendMsg(const AStn: TStation; const AMsg: TStationMessage); override;
procedure OnWipeBoxes; override;
function OnExchangeEdit(const ACall, AExch1, AExch2: string;
out AExchSummary: string) : Boolean; override;
procedure OnExchangeEditComplete; override;
procedure SetHisCall(const ACall: string); override;
function CheckEnteredCallLength(const ACall: string;
out AExchError: String) : boolean; override;
function ValidateEnteredExchange(const ACall, AExch1, AExch2: string;
Expand Down Expand Up @@ -256,6 +261,83 @@ procedure TSweepstakes.SendMsg(const AStn: TStation; const AMsg: TStationMessage
end;


{
Called after a QSO has completed or when user wipes (clears) all exchange
entry boxes on the GUI.
}
procedure TSweepstakes.OnWipeBoxes;
begin
inherited OnWipeBoxes;
ExchValidator.OnWipeBoxes;
end;


{
User has finished typing in the exchange fields and has pressed Enter or
another command keystroke.
Set Log.CallSent to False if the callsign has been modified or corrected.
}
procedure TSweepstakes.OnExchangeEditComplete;
begin
if ExchValidator.Call.IsEmpty then
inherited OnExchangeEditComplete
else if ExchValidator.Call <> Self.Me.HisCall then
Log.CallSent := False;
end;


{
This overriden SetHisCall will:
- if the exchange field contains a callsign correction, apply it here;
otherwise call the base class.
- sets TContest.Me.HisCall.
- sets Log.CallSent to False if the callsign should be sent.
}
procedure TSweepstakes.SetHisCall(const ACall: string);
begin
var CorrectedCallsign: string := ExchValidator.Call;
if CorrectedCallsign <> '' then
begin
// resend Callsign if it has changed since last time it was sent
if (CorrectedCallsign <> Self.Me.HisCall) and
not Self.Me.UpdateCallInMessage(CorrectedCallsign) then
begin
Self.Me.HisCall := CorrectedCallsign;
Log.CallSent := True;
end
else if (CorrectedCallsign = Self.Me.HisCall) and not CallSent then
Log.CallSent := True;
end
else
inherited SetHisCall(ACall);
end;


{
Called after each keystoke for the Exch2 entry field.
Parse user-entered Exchange and returns the Exchange summary.
Overriden here to handle complex ARRL Sweepstakes exchange.
Returns whether Exchange summary is non-empty.
}
function TSweepstakes.OnExchangeEdit(
const ACall, AExch1, AExch2: string; out AExchSummary: string) : Boolean;
var
ExchError: string;
begin
// incrementally parse the exchange with each keystroke
ExchValidator.ValidateEnteredExchange(ACall, AExch1, AExch2, ExchError);

// return summary (displayed above Exch2's Caption)
AExchSummary := ExchValidator.ExchSummary;
Result := not AExchSummary.IsEmpty;
end;


{
Verify callsign using length-based check.
For ARRL SS, if Call has been parsed, it is assumed valid; otherwise
call the base class implementation.
}
function TSweepstakes.CheckEnteredCallLength(const ACall: string;
out AExchError: String) : boolean;
begin
Expand Down
53 changes: 53 additions & 0 deletions Contest.pas
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,11 @@ TContest = class
const AStationCallsign : string) : TExchTypes; virtual;
procedure SendMsg(const AStn: TStation; const AMsg: TStationMessage); virtual;
procedure SendText(const AStn: TStation; const AMsg: string); virtual;
procedure OnWipeBoxes; virtual;
function OnExchangeEdit(const ACall, AExch1, AExch2: string;
out AExchSummary: string) : Boolean; virtual;
procedure OnExchangeEditComplete; virtual;
procedure SetHisCall(const ACall: string); virtual;

function CheckEnteredCallLength(const ACall: string;
out AExchError: String) : boolean; virtual;
Expand Down Expand Up @@ -432,6 +437,54 @@ procedure TContest.SendText(const AStn: TStation; const AMsg: string);
end;


{
Called at end of each QSO or by user's Cntl-W (Wipe Boxes) keystroke.
}
procedure TContest.OnWipeBoxes;
begin
Log.CallSent := False;
Log.NrSent := False;
end;


{
Called after each keystroke of the Exch2 field (Edit3).
}
function TContest.OnExchangeEdit(const ACall, AExch1, AExch2: string;
out AExchSummary: string) : Boolean;
begin
AExchSummary := '';
Result := False;
end;


{
Called at the start of each action/command after user has finished typing
in the Exchange fields. Can be overriden as needed for complex exchange
behaviors (e.g. ARRL SS).
}
procedure TContest.OnExchangeEditComplete;
begin
Log.CallSent := (Mainform.Edit1.Text <> '') and
(Mainform.Edit1.Text = Self.Me.HisCall);
end;


{
SetHisCall will:
- sets TContest.Me.HisCall to the supplied callsign, ACall.
- sets Log.CallSent to False if the callsign should be sent.
Override as needed to provide more complex callsign behaviors (e.g. ARRL
Sweepstakes allows callsign corrections in the exchange).
}
procedure TContest.SetHisCall(const ACall: string);
begin
Self.Me.HisCall := ACall;
Log.CallSent := ACall <> '';
end;


{
Find exchange errors in the current Qso.
Called at end of each Qso during Qso validaiton.
Expand Down
2 changes: 1 addition & 1 deletion Log.pas
Original file line number Diff line number Diff line change
Expand Up @@ -184,7 +184,7 @@ implementation
CQWW_RST_COL = 'RST,4,L';
CQ_ZONE_COL = 'Zone,4,L';
SS_CALL_COL = 'Call,9,L';
SS_PREC_COL = 'Pr,2.5,C';
SS_PREC_COL = 'Pr,2.5,L';
SS_CHECK_COL = 'Chk,3.25,C';

{$ifdef DEBUG}
Expand Down
1 change: 1 addition & 0 deletions Main.dfm
Original file line number Diff line number Diff line change
Expand Up @@ -231,6 +231,7 @@ object MainForm: TMainForm
TabOrder = 2
OnEnter = Edit3Enter
OnKeyPress = Edit3KeyPress
OnKeyUp = Edit3KeyUp
end
object Panel2: TPanel
Left = 522
Expand Down
33 changes: 30 additions & 3 deletions Main.pas
Original file line number Diff line number Diff line change
Expand Up @@ -314,6 +314,7 @@ TMainForm = class(TForm)
procedure Edit4Exit(Sender: TObject);
procedure SpinEdit1Exit(Sender: TObject);
procedure Edit3Enter(Sender: TObject);
procedure Edit3KeyUp(Sender: TObject; var Key: Word; Shift: TShiftState);

private
MustAdvance: boolean; // Controls when Exchange fields advance
Expand Down Expand Up @@ -528,9 +529,10 @@ procedure TMainForm.SendMsg(AMsg: TStationMessage);
SpinEdit1Exit(SpinEdit1);

if AMsg = msgHisCall then begin
// retain current callsign, including ''. if empty, return.
Tst.Me.HisCall := Edit1.Text;
CallSent := Edit1.Text <> '';
// retain current callsign, including ''.
Tst.SetHisCall(Edit1.Text); // virtual; sets Tst.Me.HisCall and Log.CallSent

// if his callsign is empty or hasn't changed, return
if not CallSent then
Exit;

Expand Down Expand Up @@ -586,6 +588,22 @@ procedure TMainForm.Edit2KeyPress(Sender: TObject; var Key: Char);
end;
end;

{
Called after each keystroke while editing the Exch2 field (Edit3).
Allows special processing to occur for certain contests.
}
procedure TMainForm.Edit3KeyUp(Sender: TObject; var Key: Word;
Shift: TShiftState);
begin
// some contests have additional processing (e.g. ARRL SS)
// (exclude function keys so we can use the debugger)
var ExchSummary: string;
if (SimContest in [scArrlSS]) and ((Key < VK_F1) or (Key > VK_F12)) then
begin
Tst.OnExchangeEdit(Edit1.Text, Edit2.Text, Edit3.Text, ExchSummary);
end;
end;

procedure TMainForm.Edit3Enter(Sender: TObject);
begin
Edit3.SelStart := 0;
Expand Down Expand Up @@ -736,6 +754,9 @@ procedure TMainForm.FormKeyPress(Sender: TObject; var Key: Char);
Exit;
end;

// some contests have additional exchange processing (e.g. ARRL SS)
Tst.OnExchangeEditComplete; // sets Log.CallSent

if not CallSent then
SendMsg(msgHisCall);
SendMsg(msgTU);
Expand Down Expand Up @@ -945,6 +966,9 @@ procedure TMainForm.ProcessEnter;
Exit;
end;

// Update CallSent (HisCall has been sent)
Tst.OnExchangeEditComplete;

//current state
C := CallSent;
N := NrSent; // 'Nr' represents the exchange (<exch1> <exch2>).
Expand Down Expand Up @@ -1668,6 +1692,9 @@ procedure TMainForm.WipeBoxes;
Edit3.Text := '';
ActiveControl := Edit1;

if Assigned(Tst) then
Tst.OnWipeBoxes;

CallSent := false;
NrSent := false;
end;
Expand Down
2 changes: 1 addition & 1 deletion Test/SSExchParserTest.pas
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ TestTSSExchParser = class
[Category('Simple')]

[TestCase('Simple.1', '1, 1...-Missing/Invalid Precedence')]
[TestCase('Simple.2', '12, 0..12.-Missing/Invalid Serial NUmber')]
[TestCase('Simple.2', '12, 0..12.-Missing/Invalid Serial Number')]
[TestCase('Simple.3', '123, 123...-Missing/Invalid Precedence')]
[TestCase('Simple.4', '1234, 1234...-Missing/Invalid Precedence')]
[TestCase('Simple.5', '11 22, 11..22.')] // rotate to NR
Expand Down
32 changes: 31 additions & 1 deletion Util/SSExchParser.pas
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ TSSExchParser = class
protected
IsValidExchange: Boolean;
Lexer: TSSLexer;
PreviousCall: String;
PreviousCall: String; // copy of user-entered call (Edit1)
Tokens: TObjectList<TSSExchToken>;
TwoDigitList: TList<TSSExchToken>; // insert each new 2digit token into this list (mru at 0)
CheckTokenstack: TStack<TSSExchToken>; // each new check token is pushed
Expand All @@ -79,6 +79,7 @@ TSSExchParser = class
SectionToken: TSSExchToken;
FExchError: String; // most recent exchange parsing error
procedure Reset;
function GetExchSummary: String;

public
NR: Integer;
Expand All @@ -87,10 +88,12 @@ TSSExchParser = class
Section: String;
Call: String;
property ExchError: String read FExchError;
property ExchSummary: String read GetExchSummary;

constructor Create;
destructor Destroy; override;

procedure OnWipeBoxes;
function ValidateEnteredExchange(const ACall, AExch1, AExch2: string;
out AExchError: String) : boolean;
end;
Expand Down Expand Up @@ -330,6 +333,31 @@ procedure TSSExchParser.Reset;
IsValidExchange := False;
end;


{
Exchange summary shows parsed result; displayed as an updated Caption.
Example: '192A W7SST 72 OR'
}
function TSSExchParser.GetExchSummary: String;
begin
if Assigned(NRToken) or
Assigned(CheckToken) or
Assigned(PrecedenceToken) or
Assigned(SectionToken) or
not Call.IsEmpty then
Result := format('%d%s %s %s %s', [NR, Precedence,
IfThen(Call.IsEmpty, PreviousCall, Call), Check, Section])
else
Result := '';
end;


procedure TSSExchParser.OnWipeBoxes;
begin
Reset;
end;


function TSSExchParser.ValidateEnteredExchange(const ACall, AExch1, AExch2: string;
out AExchError: String) : boolean;
var
Expand Down Expand Up @@ -499,8 +527,10 @@ function TSSExchParser.ValidateEnteredExchange(const ACall, AExch1, AExch2: stri
begin
if TwoDigitList.Count > 0 then
begin
{$ifdef DEBUG}
assert(Assigned(NRToken));
assert(CheckToken = TwoDigitList.Last);
{$endif}
CheckToken := TwoDigitList.Last;
CheckTokenstack.Push(CheckToken);
end
Expand Down

0 comments on commit 2c905ec

Please sign in to comment.