//MMenu.bcpl 27 June 1983 get "mcommon.d" get "mdecl.d" get "streams.d" external [ // OS Puts; Resets; Gets; Closes; Endofs; Zero; Noop; Min; Timer DoubleAdd; CallSwat // MIDAS MidasSwat; PrintTime // MASM @MBlock; ErrorProtect; SymbKeyComp; Wss; ResetsCSS; ResetsCS1 DoubleNeg // MIOC Wns // MSYM SkipBlankToken; @StringVec // MTXTBUF InputStream; InputTextBuffer; ClearInText; TxtBufClearFlag // MDISP Blink; DisplayOff; @NewLx; @NewCx; DisplayMaxrcx; Displayalx // MRGN UpdateDisplay; PaintSetup; PaintItem; PaintMark; DriverLoop UpdateEveryTime; SelectedRegion; ScreenLinesDirty // MMPRGN MPDdeSelect // MCMD ShowActionForm; ErrorExit; CmdStopWCAct CmdCommentStream; CmdCS1; TextCmdOutStream; CmdMDFS; CFOutStream CmdAltMenuP // xxACTIONS ActionBlock; ActionPtr; NoopAction; BugValAct EscAction; CRAction; NewEscAction; NewCRAction // Defined here CreateAction; ForgetTemporaryActions; DoAction; DoTextAction ExecuteTextCmdStream; LookUpMenu; PrintActionTime FormMenu; MarkMenus; WssMark; @WsMarkA; WssMAct MenuMChange; deSelectMenu CommandCount; SkipCommandCount; SkipName; AbortingCFile @ItemV; ItemStream; @CurrentMDFS; CFileStream EscMBunion; EscMDFS; EscInputText; ThisAction; TimeStartAction // Defined here for init only LastPermanentAction; MenuBlock ] static[ ItemStream; @ItemV; @CurrentMDFS LastPermanentAction; MenuBlock CommandCount = 0; SkipCommandCount = 0; SkipName; CFileStream = 0 AbortingCFile = 0 EscMBunion = TopButton; EscMDFS; EscInputText; ThisAction @InLine; @Height; @ProcNMax; @TextLines; @SizeVec; @ProcVec TimeStartAction ] let LookUpMenu(Letter,LineN) = valof [ if LineN ge MaxLineN then resultis 0 let LetterV = Letter-$A LetterV = (LineN < 0) ? LetterV,(LetterV*MaxLineN)+LineN+($Z-$A+1) resultis LetterV > MaxMenus ? 0,MenuBlock!LetterV ] and CreateAction(Name,lvProc,Arg,lvMProc,Char,TO; numargs NA) = valof [ if ActionPtr ge MaxActions then MidasSwat(ActionOVF) let B = ActionBlock+ActionPtr*(size Action/16) B>>Action.Name = Name B>>Action.lvProc = lvProc B>>Action.lvMProc = NA < 4 ? 0,lvMProc B>>Action.Arg = Arg (lv B>>Action.Char)!0 = 0 if NA ge 5 do [ B>>Action.Char = Char if NA ge 6 then B>>Action.ifTO = TO ] ActionPtr = ActionPtr+1 resultis B ] and ForgetTemporaryActions() be ActionPtr = LastPermanentAction and LookUpAction(Name) = valof [ let B,C = ActionBlock,ActionBlock+(ActionPtr*(size Action/16)) while B < C do [ if SymbKeyComp(B>>Action.Name,Name) eq 0 then resultis B B = B+(size Action/16) ] resultis 0 ] and DoTextAction(Char) be [ let Action = valof [ let B,C = ActionBlock,ActionBlock+(ActionPtr*(size Action/16)) while B < C do [ if B>>Action.Char eq Char then resultis B B = B+(size Action/16) ] resultis 0 ] test Action eq 0 ifso Blink() ifnot DoAction(Action,TopButton,CmdMDFS) ] and DoAction(Action,MBunion,MDFS) be [ Timer(TimeStartAction); DoubleNeg(TimeStartAction) //Flush trailing blanks in input text except for BugValAct. //Reset input text for BugValAct when indicated test (Action eq BugValAct) & ((MBunion & BottomButton) ne 0) ifso if TxtBufClearFlag then Resets(InputStream) ifnot while InputTextBuffer!(InputTextBuffer!0) eq $ do InputTextBuffer!0 = InputTextBuffer!0-1 //Insert a bogus TimeOut action sequence around commands that require //this in command files. Actions from a command file aren't written on //the output file. if (TextCmdOutStream ne 0) & (Action ne CmdStopWCAct) & (CFileStream eq 0) then [ let TimeoutRequired = Action>>Action.ifTO if TimeoutRequired ne 0 then Wss(TextCmdOutStream,"L X TimeOut 30000; 12 sec*N") WLogicalActToS(TextCmdOutStream,MBunion,MDFS,Action) Puts(TextCmdOutStream,$*N) if TimeoutRequired ne 0 then Wss(TextCmdOutStream,"L X Skip 1*NL X ShowError Timed out*N") ] let SvEscAction,SvCRAction = EscAction,CRAction let EscCRChange = false test Action>>Action.ifEsc ne 0 //EscAction = ThisAction ifso [ NewEscAction,EscMBunion,EscMDFS = Action,MBunion,MDFS MBlock(EscInputText,InputTextBuffer,InputTextBuffer!0+1) NewCRAction = NoopAction EscCRChange = true ] ifnot if Action>>Action.ifResEsc ne 0 do [ NewEscAction,NewCRAction = NoopAction,NoopAction EscCRChange = true ] //EscAction and CRAction point at NoopAction during an action so that //typing ahead won't crash. EscAction,CRAction = NoopAction,NoopAction ClearInText() //Set flag so next in char clears if Action>>Action.ifNoRes eq 0 do [ ResetsCSS(); ResetsCS1() ] ThisAction = Action //Special kludge so RdCmds can get //at Action>>Action.lvMProc used as an arg ErrorProtect(Action>>Action.lvProc,Action>>Action.Arg, MBunion,MDFS) //If this action changes EscAction and CRAction then pickup changes, //else restore previous values. test EscCRChange ifso [ EscAction,CRAction = NewEscAction,NewCRAction ] ifnot [ EscAction,CRAction = SvEscAction,SvCRAction ] PrintActionTime() ] and PrintActionTime() be [ let T = vec 1; Timer(T); DoubleAdd(T,TimeStartAction); PrintTime(T) ] //Execute command file. ExecuteTextCmdStream is called from the top level //procedure InitRes(), where a list of nested command files is maintained, //on "RdCmds" or "RunProg" actions. Recursive calls of ExecuteTextCmdStream //occur on "command overlay" actions, where StartCmdOverlay calls DoOverlay, //which calls ExecuteTextCmdStream. However, during an overlay, the //only exit from ExecuteTextCmdStream is by QuitCmdOverlay(..) which returns //directly to the frame of the ErrorProtect call in StartCmdOverlay. //Cfile syntax errors always shut down the current cfile and resume the next //higher cfile, as does a skip beyond EOF, irrespective of whether the user //aborts or continues from the error. Errors during an action abort the //action and resume the current cfile if the user continues, or abort the //cfile if the user aborts. On any of these errors, control returns to //ExecuteTextCmdStream with R=0. The two normal exits are EOF and executing //the "Return" action; for these R=-1. Note that the caller of //ExecuteTextCmdStream is always InitRes when these returns occur. //Inside a command overlay, an error sends control to StartCmdOverlay with a //GotoFrame, erasing the overlay; GotoFrame is in ErrorExit when continuing //or in QuitCmdOverlay when aborting. StartCmdOverlay returns eventually to //ExecuteTCS1, which returns to ExecuteTextCmdStream before the next action //if AbortingCFile ne 0. Because the context to which StartCmdOverlay must //return is destroyed, RdCmds is illegal during a command overlay, and the //cfile which started the overlay must not terminate before the overlay has //completed. and ExecuteTextCmdStream(S) be [ CFileStream = S let R = ErrorProtect(lv ExecuteTCS1,S) //Only return here if not inside a command overlay. Closes(CFileStream) //On "Return", AbortingCFile=n+1, so at the onset of the next action //ExecuteTCS1 will return to ExecuteTextCmdStream which will set //SkipCommandCount=n, AbortingCFile=0, and return to InitRes. AbortingCFile //will be -1 on control-Z, ErrorExit, or ShowError to abort all nested //cfiles; or 0 if not aborting. test AbortingCFile > 0 ifso [ SkipCommandCount = AbortingCFile-1 AbortingCFile = 0 ] ifnot SkipCommandCount = 0 CFileStream = 0 ] and ExecuteTCS1(S) = valof [ SkipName!0 = 0 //Loop over all text lines in command file [ let ALength,APtr,semiF,Char = 0,1,false,nil CommandCount = CommandCount-1 if AbortingCFile ne 0 then resultis 0 if CommandCount eq 0 then DriverLoop() //Read in command line stripped of any comment, replacing tabs by blanks [ if Endofs(S) do [ if ALength > 0 then ErrorExit("Incomplete action at EOF ",StringVec) if SkipName!0 ne 0 then ErrorExit("Undefined skip tag ",SkipName) if SkipCommandCount ne 0 then ErrorExit("Skip beyond EOF") if CmdAltMenuP ne 0 then ErrorExit("Premature EOF inside overlay") resultis -1 ] Char = Gets(S) if Char eq $*N then break if Char < 40B then Char = $ if Char eq $; then semiF = true if semiF then loop //Flush comment if ALength ge 99 then ErrorExit("Line too long") ALength = ALength+1 StringVec!ALength = Char StringVec!0 = ALength ] repeat //Flush the text line if not enough on it if ALength le 1 then loop let CPos = 0 //Collect ".TAG", if any, and match against searched for tag, if any if StringVec!APtr eq $. do [ APtr = APtr+1 while APtr le ALength do [ Char = StringVec!APtr; APtr = APtr+1 if Char eq $ then break CPos = CPos+1; StringVec>>CV↑CPos = Char ] StringVec>>lh = CPos if SymbKeyComp(StringVec,SkipName) eq 0 then SkipName!0 = 0 StringVec!0 = ALength ] //If skipping commands then don't parse further if SkipName!0 ne 0 then loop SkipBlankToken(StringVec,lv APtr) //Make sure got a real command before applying skip test if (ALength - APtr) le 1 then loop if SkipCommandCount > 0 do [ SkipCommandCount = SkipCommandCount-1; loop ] //Collect mouse buttons terminated by blank let Buttons,Action = 0,0 while APtr le ALength do [ Char = StringVec!APtr APtr = APtr+1 if Char eq $ then break Buttons = Buttons logor selecton Char into [ case $L: TopButton case $M: MiddleButton case $R: BottomButton default: -1 ] ] if Buttons le 0 then ErrorExit("Bad mouse button") // Collect menu char SkipBlankToken(StringVec,lv APtr) if APtr le ALength do [ Char = StringVec!APtr; APtr = APtr+1 ] // Collect menu line number let N,N1 = -1,nil while APtr le ALength do [ N1 = StringVec!APtr; APtr = APtr+1 if (N1 > $9) % (N1 < $0) then break N = (N < 0 ? N1,(N*10)+N1) - $0 ] let MDFS = LookUpMenu(Char,N) if MDFS eq 0 then ErrorExit("Undefined menu") //Collect action name terminated by *N or blank CPos = 0 while APtr le ALength do [ Char = StringVec!APtr; APtr = APtr+1 //Flush leading blanks before command line input if Char eq $ do [ Resets(InputStream) SkipBlankToken(StringVec,lv APtr) //Put command line text onto command line while APtr le ALength do [ Puts(InputStream,StringVec!APtr); APtr = APtr+1 ] break ] CPos = CPos+1; StringVec>>CV↑CPos = Char ] StringVec>>lh = CPos Action = LookUpAction(StringVec) if Action eq 0 then ErrorExit("Undefined action ",StringVec) if not DisplayOff do // Show what's happening [ deSelectMenu(SelectedRegion) //Displace from beginning of MDFS structure back to ProcVec PointPV(MDFS) let N = 0 for I = 1 to ProcVec!0 do if ProcVec!I eq Action then [ N = I; break ] MenuMarkItems(MDFS,N) SelectedRegion = MDFS UpdateEveryTime(); UpdateDisplay() ] // End of display update //CommandCount controls cfile timeouts; ordinarily it is < 0. Actions which //use the EveryTimeList will return from DoAction before completion, but //cfile execution must not resume until QuitCmdOverlay is called later by an //EveryTimeList procedure; meanwhile, DriverLoop must run the EveryTimeList //and display. These actions require a preceding TimeOut action, which sets //CommandCount=2. DriverLoop is called after completing the action //following a TimeOut. DriverLoop then runs until the overlay terminates //with QuitCmdOverlay. test CommandCount > 0 //Preceding TimeOut? ifso if Action>>Action.ifTO eq 0 do [ ResetsCSS(); ErrorExit("Illegal TimeOut before ",StringVec) ] ifnot test Action>>Action.ifTO ne 0 ifso [ ResetsCSS(); ErrorExit("Missing TimeOut before ",StringVec) ] ifnot CommandCount = -1 DoAction(Action,Buttons,MDFS) ] repeat ] //The MDFS structure is preceded by the TextLines, SizeVec, and ProcVec //vectors, as discussed in MDECL.D. Setup pointers to these and store //several other interesting values in statics and PointPV(MDFS) be [ Height,ProcNMax = MDFS>>MDFS.Rgn.Height,MDFS>>MDFS.ProcNMax TextLines = MDFS-Height SizeVec = TextLines-((Height+ProcNMax) rshift 1) ProcVec = SizeVec-ProcNMax ] //Call the procedure for forming the menu and FormMenu(MDFS,Proc,nil) be [ CurrentMDFS = MDFS PointPV(MDFS); Zero(ProcVec,MDFS-ProcVec) InLine = 0 //ItemV is a TextVec that is filled with successive items by MarkMenus ItemV!0 = 0 PaintSetup(MDFS,InLine) //Call the procedure for forming the menu--it will make calls on MarkMenus //(usually via WsMarkA) to add successive menu items to the region Proc(ItemStream,MDFS) MDFS>>MDFS.inLine = InLine+1 MDFS>>MDFS.SelectedItem = 0 MenuSelectItem(MDFS) ] //The setup for MarkMenus is carried out by FormMenu. and MarkMenus(Action) be [ if ProcVec!0 ge ProcNMax-1 then test Action eq 0; ifso return; ifnot MidasSwat(TooManyActions) let Size = ItemV!0 //No text in ItemV is a carriage return if (TextLines!InLine + Size > DisplayMaxrcx) % (Size eq 0) then [ if InLine+1 ge Height then return InLine = InLine+1; PaintSetup(CurrentMDFS,InLine) if Size eq 0 then return ] ProcVec!0 = ProcVec!0 + 1 ProcVec!(ProcVec!0) = Action let Z,SVZ,I = 0,nil,1 [ SVZ = (SizeVec>>CV↑Z)+1 if I > InLine then break; I = I+1; Z = Z+SVZ ] repeat SizeVec>>CV↑Z = SVZ SizeVec>>CV↑(Z+SVZ) = Size PaintItem(Displayalx+InLine,ItemV,TextLines!InLine) TextLines!InLine = TextLines!InLine + Size ItemV!0 = 0 ] and WssMAct(Act) be [ Wss(ItemStream,Act>>Action.Name); MarkMenus(Act) ] and WssMark(Name,Act) be [ Wss(ItemStream,Name); MarkMenus(Act) Puts(ItemStream,$ ); MarkMenus(0) ] and WsMarkA(Act) be [ WssMAct(Act); Puts(ItemStream,$ ); MarkMenus(0) ] //Called from FormMenu and MenuMChange, each of which has called PointPV and MenuSelectItem(MDFS) be [ let X,N = 0,0 if MDFS>>MDFS.mIn ne 0 do [ let rlx = Min(NewLx-MDFS>>MDFS.Rgn.aLineX,MDFS>>MDFS.inLine) let rcx = NewCx-MDFS>>MDFS.Rgn.aCharX let SVX,I = nil,1 [ SVX = SizeVec>>CV↑X; if I ge rlx then break I = I+1; N = N+SVX; X = X+SVX+1 ] repeat let Y = 0 for I = 1 to SVX do [ Y = Y + SizeVec>>CV↑(X+I) N = N + 1; if Y ge rcx then break ] ] MenuMarkItems(MDFS,N) ] //Called from ExecuteTextCmdStream, MenuSelectItem, and deSelectMenu, each //of which has called PointPV and MenuMarkItems(MDFS,N) be [ let OldMenuItem = MDFS>>MDFS.SelectedItem if OldMenuItem ne N then [ MenuMarkSItem(MDFS,OldMenuItem,0) MenuMarkSItem(MDFS,N,200B) MDFS>>MDFS.SelectedItem = N ScreenLinesDirty = true ] ] //Called from MenuMarkItems. PointPV has been called already. //Flag is 200B (white-on-black) or 0 (normal) and MenuMarkSItem(MDFS,N,Flag) be [ if N eq 0 then return if (ProcVec!N eq 0) & (Flag ne 0) then return //X is rlx (relative line number) //Y points at the size of the current item //M is the item number let X,Y = 0,0 [ let SVY = SizeVec>>CV↑Y if SVY ge N then break X = X+1; N = N-SVY; Y = Y+SVY+1 ] repeat let charX = 1 for I = 2 to N do [ Y = Y+1; charX = charX+SizeVec>>CV↑Y ] PaintMark(MDFS,X,charX,(SizeVec>>CV↑(Y+1))+charX-1,Flag) ] and WLogicalActToS(S,MB,MDFS,Act) be [ if (MB & TopButton) ne 0 then Puts(S,$L) if (MB & MiddleButton) ne 0 then Puts(S,$M) if (MB & BottomButton) ne 0 then Puts(S,$R) Puts(S,$ ) Puts(S,MDFS>>MDFS.Letter) let L = MDFS>>MDFS.LineN if (L ge 0) & (L le MaxLineN) then Wns(S,L,0,10) Puts(S,$ ); Wss(S,Act>>Action.Name); Puts(S,$ ) for I = 1 to InputTextBuffer!0 do Puts(S,InputTextBuffer!I) let Char = Act>>Action.Char if Char ne 0 then test Char < 40B ifso [ Wss(S,"; control-"); Puts(S,100B+Char) ] ifnot [ Wss(S,"; ;"); Puts(S,Char) ] ] //deSelectMenu and MenuMChange are the entries to the menu stuff from //MRGN.BCPL. Because of the way MPD, MDFS, and RGN structures nest, //a pointer to MPD is also a pointer to MDFS and to RGN. //At present the two kinds of menu are the command menu (nothing special //on deselect) and the name-value menus. and deSelectMenu(R) be [ if R>>Rgn.Type ne MenRgn then return if R ne CmdMDFS do MPDdeSelect(R) R>>MDFS.mIn = 0; PointPV(R); MenuMarkItems(R,0) if ShowActionForm then ResetsCSS() ] //MenuMChange is only called from DriverLoop when the mouse buttons, //line, or character position have changed, when the newly selected //region is a menu region, and when actions in that menu region are //legal (When an error menu is up, only command menu actions are legal). //MenuMChange first calls Action.lvMProc; lvMProc will not be called //during command files, so its effects should be limited to menu //modifications. lvMProc can also print stuff on CmdCS1 but cannot //use CmdCommentStream because ShowActions uses that. At present only //the MPD menus make use of lvMProc (MPDMChange is called to show the //menu "underneath" the one over which the buttons are depressed). //DoAction is called if the mouse is still in the same region that it //was in when the first button was depressed, and if mouse buttons have //just become 0 after being non-0. and MenuMChange(R,InR,MB,MBunion) be [ R>>MDFS.mIn = InR PointPV(R); MenuSelectItem(R) let Item = R>>MDFS.SelectedItem if Item > 0 do [ let Action = ProcVec!Item if Action ne 0 do [ if ShowActionForm then [ ResetsCSS() WLogicalActToS(CmdCommentStream,MBunion,R,Action) ] //The lvMProc word in the Action structure is sometimes used for an //extra argument (small integer < #1000) if (Action>>Action.lvMProc & 177000B) ne 0 then ErrorProtect(Action>>Action.lvMProc, Action>>Action.Arg,R,MBunion,MB) if (MB eq 0) & (MBunion ne 0) & InR then DoAction(Action,MBunion,R) ] ] ]