diff --git a/ArrlFd.pas b/ArrlFd.pas index 8b1799f..622b5f4 100644 --- a/ArrlFd.pas +++ b/ArrlFd.pas @@ -96,8 +96,6 @@ function TArrlFieldDay.LoadCallHistory(const AUserCallsign : string) : boolean; end; end; - // retain user's callsign after successful load - SetUserCallsign(AUserCallsign); Result := True; finally diff --git a/CWOPS.pas b/CWOPS.pas index c7c4e66..2b14295 100644 --- a/CWOPS.pas +++ b/CWOPS.pas @@ -80,8 +80,6 @@ function TCWOPS.LoadCallHistory(const AUserCallsign : string) : boolean; end; end; - // retain user's callsign after successful load - SetUserCallsign(AUserCallsign); Result := True; finally diff --git a/Contest.pas b/Contest.pas index 329a410..ba8c7f0 100644 --- a/Contest.pas +++ b/Contest.pas @@ -8,21 +8,21 @@ interface uses - SysUtils, SndTypes, Station, StnColl, MyStn, Math, Ini, System.Classes, - MovAvg, Mixers, VolumCtl, RndFunc, TypInfo, DxStn, DxOper, Log; + SndTypes, Station, StnColl, MyStn, Ini, System.Classes, + MovAvg, Mixers, VolumCtl, DxStn; type TContest = class private - UserCallsign : String; // used by LoadCallHistory() to minimize reloads + LastLoadCallsign : String; // used to minimize call history file reloads function DxCount: integer; procedure SwapFilters; protected constructor Create; - function HasUserCallsignChanged(const AUserCallsign : String) : boolean; - procedure SetUserCallsign(const AUserCallsign : String); + function IsReloadRequired(const AUserCallsign : String) : boolean; + procedure SetLastLoadCallsign(const AUserCallsign : String); public BlockNumber: integer; @@ -36,7 +36,7 @@ TContest = class destructor Destroy; override; procedure Init; - function LoadCallHistory(const AHomeCallsign : string) : boolean; virtual; abstract; + function LoadCallHistory(const AUserCallsign : string) : boolean; virtual; abstract; function PickStation : integer; virtual; abstract; procedure DropStation(id : integer); virtual; abstract; @@ -45,6 +45,9 @@ TContest = class function GetStationInfo(const ACallsign : string) : string; virtual; function PickCallOnly : string; + function OnSetMyCall(const AUserCallsign : string; out err : string) : boolean; virtual; + function OnContestPrepareToStart(const AUserCallsign: string; + const ASentExchange : string) : Boolean; virtual; function GetSentExchTypes( const AStationKind : TStationKind; const AMyCallsign : string) : TExchTypes; @@ -52,6 +55,10 @@ TContest = class const AStationKind : TStationKind; const AMyCallsign : string; const ADxCallsign : string) : TExchTypes; + function GetExchangeTypes( + const AStationKind : TStationKind; + const AMsgType : TRequestedMsgType; + const ADxCallsign : string) : TExchTypes; virtual; function Minute: Single; function GetAudio: TSingleArray; procedure OnMeFinishedSending; @@ -65,6 +72,7 @@ TContest = class implementation uses + SysUtils, RndFunc, Math, DxOper, Log, Main, CallLst, ARRL; { TContest } @@ -96,7 +104,7 @@ constructor TContest.Create; Agc.HoldSamples := 155; Agc.AgcEnabled := true; NoActivityCnt :=0; - UserCallsign := ''; + LastLoadCallsign := ''; Init; end; @@ -119,21 +127,26 @@ procedure TContest.Init; Me.Init; Stations.Clear; BlockNumber := 0; - UserCallsign := ''; + LastLoadCallsign := ''; end; -{ return whether to call history file is valid based on user's callsign. } -function TContest.HasUserCallsignChanged(const AUserCallsign : string) : boolean; +{ + user's home callsign is required when loading some contests + (don't load if user callsign is empty or is the same as last time). + + return whether the call history file is valid. This varies by contest. +} +function TContest.IsReloadRequired(const AUserCallsign : string) : boolean; begin - // user's home callsign is required to load this contest. - Result := (not AUserCallsign.IsEmpty) and (UserCallsign <> AUserCallsign); + Result := not (AUserCallsign.IsEmpty or (LastLoadCallsign = AUserCallsign)); end; -procedure TContest.SetUserCallsign(const AUserCallsign : String); +// called by LoadCallHistory after loading the call history file. +procedure TContest.SetLastLoadCallsign(const AUserCallsign : String); begin - UserCallsign := AUserCallsign; + LastLoadCallsign := AUserCallsign; end; @@ -159,15 +172,67 @@ function TContest.PickCallOnly : string; end; +{ + OnSetMyCall() is called whenever the user's callsign is set. + Can be overriden by derived classes as needed to update contest-specific + settings. Note that derived classes should update contest-specific + settings before calling this function since the Sent Exchange settings + may depend upon this contest-specific information. + + Returns whether the call was successful. +} +function TContest.OnSetMyCall(const AUserCallsign : string; out err : string) : boolean; +begin + Me.MyCall:= AUserCallsign; + + // update my sent exchange field types + Me.SentExchTypes:= GetSentExchTypes(skMyStation, AUserCallsign); + + Result:= True; +end; + + +{ + OnContestPrepareToStart() event is called whenever a contest is started. + Some contests will override this method to provide additional contest-specfic + behaviors. When overriding this function, be sure to call this base-class + function. + + Current behavior is to load the call history file. This action has been + defferred until now since some contests use the user's callsign to determine + which stations can work other stations in the contest. For example, in the + ARRL DX Contest, US/CA Stations work DX (non-US/CA) stations. + + Returns whether the operation was successfull. +} +function TContest.OnContestPrepareToStart(const AUserCallsign: string; + const ASentExchange : string) : Boolean; +begin + // reload call history iff user's callsign has changed. + if IsReloadRequired(AUserCallsign) then + begin + // load contest-specific call history file + Result:= LoadCallHistory(AUserCallsign); + + // retain user's callsign after successful load + if Result then + SetLastLoadCallsign(AUserCallsign); + end + else + Result:= True; +end; + + { Return sent dynamic exchange types for the given kind-of-station and callsign. + AStationKind represents either the user's station (representing current + simulation) or the DxStn represented a simulated station calling the user. } function TContest.GetSentExchTypes( const AStationKind : TStationKind; const AMyCallsign : string) : TExchTypes; begin - Result.Exch1 := ActiveContest.ExchType1; - Result.Exch2 := ActiveContest.ExchType2; + Result:= Self.GetExchangeTypes(AStationKind, mtSendMsg, {ADxCallsign=}''); end; @@ -180,6 +245,16 @@ function TContest.GetRecvExchTypes( const AStationKind : TStationKind; const AMyCallsign : string; const ADxCallsign : string) : TExchTypes; +begin + // perhaps need to pass in AUserCallsign instead of using TArrlDx.HomeCallIsWVE + Result:= Self.GetExchangeTypes(AStationKind, mtRecvMsg, ADxCallsign); +end; + + +function TContest.GetExchangeTypes( + const AStationKind : TStationKind; + const AMsgType : TRequestedMsgType; + const ADxCallsign : string) : TExchTypes; begin Result.Exch1 := ActiveContest.ExchType1; Result.Exch2 := ActiveContest.ExchType2; diff --git a/CqWW.pas b/CqWW.pas index 159f749..04e8b5d 100644 --- a/CqWW.pas +++ b/CqWW.pas @@ -88,8 +88,6 @@ function TCqWw.LoadCallHistory(const AUserCallsign : string) : boolean; end; end; - // retain user's callsign after successful load - SetUserCallsign(AUserCallsign); Result := True; finally diff --git a/CqWpx.pas b/CqWpx.pas index f316d7c..f6564d3 100644 --- a/CqWpx.pas +++ b/CqWpx.pas @@ -47,8 +47,6 @@ function TCqWpx.LoadCallHistory(const AUserCallsign : string) : boolean; CallLst.LoadCallList; - // retain user's callsign after successful load - SetUserCallsign(AUserCallsign); Result := True; end; diff --git a/Ini.pas b/Ini.pas index c92987a..694d9b5 100644 --- a/Ini.pas +++ b/Ini.pas @@ -49,6 +49,9 @@ TContestDefinition = record PContestDefinition = ^TContestDefinition; const + UndefExchType1 : TExchange1Type = TExchange1Type(-1); + UndefExchType2 : TExchange2Type = TExchange2Type(-1); + { Each contest is declared here. Long-term, this will be a generalized table-driven implementation allowing new contests to be configured diff --git a/Main.dfm b/Main.dfm index 1af3f6e..63b6b8c 100644 --- a/Main.dfm +++ b/Main.dfm @@ -786,6 +786,7 @@ object MainForm: TMainForm TabOrder = 0 Text = 'VE3NEA' OnChange = Edit4Change + OnExit = Edit4Exit end object SpinEdit1: TSpinEdit Left = 91 diff --git a/Main.pas b/Main.pas index 4085737..342abc6 100644 --- a/Main.pas +++ b/Main.pas @@ -12,12 +12,12 @@ interface uses - Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs, - Buttons, SndCustm, SndOut, Contest, Ini, MorseKey, CallLst, - VolmSldr, VolumCtl, StdCtrls, Station, Menus, ExtCtrls, MAth, - ComCtrls, Spin, SndTypes, ShellApi, jpeg, ToolWin, ImgList, Crc32, - WavFile, IniFiles, Idhttp, - System.ImageList; + Windows, Messages, Classes, Graphics, Controls, Forms, + Buttons, SndCustm, SndOut, Contest, Ini, + VolmSldr, VolumCtl, StdCtrls, Station, Menus, ExtCtrls, + ComCtrls, Spin, SndTypes, + WavFile, + System.ImageList, Vcl.ToolWin, Vcl.ImgList; const WM_TBDOWN = WM_USER+1; @@ -349,13 +349,13 @@ TMainForm = class(TForm) procedure mnuShowCallsignInfoClick(Sender: TObject); procedure SimContestComboChange(Sender: TObject); procedure ExchangeEditExit(Sender: TObject); + procedure Edit4Exit(Sender: TObject); private MustAdvance: boolean; + UserCallsignDirty: boolean; // SetMyCall is called after callsign edits function CreateContest(AContestId : TSimContest) : TContest; - procedure ConfigureExchangeFields( - AExchType1: TExchange1Type; - AExchType2: TExchange2Type); + procedure ConfigureExchangeFields; procedure SetMyExch1(const AExchType: TExchange1Type; const Avalue: string); procedure SetMyExch2(const AExchType: TExchange2Type; const Avalue: string); function ValidateExchField(const FieldDef: PFieldDefinition; @@ -375,7 +375,7 @@ TMainForm = class(TForm) // Received Exchange information is contest-specific and depends on contest, // user's QTH/location, DX station's QTH/location, and whether the user's - // station is local/DX relative to the contest. + // simulated station is local/DX relative to the contest. // This value is set by calling the virtual TContest.GetSentExchTypes() // function. RecvExchTypes: TExchTypes; @@ -413,7 +413,9 @@ implementation uses ARRL, ARRLFD, NAQP, CWOPS, CQWW, CQWPX, - TypInfo, ScoreDlg, Log, PerlRegEx; + MorseKey, CallLst, + SysUtils, ShellApi, Crc32, Idhttp, Math, IniFiles, + Dialogs, System.UITypes, TypInfo, ScoreDlg, Log, PerlRegEx, StrUtils; {$R *.DFM} @@ -789,6 +791,11 @@ procedure TMainForm.ProcessEnter; ExchangeEditExit(ActiveControl); Exit; end; + if ActiveControl = Edit4 then + begin + Edit4Exit(ActiveControl); + Exit; + end; MustAdvance := false; if (GetKeyState(VK_CONTROL) or GetKeyState(VK_SHIFT) or GetKeyState(VK_MENU)) < 0 then @@ -859,7 +866,15 @@ procedure TMainForm.DecSpeed; procedure TMainForm.Edit4Change(Sender: TObject); begin - SetMyCall(Trim(Edit4.Text)); + // user callsign edit has occurred; allows SetMyCall to be called. + UserCallsignDirty := True; +end; + +procedure TMainForm.Edit4Exit(Sender: TObject); +begin + // call SetMyCall if the callsign has been edited + if UserCallsignDirty then + SetMyCall(Trim(Edit4.Text)); end; procedure TMainForm.ExchangeEditExit(Sender: TObject); @@ -902,7 +917,12 @@ procedure TMainForm.SetContest(AContestNum: TSimContest); // the following will initialize simulation-specific data owned by contest. // (moved here from Ini.FromIni) begin - SetMyCall(Ini.Call); + // set contest-specific Sent Exchange field prior to calling SetMyCall(). + // UI assumes uppercase only, so convert .ini file data to uppercase. + ExchangeEdit.Text := UpperCase(Ini.UserExchangeTbl[SimContest]); + + // set user's call - also calls SetMyExchange and ConfigureExchangeFields. + SetMyCall(UpperCase(Ini.Call)); SetPitch(ComboBox1.ItemIndex); SetBw(ComboBox2.ItemIndex); SetWpm(Ini.Wpm); @@ -912,16 +932,9 @@ procedure TMainForm.SetContest(AContestNum: TSimContest); assert(Tst.Filt.SamplesInInput = Ini.BufSize); assert(Tst.Filt2.SamplesInInput = Ini.BufSize); - // load contest-specific call history file - if not Tst.LoadCallHistory(Ini.Call) then - Exit; - - // update my sent exchange types (must be called after loading call lists?) - Tst.Me.SentExchTypes:= Tst.GetSentExchTypes(skMyStation, Ini.Call); + // my sent exchange set by SetMyCall() above + assert(Tst.Me.SentExchTypes = Tst.GetSentExchTypes(skMyStation, Ini.Call)); end; - - // update Exchange field labels and length settings (e.g. RST, Nr.) - ConfigureExchangeFields(ActiveContest.ExchType1, ActiveContest.ExchType2); end; {procedure TMainForm.SetNumber(ANumber: string); @@ -934,6 +947,9 @@ procedure TMainForm.SetContest(AContestNum: TSimContest); { Set my "sent" exchange fields using the exchange string containing two values, separated by a space. Error/warning messages are displayed in the status bar. + + My "sent" exchange types (Tst.Me.SentExchTypes) have been previously set by + SetMyCall(). } procedure TMainForm.SetMyExchange(const AExchange: string); var @@ -944,8 +960,9 @@ procedure TMainForm.SetMyExchange(const AExchange: string); begin sl:= TStringList.Create; try - SentExchTypes:= Tst.GetSentExchTypes(skMyStation, Ini.Call); - assert(Tst.Me.SentExchTypes = SentExchTypes, 'this is already set; above call not necessary'); + assert(Tst.Me.SentExchTypes = Tst.GetSentExchTypes(skMyStation, Ini.Call), + 'set by TMainForm.SetMyCall'); + SentExchTypes := Tst.Me.SentExchTypes; Field1Def := @Exchange1Settings[SentExchTypes.Exch1]; Field2Def := @Exchange2Settings[SentExchTypes.Exch2]; @@ -956,7 +973,7 @@ procedure TMainForm.SetMyExchange(const AExchange: string); if sl.Count = 1 then sl.AddStrings(['']); - // validate exchange string + // validate sent exchange strings if not ValidateExchField(Field1Def, sl[0]) or not ValidateExchField(Field2Def, sl[1]) then begin @@ -966,7 +983,10 @@ procedure TMainForm.SetMyExchange(const AExchange: string); sbar.Align:= alBottom; sbar.Visible:= true; sbar.Font.Color := clRed; + + // update the Sent Exchange field value ExchangeEdit.Text := AExchange; + Ini.UserExchangeTbl[SimContest]:= AExchange; exit; end else @@ -976,12 +996,12 @@ procedure TMainForm.SetMyExchange(const AExchange: string); sbar.Caption := ''; end; - // set contest-specific exchange values + // set contest-specific sent exchange values SetMyExch1(SentExchTypes.Exch1, sl[0]); SetMyExch2(SentExchTypes.Exch2, sl[1]); assert(Tst.Me.SentExchTypes = SentExchTypes); - // update the Exchange field value + // update the Sent Exchange field value ExchangeEdit.Text := AExchange; Ini.UserExchangeTbl[SimContest]:= AExchange; @@ -1004,21 +1024,53 @@ procedure TMainForm.UpdateTitleBar; procedure TMainForm.SetMyCall(ACall: string); +var + err : string; begin Ini.Call := ACall; Edit4.Text := ACall; Tst.Me.MyCall := ACall; + + // some contests have contest-specific settings (e.g location local/dx). + // sets Tst.Me.SentExchTypes. + if not Tst.OnSetMyCall(ACall, err) then + begin + MessageDlg(err, mtError, [mbOK], 0); + Exit; + end; + assert(Tst.Me.SentExchTypes = Tst.GetSentExchTypes(skMyStation, ACall)); + + // update my "sent" exchange information. + // depends on: contest, my call, sent exchange (ExchangeEdit). + // SetMyExchange() may report an error in the status field. + SetMyExchange(Trim(ExchangeEdit.Text)); + + // update "received" Exchange field types, labels and length settings + // (e.g. RST, Nr.). depends on: contest, my call and dx station's call. + ConfigureExchangeFields; + + UserCallsignDirty := False; end; { - Exchange Field types are determined by each contest. - Exchange field labels and exchange field maximum length are set. - Prior field values from .INI file are applied. - This procedure is called by SetContest() whenever the contest changes. + Received Exchange Field types are defined by each contest. + Exchange Field types can also dynamically change for various contests: + - ARRL DX: Exchange 2 changes between etStateProv and etPower. + - ARRL 10m: Exchange 2 changes between etStateProv10m, etIARU, etSerial, + depending on sending station's callsign. + + Received exchange field labels and exchange field maximum length are set. + + This procedure is called whenever: + a) the contest changes by SetContest(). + b) when DxStation's callsign is entered (dynamic during contest). + + Note: using DxStations's callsign can be eliminated by using ASCII + exchange fields and not applying semantics until the log entry is + constructed and compared. This may simplify how dynamic exchange field + types are handled. } -procedure TMainForm.ConfigureExchangeFields( - AExchType1: TExchange1Type; - AExchType2: TExchange2Type); +procedure TMainForm.ConfigureExchangeFields; const { the logic below allows Exchange label to be optional. If necessary, move this value into ContestDefinitions[] table. } @@ -1028,6 +1080,9 @@ procedure TMainForm.ConfigureExchangeFields( Visible: Boolean; begin + // Load Received exchange field types + RecvExchTypes:= Tst.GetRecvExchTypes(skMyStation, Tst.Me.MyCall, Trim(Edit1.Text)); + // Optional Contest Exchange label and field Visible := AExchangeLabel <> ''; Label17.Visible:= Visible; @@ -1038,26 +1093,20 @@ procedure TMainForm.ConfigureExchangeFields( ExchangeEdit.Enabled := ActiveContest.ExchFieldEditable; // setup Exchange Field 1 (e.g. RST) - assert(AExchType1 = TExchange1Type(Exchange1Settings[AExchType1].T), + assert(RecvExchTypes.Exch1 = TExchange1Type(Exchange1Settings[RecvExchTypes.Exch1].T), Format('Exchange1Settings[%d] ordering error: found %s, expecting %s.', - [Ord(AExchType1), ToStr(AExchType1), - ToStr(TExchange1Type(Exchange1Settings[AExchType1].T))])); - Label2.Caption:= Exchange1Settings[AExchType1].C; - Edit2.MaxLength:= Exchange1Settings[AExchType1].L; - RecvExchTypes.Exch1 := AExchType1; + [Ord(RecvExchTypes.Exch1), ToStr(RecvExchTypes.Exch1), + ToStr(TExchange1Type(Exchange1Settings[RecvExchTypes.Exch1].T))])); + Label2.Caption:= Exchange1Settings[RecvExchTypes.Exch1].C; + Edit2.MaxLength:= Exchange1Settings[RecvExchTypes.Exch1].L; // setup Exchange Field 2 (e.g. Serial #) - assert(AExchType2 = TExchange2Type(Exchange2Settings[AExchType2].T), + assert(RecvExchTypes.Exch2 = TExchange2Type(Exchange2Settings[RecvExchTypes.Exch2].T), Format('Exchange2Settings[%d] ordering error: found %s, expecting %s.', - [Ord(AExchType2), ToStr(AExchType2), - ToStr(TExchange2Type(Exchange2Settings[AExchType2].T))])); - Label3.Caption := Exchange2Settings[AExchType2].C; - Edit3.MaxLength := Exchange2Settings[AExchType2].L; - RecvExchTypes.Exch2 := AExchType2; - - // Set my exchange value (from INI file) - // UI assumes uppercase only, so convert .ini files to uppercase. - SetMyExchange(UpperCase(Ini.UserExchangeTbl[SimContest])); + [Ord(RecvExchTypes.Exch2), ToStr(RecvExchTypes.Exch2), + ToStr(TExchange2Type(Exchange2Settings[RecvExchTypes.Exch2].T))])); + Label3.Caption := Exchange2Settings[RecvExchTypes.Exch2].C; + Edit3.MaxLength := Exchange2Settings[RecvExchTypes.Exch2].L; end; procedure TMainForm.SetMyExch1(const AExchType: TExchange1Type; @@ -1139,7 +1188,7 @@ procedure TMainForm.SetMyExch2(const AExchType: TExchange2Type; end; etStateProv: // e.g. NAQP (OR) begin - // 'expecting State or Providence (e.g. OR)' + // 'expecting State or Province (e.g. OR)' Ini.UserExchange2[SimContest] := Avalue; Tst.Me.Exch2 := Avalue; if BDebugExchSettings then Edit3.Text := Avalue; // testing only @@ -1379,6 +1428,23 @@ procedure TMainForm.Run(Value: TRunMode); if Value = Ini.RunMode then Exit; + if Value <> rmStop then + begin + { + consider special case of click Run while focus in CallSign or Exchange + fields. + + clicking in the Run button does not generate an OnExit event for the + Callsign nor Exchange fields until after the Run button has been processed. + } + if UserCallsignDirty then + SetMyCall(Trim(Edit4.Text)); + + // load call history and other contest-specific setup before starting + if not Tst.OnContestPrepareToStart(Ini.Call, ExchangeEdit.Text) then + Exit; + end; + BStop := Value = rmStop; BCompet := Value in [rmWpx, rmHst]; RunMode := Value; diff --git a/MyStn.pas b/MyStn.pas index 4489649..3945d28 100644 --- a/MyStn.pas +++ b/MyStn.pas @@ -59,8 +59,8 @@ procedure TMyStation.Init; Wpm := Ini.Wpm; Amplitude := 300000; - // My sent exchange types depends on my callsign - SentExchTypes:= Tst.GetSentExchTypes(skMyStation, MyCall); + // invalidate SentExchTypes. Will be set by Tst.OnSetMyCall(). + SentExchTypes := ExchTypesUndef; // Adding a contest: Initialize Exch1 and Exch2 // (try to use the generalized Exch1 and Exch2 fields for new contests.) diff --git a/NaQp.pas b/NaQp.pas index c34f78b..e7cc6e6 100644 --- a/NaQp.pas +++ b/NaQp.pas @@ -91,8 +91,6 @@ function TNcjNaQp.LoadCallHistory(const AUserCallsign : string) : boolean; end; end; - // retain user's callsign after successful load - SetUserCallsign(AUserCallsign); Result := True; finally diff --git a/Station.pas b/Station.pas index 3565a65..0ce7bfa 100644 --- a/Station.pas +++ b/Station.pas @@ -93,6 +93,12 @@ TStation = class (TCollectionItem) property Bfo: Single read GetBfo; end; +const + ExchTypesUndef : TExchTypes = ( + Exch1: TExchange1Type(-1); + Exch2: TExchange2Type(-1); + ); + implementation uses @@ -113,8 +119,7 @@ constructor TStation.CreateStation; begin inherited Create(nil); - SentExchTypes.Exch1:= TExchange1Type(-1); - SentExchTypes.Exch2:= TExchange2Type(-1); + SentExchTypes:= ExchTypesUndef; end;