//MMenu.bcpl // Last edited: 10 November 1979 get "mcommon.d" get "mdecl.d" get "streams.d" external [ // OS Puts; Resets; Gets; Closes; Endofs; Zero; Noop; Min; Timer DoubleAdd // 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 UpdateEveryTime; SelectedRegion; ScreenLinesDirty // MMPRGN MPDdeSelect // MCMD CmdCommentStream; CmdCS1; ShowActionForm; DisplayError TextCmdOutStream; CmdMDFS; CFOutStream; CmdStopWCAct; ErrorAbort // 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; 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 then B>>Action.Char = Char 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 10000*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 and ExecuteTextCmdStream(S) be [ let OldCFileStream = CFileStream; CFileStream = S //Trap command file errors here; ExecuteTCS1 returns -1 normally, 0 //after a comfile error. let R = ErrorProtect(lv ExecuteTCS1,S) //Careful here because a single command file may result in several //calls to ExecuteTextCmdStream during "Go" or other commands that do //StartCmdOverlay. Hence, only cleanup when OldCFileStream is different. if CFileStream ne OldCFileStream do [ Closes(S) SkipCommandCount = (AbortingCFile ne 0) ? AbortingCFile-1,0 if (CFileStream eq 0) % (AbortingCFile > 0) then AbortingCFile = 0 CFileStream = OldCFileStream if (R ne 0) & (SkipName!0 ne 0) then ErrorProtect(lv DisplayError,"Undefined tag ",0,SkipName) ] ] and ExecuteTCS1(S) = valof [ CommandCount,SkipName!0 = 0,0 let ABuf = vec 100 //Loop over all text lines in command file [ let ALength,APtr,semiF,Char = 0,1,false,nil //Read in command line stripped of any comment, replacing tabs by blanks [ if Endofs(S) % (AbortingCFile ne 0) then resultis -1 Char = Gets(S) if Char eq $*N then break if Char eq $; then semiF = true if semiF then loop //Flush comment if ALength ge 99 then ErrorAbort("Line too long") ALength = ALength+1; ABuf!ALength = Char eq $*t ? $ ,Char ] repeat //Flush the text line if not enough on it if ALength le 1 then loop ABuf!0 = ALength let CPos = 0 //Collect ".TAG", if any, and match against searched for tag, if any if ABuf!APtr eq $. do [ APtr = APtr+1 while APtr le ALength do [ Char = ABuf!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 ] //If skipping commands then don't parse further if SkipName!0 ne 0 then loop SkipBlankToken(ABuf,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 = ABuf!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 ErrorAbort("Bad mouse button") // Collect menu char SkipBlankToken(ABuf,lv APtr) if APtr le ALength do [ Char = ABuf!APtr; APtr = APtr+1 ] // Collect menu line number let N,N1 = -1,nil while APtr le ALength do [ N1 = ABuf!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 ErrorAbort("Undefined menu") //Collect action name terminated by *N or blank CPos = 0 while APtr le ALength do [ Char = ABuf!APtr; APtr = APtr+1 //Flush leading blanks before command line input if Char eq $ do [ Resets(InputStream) SkipBlankToken(ABuf,lv APtr) //Put command line text onto command line while APtr le ALength do [ Puts(InputStream,ABuf!APtr); APtr = APtr+1 ] break ] CPos = CPos+1; StringVec>>CV↑CPos = Char ] StringVec>>lh = CPos Action = LookUpAction(StringVec) if Action eq 0 then ErrorAbort("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 test CommandCount > 0 //TimeOut pending? ifso if Action>>Action.ifTO eq 0 then ErrorAbort("Illegal TimeOut before ",StringVec) //**Should cleanup ifnot if Action>>Action.ifTO ne 0 then ErrorAbort("Missing TimeOut before ",StringVec) DoAction(Action,Buttons,MDFS) CommandCount = CommandCount - 1 ] 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) ] //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) ] ] ]