//Mcmd.bcpl--command menu, error, and timeout procedures
//
Last edited: 2 January 1980

get "streams.d"
get "mdecl.d"
get "mcommon.d"

external [
// OS
Dvec; AddToZone; Allocate; Free; sysZone; keys
Resets; Endofs; Gets; ReadBlock; WriteBlock; Closes; CreateDiskStream
CallSwat; DoubleAdd; Timer; GotoLabel; CallersFrame; GotoFrame

// MINIT0
DtachFP; RunProgFP; MStatus; FirstStatic; LastStatic; NStatics
Storage; EndStorage; StateBlock; StatePtr; StateEnd

// MIDAS
MidasSwat; StartTimer; TimerGoing; Initialized; BegCF; TopFrame
Resume; StateFileSize; @NestedCFiles

// MASM
Wss; @WssCSS; PutsCSS; ResetsCSS
DummyCall1; ErrorProtect; @AbortLabel; @AbortFrame

// MIOC
Wns

// MTXTBUF
ClearInText; InputStream; InputTextBuffer; TxtBNewChar

// MOVERLAY
KillOverlays; FlushOverlays; PeelOverlay
OverlayZone; OverlayFlushed

// MSYM
StreamFromTextName

// MDISP
MakeDispZoneAvail; SetDisplay; Blink; DisplayOff

// MRGN
DriverLoop; UpdateDisplay; RemoveFromEveryTimeList
ErrorFlag; AllowedRgn; PaintRgnLine

// MMENU
CreateAction; ForgetTemporaryActions; ExecuteTextCmdStream
FormMenu; DoTextAction; CommandCount; SkipCommandCount; CFileStream
@WsMarkA; WssMark; ThisAction

// MGO
@QuitF

// MCMDOV
TextCmdOutStream

// xxACTIONS
CmdDumpAct; CmdStartWCAct; CmdStopWCAct; CmdShowCmdAct
CmdConcealAct; CmdErrorContinueAct; CmdErrorAbortAct
CmdCFileAbortAct
LastAction; FirstCmdAction; LastCmdAction; TimeTimeOut
HWCFActions; NHWCFActions

// Machine dependent
FormHWMenu; DetachHardware

//Defined here
WnsCSS; WnsCSSD; WnsCS1; WnsCS1D; CmdDoRC; RunProgMenu; ReadCMenu
FormCmdMenu; ShowActions; DisplayError; ErrorExit; ErrorAbort
ConfirmAct; StartCmdOverlay; StartLargeOverlay; QuitCmdOverlay
SetAbort; SetAbortPure; CmdAbort; CmdWriteState; RestoreState
TEveryTime; TimeOutEveryTime
CmdCommentStream; CmdCS1; CFOutStream
SavedLoadText; Confirmed; LoadDone; ShowActionForm
CmdMDFS; CmdAltMenuP; @CmdAbortAct; PassiveOnly

// Defined here for init only
DoOverlay; EndError; CmdRunProg; ExecuteCFile
ProgramAct; NPrograms; ReadCAct; NReadCFiles
]

static
[
CmdMDFS; CmdCommentStream; CmdCS1; CmdEString2; CmdAltMenuP
CmdQuitOverlayF; LoadDone = false; Confirmed = false
ShowActionForm = false; SavedLoadText
ProgramAct; NPrograms; ReadCAct; NReadCFiles
PassiveOnly = false; TEveryTime = 0; CFOutStream = 0; @CmdAbortAct
]

// Procedures made external

let WnsCSS(Num) be Wns(CmdCommentStream,Num,0,8)
and WnsCSSD(Num) be Wns(CmdCommentStream,Num,0,10)
and WnsCS1(N) be Wns(CmdCS1,N,0,8)
and WnsCS1D(N) be Wns(CmdCS1,N,0,10)


and FormCmdMenu() be FormMenu(CmdMDFS,FormCmdmenuText)


//Action subr is called with args
// (1) menu stream (always ItemStream)
// (2) MDFS (always CmdMDFS for command menu)
// (3) Mouse button union
// (4) arg from ActionBlock

and FormCmdmenuText(S,MDFS) be
[ //Initialize the ScreenTV stuff for this region to all blanks and
//set dirty
for L = 0 to MDFS>>Rgn.Height-1 do
PaintRgnLine(MDFS,L,table [ 0 ] )
test CmdAltMenuP ne 0
ifso CmdAltMenuP()
ifnot
[
//Kludge to allow partitioning of the main command menu actions according
//to flags in the Action structure. At the moment only two flags are used:
//ifPassive and ifActive. Machine-dependent code manipulates the
//PassiveOnly flag to control what’s displayed.
for I = FirstCmdAction to LastCmdAction by size Action/16 do
[ test PassiveOnly
ifso if I>>Action.ifPassive eq 0 then loop
ifnot if I>>Action.ifActive eq 0 then loop
WsMarkA(I)
]
if LoadDone & not PassiveOnly then WsMarkA(CmdDumpAct)
WsMarkA(ShowActionForm ? CmdConcealAct,CmdShowCmdAct)
WsMarkA(TextCmdOutStream eq 0 ? CmdStartWCAct,CmdStopWCAct)
FormHWMenu()
]
]


