{********************************************************************
 ***                                                              ***
 ***                      MODEM TOOLS                             ***
 ***                                                              ***
 ********************************************************************}
{Provides a device, descended from TSerialDevice, with extra fields/methods
for handling modems, and a simple Dial command for easily calling a dial-up
procedure}
{$I compflgs}

unit modems;

INTERFACE

uses comport,
		objects, {for stxxx constants}
		scodes,
		tuiedit, files;

const
	ModemDeviceExt = 'MDM';

procedure Dial(TelNum : string; TargetCountry : TSCode);

type
	PModem = ^TModem;
	TModem = object(TSerialDevice)

		InitString 		: string[40];	{initialisation string}
		GetNameString : string[20];
		PreDial 			: string[20];
		PostDial 			: string[20];
		HangupString 	: string[20];

		LastMsg : string[20]; {last message returned from, eg initialise, etc}

		procedure CommonInit; virtual;
		constructor Load(var S : TDataStream);
		procedure Store(var S : TDataStream);

		procedure AddEditFields(EditBox : PEditBox); virtual;

		procedure Initialise; virtual;
		function GetName : string; virtual; {returns from getnamestring}

		procedure HangUp;
		procedure Dial(TelNum : string);

		procedure Flush; virtual;
	end;


function EditModemDevice(var FullFileName : FNameStr; const Ext : string) : word;

IMPLEMENTATION

uses
{$IFDEF WINDOWS}
		wui,	{windows}
{$ELSE}
		app,	views, tuimsgs, dialogs, {text}
{$ENDIF}
		tasks,
		devices,
		help,
		global,
		kamsetup, {which modem}
		menus,
		tui,
		autodial,
		minilib;

{Dealing with errors - see p474 of the DOS programmers ref}

{**********************************************************
 ***                                                    ***
 ***                   MODEM SETUP                      ***
 ***                                                    ***
 **********************************************************}
procedure TModem.CommonInit;
begin
	inherited CommonInit;
	LastMsg := '';
end;