and ShowActions(Act1,FirstAct,NActs) be
[
if Act1 ne -1 then WsMarkA(Act1)
for I = 1 to NActs do
[ WsMarkA(FirstAct); FirstAct = FirstAct+(size Action/16)
]
]


and SetAbort(lvProc,Arg) be CmdAbortAct =
CreateAction("Abort",lvProc,Arg,0,$C-100B)


//Like SetAbort but don’t clear comment lines at onset of action.
and SetAbortPure(lvProc,Arg) be
[
SetAbort(lvProc,Arg)
CmdAbortAct>>Action.ifNoRes = 1
]


and CmdAbort(nil,nil,nil) be
[
ResetsCSS(); WssCSS("XXX"); QuitCmdOverlay(0)
]

//Called both as an action (with Menu eq ReadCMenu) and during
//initialization (with Menu eq RunProgMenu).
and CmdDoRC(lvMenu,nil,nil) be
[
if CmdAltMenuP ne 0 then
ErrorAbort("RdCmds ill. inside an action")
unless TimerGoing do StartTimer()
SkipCommandCount = 0
//Use menu when junk numbers are on input line
if (InputTextBuffer!1 le $9) & (InputTextBuffer!1 ge $0)
then Resets(InputStream)
if InputTextBuffer!0 eq 0 do//Get name from menu?
[ let DisplayState = SetDisplay(false)//Display on
SetAbort(lv CmdAbort,0)
SkipCommandCount = StartCmdOverlay(rv lvMenu)
SetDisplay(DisplayState)
]
//Flush stack and continue in MIDAS.BCPL
GotoLabel(TopFrame,BegCF,0)
]


and ExecuteCFile(fname,MBunion,nil) = valof
[
Wss(InputStream,fname)
//The MProc word in the Action table for some of the ReadCMenu actions
//contains the entry-skip-count or 0 if no skip
resultis QuitCmdOverlay(ThisAction>>Action.lvMProc)
]


and ReadCMenu() be
[
ShowActions(CmdAbortAct,LastAction+(NPrograms*(size Action/16)),
NReadCFiles)
ShowActions(-1,HWCFActions,NHWCFActions)
]

and RunProgMenu() be ShowActions(CmdAbortAct,LastAction,NPrograms)


//At the moment, this procedure is pretty clean. However, if at some
//future time code is added to save the state of the current program
//prior to a Dtach, then will have to add the commented code below.
//***NOTE CHANGES HERE FOR D0***
and CmdRunProg(Dtach,nil,nil) be
[
if CmdAltMenuP ne 0 do
[ if not MStatus>>MStatus.MachRunning then
ErrorAbort("RunProg/Boot ill. inside an action")
//
RemoveFromEveryTimeList(QuitF)
//
ForgetTemporaryActions()
//
FlushOverlays()
//TimeOut will be on when Dtach’ing from a Go inside a command file.
//
if TEveryTime ne 0 do
//
[ RemoveFromEveryTimeList(TEveryTime); TEveryTime = 0
//
]
]
SetDisplay(true)
//
if Dtach do
//
[ if CFileStream ne 0 do
//
[ Closes(CFileStream); CFileStream = 0; NestedCFiles = 0
//
]
//
if TextCmdOutStream ne 0 do
//
[ Closes(TextCmdOutStream); TextCmdOutStream = 0
//
]
//
]
if CFileStream eq 0 then StartTimer()
MStatus>>MStatus.RunProg = Dtach ? 2,1
MStatus>>MStatus.CFileStream = CFileStream //For RdCmds
MStatus>>MStatus.CFOutStream = CFOutStream //For Open/AppendOutput
MStatus>>MStatus.TextCmdOutStream = TextCmdOutStream //For WrtCmds
MStatus>>MStatus.ShowActionForm = ShowActionForm
MakeDispZoneAvail()
//
OverlayFlushed = true
while PeelOverlay() do [ ]
//Reset Midas and continue execution at "Resume" in MIDAS.BCPL
RestoreState((Dtach ? DtachFP,RunProgFP),true)
]


and TimeOutEveryTime(nil) be
[
let Now = vec 1; Timer(Now); DoubleAdd(Now,TimeTimeOut)
//Timed out or completed?
if (Now!0 ge 0) % (CommandCount le 0) do
[ RemoveFromEveryTimeList(TEveryTime); TEveryTime = 0
if CommandCount > 0 do
[ SkipCommandCount = 1
//DoTextAction should not return because QuitCmdOverlay will be called
DoTextAction($C - 100B)
]
]
]


//Start a very large overlay: First call InitP to build the actions;
//InitP returns lvProc for a procedure that will return the menu-forming
//procedure. Then flush out OverlayZone, add large storage blocks from
//the stack and sysZone to OverlayZone, and call StartCmdOverlay(lvProc);
//overlays needed will swap in during execution. After QuitCmdOverlay,
//flush everything and clean up.
//**All of callers leading to StartLargeOverlay must be resident.
and StartLargeOverlay(lvInitP,A1,A2,A3,A4; numargs NA) be
[
lvInitP = DummyCall1(lvInitP,NA-1,A1,A2,A3,A4)
FlushOverlays()
//sysZone block will be used for the D1SimIFU overlay.
let ExtraSysZone = Allocate(sysZone,#5100)
if ExtraSysZone eq 0 then MidasSwat(SmallsysZone)
AddToZone(OverlayZone,ExtraSysZone,#5100)
//Use Dvec so stack depth of DummyCall1(..) above won’t add to the #3100
//words here. Nesting of DummyCall1, SimGo, SetupIMA, PrCCV, SearchBlocks,
//GetBlock, ... is quite deep.
let ExtraStack = #3100
Dvec(StartLargeOverlay,lv ExtraStack)
//This block will be used for D1SimCon overlay.
AddToZone(OverlayZone,ExtraStack,#3100)
StartCmdOverlay(lvInitP)
KillOverlays()//Rebuilds zone if display is off
Free(sysZone,ExtraSysZone)
]


//Initiate an action which uses an alternate command menu.
//Action terminates with QuitCmdOverlay(Result) and Result is returned,
//or by ErrorExit or DisplayError, which return 0.
//StartCmdOverlay establishes a super-errorset that overrides the
//more local errorsets established by ErrorProtect.
//lvInitP may be either a menu-forming procedure or lvProc for a procedure
//that returns the menu-forming procedure as its result.
and StartCmdOverlay(lvInitP,A1,A2,A3,A4; numargs NA) = valof
[
let DisplayState = DisplayOff
let oldCmdAltMenuP,oldCmdQuitOverlayF = CmdAltMenuP,CmdQuitOverlayF
CmdAltMenuP = lvInitP > LastStatic ? lvInitP,
DummyCall1(lvInitP,NA-1,A1,A2,A3,A4)
FormCmdMenu()
//DoOverlay has to be called by ErrorProtect so that AbortFrame and
//AbortLabel will be restored after QuitCmdOverlay before any other errors
//can occur.
let Result = ErrorProtect(lv DoOverlay)
CmdAltMenuP,CmdQuitOverlayF = oldCmdAltMenuP,oldCmdQuitOverlayF
FormCmdMenu()
//Restore display to its old on/off state
SetDisplay(DisplayState); resultis Result
]


//In command files:
//For "Go" CommandCount will be > 0, so DriverLoop() will be called
//For "AltIO", CommandCount will be 0, so the subsidiary menu commands
//will come from the command file.
//DoOverlay is called from StartCmdOverlay and Init2.
and DoOverlay() = valof
[
CmdQuitOverlayF = CallersFrame()
test (CFileStream ne 0) & (CommandCount le 0) & (not ErrorFlag)
ifso ExecuteTextCmdStream(CFileStream)
ifnot DriverLoop()
//The above calls don’t return--QuitCmdOverlay(..) will be called
MidasSwat(CFileRetn)
]


//Used by actions to return from command overlays; by DisplayError and
//ErrorExit to abort either the command overlay or last ErrorProtect call.
and QuitCmdOverlay(Result) be
[
if CmdAltMenuP ne 0 do
[ ForgetTemporaryActions(); GotoFrame(CmdQuitOverlayF,Result)
]
GotoLabel(AbortFrame,AbortLabel,Result)
]


//If numargs is > 3 then CmdCommentStream is untouched
and DisplayError(S1,S2,S3,NoCSS; numargs NA) = valof
[
Confirmed = false
if not Initialized then CallSwat(S1)
let DisplayOff = SetDisplay(false)
if NA < 4 do
[ ResetsCSS()
if NA > 2 then WssCSS(S3)
WssCSS(S1)
]
CmdEString2 = NA > 1? S2, 0
ErrorFlag,AllowedRgn = true,CmdMDFS
let R = StartCmdOverlay(ErrorMenu)
ErrorFlag = false
SetDisplay(DisplayOff)
if R then resultis true//Continue
//Return 0 from StartCmdOverlay or last ErrorProtect(..)
QuitCmdOverlay(0)
]

//P is true to continue from error, false to abort command
and EndError(P,nil,nil) be
[
if P then GotoFrame(CmdQuitOverlayF,P)
QuitCmdOverlay(0)
]


and ErrorMenu() be
[
if CmdEString2 ne 0 then WssMark(CmdEString2,CmdErrorContinueAct)
//Presently cannot allow abort of command within CFile because on
//OpenFile errors this crashes.
WsMarkA((CFileStream ne 0 ? CmdCFileAbortAct,CmdErrorAbortAct))
]


and ErrorPrin(S1,S2,S3; numargs NA) be
[
if NA < 1 then S1 = "No good"
Confirmed = false
test Initialized
ifso WssCSS(S1)
ifnot CallSwat(S1)
if NA > 1 then WssCSS(S2)
if NA > 2 then WssCSS(S3)
test CFileStream ne 0
ifso DisplayError(0,"Continue",0,true)
ifnot
[ SetDisplay(false); Blink()
]
]


//Prints the three argument strings on CmdCommentStream (prints
//"No good" if none of these strings is given); then, if inside a
//command overlay calls DisplayError and exits from the command overlay
//if the user continues; if not inside a command overlay, exits from the
//last ErrorProtect call with a result of 0.
and ErrorExit(S1,S2,S3; numargs NA) be
[
DummyCall1(lv ErrorPrin,NA,S1,S2,S3)
QuitCmdOverlay(0)
]


//Abort action not in command overlay (actions that are illegal during
//a command overlay, such as SetValue, cannot call ErrorExit because
//they crash Midas).
and ErrorAbort(S1,S2,S3; numargs NA) be
[
DummyCall1(lv ErrorPrin,NA,S1,S2,S3)
GotoLabel(AbortFrame,AbortLabel,0)
]


and ConfirmAct(Str,TV) = valof
[
if Confirmed then
[ Confirmed = false; resultis true
]
WssCSS(Str)
for I = 1 to TV!0 do PutsCSS(TV!I)
WssCSS(" [confirm]")
UpdateDisplay()
let DisplayOff = SetDisplay(false)
[ if not Endofs(keys) then switchon Gets(keys) into
[
case $n: case $N: case 177B: case $C-100:
ResetsCSS(); WssCSS("XXX")
SetDisplay(DisplayOff); resultis false

case $.: case $Y: case $y: case $*N:
ResetsCSS(); SetDisplay(DisplayOff); resultis true

default: Blink()
]
] repeat
]

//The state of the program consists of its statics (all of which are saved),
//some page zero items (explicitly saved by calls on SaveStatics), and the
//core between StateEnd and EndStorage when SaveState is called.
//The address of the first static (Layout!26) and last static (Layout!27)
//are obtained from the layout vector in the Executive’s call to Midas.
//It seems wasteful to save all procedure statics, but this is necessary
//if the Overlay package is used, and it is easier to save all than be
//selective. There is no provision for enumerating individual blocks of
//storage outside the region delimited by StateEnd and (final) EndStorage.

//SaveState can be called multiple times, allowing the program to
//fire up with different initial information.
and SaveState(S) = valof
[
if S eq 0 then MidasSwat(BadSaveStateFP)
//Copy selected items (zrel statics) into StateBlock
for I = 0 to StatePtr-2 by 2 do
[ StateBlock!(I+1) = rv StateBlock!I
]
WriteBlock(S,FirstStatic,NStatics)
let BufSize = StateEnd-EndStorage
WriteBlock(S,EndStorage,BufSize)
Closes(S)
//Return file size for statistical purposes
resultis NStatics+BufSize
]


//Must be resident
and CmdWriteState(nil,nil,nil) be
[
if InputTextBuffer!0 eq 0 then ErrorAbort("Missing state file name")
let S = StreamFromTextName(InputTextBuffer,
".STATE",ksTypeWriteOnly,wordItem)
TxtBNewChar(#177); KillOverlays()
let C = CFileStream; CFileStream = 0
StateFileSize = SaveState(S)
CFileStream = C
]


//S is a stream argument for the file created by SaveState. reinitFlag
//should be true when RestoreState is called during initialization.
//The program can reinitialize itself during operation by calling
//RestoreState with reinitFlag false.
and RestoreState(FP,reinitFlag) be
[
let S = CreateDiskStream(FP,ksTypeReadOnly)
if S eq 0 then MidasSwat(BadRestoreStateFP)
let newEndStorage = EndStorage
ReadBlock(S,FirstStatic,NStatics)
if not reinitFlag then
if newEndStorage ne StateEnd then MidasSwat(MidasIncompatible)
let WC = StateEnd - EndStorage
let N = ReadBlock(S,EndStorage,WC)
if N ne WC then MidasSwat(StateFileClobbered)
for I = 0 to StatePtr-2 by 2 do
[ rv (StateBlock!I) = StateBlock!(I+1)
]
Closes(S); GotoLabel(TopFrame,Resume,0)
]