constructor TModem.Load(var S : TDataStream);
var Ver : byte;
begin
	S.Read(Ver, 1);
	case Ver of
		1 : begin
			inherited Load(S);

			InitString 		:=S.REadStr;
			GetNameString :=S.REadStr;
			PreDial 			:=S.REadStr;
			PostDial 			:=S.REadStr;
			HangupString 	:=S.REadStr;
		end;
	else
		ProgramError('Ver '+N2Str(Ver)+' not understood'#13'TModemDevice.Load',hcInternalErrorMsg);
		fail;
	end;
end;

procedure TModem.Store(var S : TDataStream);
var Ver: byte;
begin
	Ver := 1; S.Write(Ver,1);
	inherited Store(S);
	S.WriteStr(@InitString);
	S.WriteStr(@GetNameString);
	S.WriteStr(@PreDial);
	S.WriteStr(@PostDial);
	S.WriteStr(@HangUpString);
end;

{*********************************************
 ***         EDIT                          ***
 *********************************************}
type
	{Radio buttons for ordinal types - single byte data}
	POrdButtons = ^TOrdButtons;
	TOrdButtons = object(TERadioButtons)
		procedure GetData(var Rec); virtual;
		procedure SetData(var Rec); virtual;
		function DataSize : word; virtual;
	end;

procedure TOrdButtons.GetData;
var W : word;
begin
	inherited GetData(W);
	byte(Rec) := W;
end;

procedure TOrdButtons.SetData;
var W : word;
begin
	W := byte(Rec);
	inherited SetData(W);
end;

function TOrdButtons.DataSize;
begin DataSize := 1; end;

type
	PTestButton = ^TTestButton;
	TTestButton = object(TOurButton)
		procedure Press; virtual;
	end;

procedure TTestButton.Press;
var Modem : PModem;
		StatusBox : PComStatusWindow;

begin
	{tests modem}
	DrawState(True);

	New(Modem, init('Test Modem',1,1200));
	Owner^.GetData(Modem^);

	New(StatusBox, init(1,1,50,10,'MODEM TEST STATUS'));
	StatusBox^.Options := StatusBox^.Options and not ofSelectable;
	Desktop^.Insert(StatusBox);
	Modem^.Terminal := StatusBox^.Terminal;

	Modem^.Open;
	Modem^.Initialise;
	Modem^.GetName;

	if Modem^.Status = stOK then
		MessageBox('MODEM TEST', 'All Seems OK',mfOKButton, hcNoContext)
	else
		MessageBox('MODEM TEST', Modem^.StatusText,mfWarning+mfOKButton, hcNoContext);

	dispose(Modem, done);
	dispose(StatusBox, done);

	DrawState(False);
	Owner^.Focus;
	Owner^.DrawView;
end;





procedure TModem.AddEditFields;
var R : TRect;
begin
	with EditBox^ do begin
		GrowTo(38,20);
		HelpCtx := hcEditModem;

		{device fields}
{		InsTitledField(20,  1,20, 1, 'Modem ~N~ame', New(PInputELine, init(R, 20)));{}
		Insert(New(PSkipBytes, init(sizeof(TDeviceStream)-DataSize))); {skip rest}

		{serial port fields}
		InsTitledField(12,  3,18, 1, '~P~ort',
			New(POrdButtons, init(R, NewSitem('COM1',
																	NewSItem('COM2', nil)))));
		InsTitledField(12,  5,18, 4, '~B~aud',
			New(POrdButtons, init(R, NewSitem('110',
																	NewSItem('150',
																	NewSItem('300',
																	NewSItem('600',
																	NewSitem('1200',
																	NEwSItem('2400',
																	NewSItem('4800',
																	NewSitem('9600', nil)))))))))));

		InsTitledField(12, 10, 3, 1, 'TimeOut CH', New(PinputByte, Init(R, 3)));
		InsTitledField(21, 10, 3, 1, 'WF', New(PinputByte, Init(R, 3)));
		Insert(New(PSkipBytes, init(sizeof(TSerialDevice)-DataSize))); {skip rest}

		{modem strings}
		InsTitledField(12, 11,20, 1, '~I~nit''n', New(PInputELine, init(R, 40)));
		InsTitledField(12, 12,20, 1, 'Get Type', 	New(PInputELine, init(R, 20)));
		InsTitledField(12, 13,20, 1, 'Pre~D~ial', New(PInputELine, init(R, 20)));
		InsTitledField(12, 14,20, 1, 'PostDial', 	New(PInputELine, init(R, 20)));
		InsTitledField(12, 15,20, 1, '~H~ang up', New(PInputELine, init(R, 20)));

		Insert(New(PTestButton, init(2, 17, '~T~est', cmNone, bfNormal, nil)));

	end;
end;



{*********************************************
 ***             MODEM INSTRUCTIONS        ***
 *********************************************}
procedure TModem.Initialise;
begin
	if (InitString<>'') and (Status=stOK) then begin
		Writeln(InitString);

		Flush;

		if LastMsg = InitString then begin
			ChWait(ForRead);
			Flush;
		end;

		if LastMsg<>'OK' then begin
			Status := stInitError;
			if ErrorInfo=0 then ErrorInfo := ioBadResponse; {if not already an error set}
		end else
			IsInitialised := True;
	end;
end;

function TModem.GetName : string;
var S : string;
begin
	S := '';
	if (GetNameString<>'') and (Status=stOK) then begin
		Writeln(GetNameString);

		ChWait(ForRead); if CharReady then Readln; {reads line just written}

		{may be a blank line first, so...}
		ChWait(ForRead); if CharReady then S := S + Readln;
		ChWait(ForRead); if CharReady then S := S + Readln;
		Flush;

		if ((LastMsg<>'') and (LastMsg<>'OK')) or ((LastMsg='') and (S='ERROR')) then begin
			Status := stError;
			if ErrorInfo=0 then ErrorInfo := ioBadResponse; {if not already an error set}
			S := '';
		end;

	end;
	GetName := S;
end;

{======= HANG UP ================}
procedure TModem.HangUp;
begin
	Writeln(HangUpString);
	WaitFor(ForRead, 100); {wait for up to a second}
	Flush;
	if LastMsg = HangUpString then begin
		WaitFor(ForRead, 100); {wait for up to a second}
		Flush;
	end;
end;

{======== DIAL ==================}
procedure TModem.Dial(TelNum : string);
begin
	Writeln(Predial + delspace(TelNum) + PostDial);
end;


procedure TModem.Flush;
begin
	LastMsg := WaitToStop;
end;


{****************************************************************
 ***                 DIAL                                     ***
 ****************************************************************}



const NumSteps = 6;

var LastTelNum : string; {last one dialled}

procedure Dial(TelNum : string; TargetCountry : TSCode);
var	Result, S : string;
		Modem : PModem;
		StopWaiting : boolean;
		ProBox : PProgressBox;
		ModemName : string;
		MsgBox : PMessageBox;
		StatusBox : PComStatusWindow;
		I,L : integer;
		TelMenu : PMenu;
		MenuItem : PMenuItem;
		Control : word;

begin
	if TelNum='' then begin
		MessageBox('AutoDial','Nothing to dial!',mfOKButton+mfWarning, hcADNothing);
		exit;
	end;

	ModemName := ProgramSetup.Get(siDefaultModem,'');

	if ModemName = '' then begin
		MessageBox('AutoDial','No modem defined!'#13'Check Setup',mfOKButton+mfWarning, hcEditModem);
		exit;
	end;

	{============ GET TELEPHONE NUMBER ==============}
	TelNum := SplitTelNums(TelNum);
	L := 1;
	for I := 1 to (NumLines(TelNUm)-1) do {if it *does* match the last one, we'll want to set to 1 anyway}
		if (GetLine(TelNum,I) = LastTelNum) then L := I+1;

	{L is now default choice.  Let user select which}
	TelMenu := nil;
	for I := 1 to NumLines(TelNum) do begin
		MenuItem :=	NewItem(GetLine(TelNum, I),'', kbNone, I, hcADMenu, nil);
		AddItemEnd(TelMenu, MenuItem);
		if I=L then TelMenu^.Default := MenuItem;
	end;

	Control := DoPopUpMenu(TelMenu, Desktop);

	if (Control = 0) then exit;

	LastTelNum := GetLine(TelNum, Control);

	TelNum := MakeModemString(MakeLocalTelNum(MakeStdTelNum(LastTelNum,TargetCountry)));

	{for development}
	if MessageBox('AUTODIAL TEST','About to dial'+CRLF+TelNum,mfOKButton+mfCancelButton,0)=cmCancel then exit;{}

	Result := '';

	{--- Load Modem ------------------}
	Modem := PModem(GetDevice(PrintersPath+ModemName+'.'+ModemDeviceExt));

	if Modem=nil then begin
		MessageBox('AutoDial','Could not load modem '+ModemName, mfCancelButton+mfWarning, hcADNoLoadModem);
		exit;
	end;

	New(StatusBox, init(1,1,50,10, 'STATUS'));
	Modem^.Terminal := StatusBox^.Terminal;
	Desktop^.Insert(StatusBox);

	ProBox := NewProgressBox('AutoDial','',mfCancelButton, hcAutoDial); {do after above so it's on top}
	{try and make sure that all updates have three lines - keeps it steady}
	ProBox^.DisplayOptions := bpBar; {switch off all except bar}

	{--- Initialise serial port ------}
	ProBox^.Update(#13'Initialising Serial Port '+N2Str(ord(Modem^.Port))+#13, 1,NumSteps);

	Modem^.Open;
	Modem^.Flush; {Clear out any gunk still waiting}

	{--- Initialise Modem ----}
	if (Modem^.Status=stOK) and (Modem^.InitString <> '') and (ProBox^.Command=cmOK) then begin
		ProBox^.Update(#13'Initialising Modem'#13,2,NumSteps);

		Modem^.Initialise;

		if Modem^.Status<>stOK then begin
			ProgramWarning('Problem Initialising'#13'Modem says '+Modem^.LastMsg, hcADInitError);
		end;

	end;

	{--- report type of modem ----}
	with ProBox^ do begin Gasp; Gasp; end; {check for escape}
	ModemName := '';
	if (Modem^.Status=stOK) and (Modem^.GetNameString<>'') and (ProBox^.Command=cmOK) then begin
		ProBox^.Update(#13'Getting Type'#13,3,NumSteps);

		ModemName := Modem^.GetName;
	end;

	{--- Dial -----------}
	with ProBox^ do begin Gasp; Gasp; end; {check for escape}
	if (Modem^.Status = stOK) and (ProBox^.Command=cmOK) then begin
			{--- All fine, so dial ---}
			ProBox^.Update(ModemName+#13+'Dialling '+TelNum+#13,5,NumSteps);

			Modem^.Dial(TelNum);

			ProBox^.Update(ModemName+#13
										+'Dialed '+TelNum+#13
										+'Wait for dialling to finish'#13
										+'Then pick up handset and press Enter',
										6, NumSteps);

			repeat
				if Modem^.CharReady then S := Modem^.Readln;

				if S = 'BUSY' 			then Result := 'Line Engaged';

				if S = 'NO CARRIER' then Result := 'Out of Time!  No Answer...';

				if Pos('CONNECT',S)>0 then	Result := 'Connected!? - Dialled Fax? Modem?';

				if Pos('NO DIAL TONE',S)>0 then Result :='No Dial Tone - Are you plugged in?';

				with ProBox^ do begin Gasp; Gasp; end; {check for escape}

			until (Modem^.Status<>stOK) or (ProBox^.Command<>cmOK) or (Result<>'');

	end else

		{Not OK to dial... abandoned/problem}
		if (ProBox^.Command=cmOK) then
			{Problem}
			if (Modem^.ErrorInfo = ioTimeOut) then
				{--- Some kind of timeout problem ---}
				Result := 'Time-out problem'#13'Increase Ch Timeout?'
			else
				{--- Other problem ----}
				Result := 'Modem '+Modem^.StatusText;


	ProBox^.DisableCommands([cmCancel]);
	ProBox^.Update(ModemName + #13'Disconnecting Modem'#13,0,0);

	if Result<>'' then begin
		MsgBox := NewMessageBox('AutoDial',Result,mfOKButton+mfWarning, hcAutoDial);
		Modem^.HangUp;
		Desktop^.ExecView(MsgBox); {make modal}
		dispose(MsgBox, done);
	end else
		Modem^.HangUp;

	ProBox^.EnableCommands([cmCancel]);

	dispose(Modem, Done);
	dispose(StatusBox, done);
	dispose(ProBox, done);
end;

{***************************************
 ***          EDIT MODEM             ***
 ***************************************}
function CreateMOdem(P : pointer) : pointer; far;
var Modem : PModem;
begin
	New(Modem, init('Modem',2, 2400));
	with Modem^ do begin
		GetNameString := 'ATi1';
		PreDial := 'ATDT'; {tone dial}
		HangupString := 'ATH0';
	end;
	CreateModem := Modem;
end;

function EditModemDevice(var FullFileName : FNameStr; const Ext : string) : word;
begin
	EditModemDevice := EditDevice(FullFileName, Ext, CreateModem);
end;

procedure SetModem; far;
var P : PDeviceStream;
begin
	P := nil;
	SetDevice(P, 'SELECT MODEM', ModemDeviceExt, siDefaultModem, EditModemDevice);
	if P<>nil then dispose(P, done);
end;

{*********************************************
 ***             REGISTRATION, ETC         ***
 *********************************************}
const
	RModem : TStreamRec = (
		ObjType : srModemDevice;
		VmtLink : Ofs(TypeOf(TModem)^);
		Load : @TModem.Load;
		Store : @TModem.Store
	);

begin
	RegisterType(RModem);
	LastTelNum := '';

	RegisterTask(DesktopTasks, cmSetModem, @SetModem);{}
end.
