//Mrgn.bcpl
//	Last edited: 10 November 1979

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

external [
// OS
	Endofs; keys; Gets; Zero; SetBlock; Noop; Min

// MINIT0
	SkipScanLines

// MIDAS
	MidasSwat

// MDISP
	UpdateDisplay; DisplayOff; LineCtrlBlockPtrsVector; FontCharWidth

// MASM
	ErrorProtect

// MMENU
	deSelectMenu; MenuMChange

// Machine dependent
	@ScreenWidth; @ScreenHeight

// Defined here
	DriverLoop; PaintSetup; PaintRgnLine; PaintMark; PaintDirtyRegions
	UpdateEveryTime; AddToEveryTimeList; RemoveFromEveryTimeList
	ScreenTV; CharInputRoutine; RegionTable
	ErrorFlag; AllowedRgn; SelectedRegion; ScreenLinesDirty
	Displayalx; Displayacx; DisplayMaxrcx; @NewLx; @NewCx

// Defined here for init only
	ControlV; ScreenLineDirty; NRegions; EveryTimeP; EveryTimeA
]

static [
// statics related to display
	ScreenTV		// vector pointing to strings on screen
	ScreenLineDirty		// vector of dirty flags
	ScreenLinesDirty = true

	DisplayMaxrcx; Displayalx; Displayacx

// statics related to control
	ControlV		// vector pointing to marks vectors
	RegionTable; NRegions = -1; AllowedRgn
	EveryTimeP; EveryTimeA
	ErrorFlag = false

// statics related to mouse
	SelectedRegion; @NewLx; @NewCx

// input char routine
	CharInputRoutine
	]

//The quiescent loop time in DriverLoop should be small so that the
//Test overlay (etc.) will run quickly
let DriverLoop() be
[  let LineX,CharX,Buttons,ButtonUnion = nil,nil,0,0
   let LastMx,My = -1,-1
   let NewMBRgn = 0
   [	if ScreenLinesDirty then UpdateDisplay()
	while not Endofs(keys) do CharInputRoutine(Gets(keys))

	if My ne rv #427 do
	[ My,NewLx = rv #427,0
	  let scany,LCB = SkipScanLines,nil
	  while scany < My & NewLx < ScreenHeight do
	  [ NewLx = NewLx + 1
	    LCB = LineCtrlBlockPtrsVector!NewLx
	    scany = scany+((LCB>>LCB.Line.Height+LCB>>LCB.Sepr.Height) lshift 1)
	  ]
//Treat leading gap and borders as line 0 (a no-op region)
	  if (scany-((LCB>>LCB.Sepr.Height) lshift 1)) < My then NewLx = 0
	]

	let Mx = rv #426
	if Mx ne LastMx do
	[ LastMx = Mx
	  Mx = Mx-#16-HTab*16	//X-pos relative to left edge of 1st char
	  NewCx = Mx le 0 ? 0,
		Min(ScreenWidth,(Mx+FontCharWidth-1)/FontCharWidth)
	]

	let NewMB = (not rv #177030) & 7B
	if NewMB ne Buttons %	//New mouse buttons different
	  NewLx ne LineX %	//New line different
	  NewCx ne CharX do	//New char. pos. different
	[ for Rn = ControlV!NewLx to NRegions do
	  [ let R = RegionTable!Rn
//This check may be one off, but don't care with current Rgn arrangement
	    if R>>Rgn.aLineX > NewLx then break
	    let Cmin = R>>Rgn.aCharX
	    if Cmin < NewCx then if Cmin+R>>Rgn.Width ge NewCx then
	    [ NewMBRgn = R; break
	    ]
	  ]
	  let NewSelRgn = nil
	  test NewMB eq 0
	  ifso
	  [ NewSelRgn = NewMBRgn
//deSelectMenu is Noop if not a menu region
	    if NewSelRgn ne SelectedRegion do deSelectMenu(SelectedRegion)
	    SelectedRegion = NewSelRgn
	  ]
	  ifnot
	  [ NewSelRgn = SelectedRegion
//ButtonUnion is the union of all mouse buttons down since last up
	    ButtonUnion = ButtonUnion % NewMB
	  ]
//When an error menu is up, prevent actions in any other menus from
//being executed.
	  if (NewSelRgn>>Rgn.Type eq MenRgn) &
	     ((not ErrorFlag) % (NewSelRgn eq AllowedRgn)) then
		MenuMChange(NewSelRgn,NewSelRgn eq NewMBRgn,
			NewMB,ButtonUnion)
	  Buttons,LineX,CharX = NewMB,NewLx,NewCx
	  if NewMB eq 0 then ButtonUnion = 0
	]

	unless ErrorFlag then UpdateEveryTime()
   ] repeat
]


//Called from PaintMark, PaintRgnLine, FormMenu
and PaintSetup(R,rlx) = valof
[	DisplayMaxrcx = R>>Rgn.Width
	Displayalx,Displayacx = R>>Rgn.aLineX+1,R>>Rgn.aCharX
	let L = Displayalx+rlx
	ScreenLineDirty!L = true
	ScreenLinesDirty = true
	resultis L
]


//Called from MenuMarkSItem
and PaintMark(R,rlx,firstC,lastC,Flag) be
[	let S = ScreenTV!(PaintSetup(R,rlx))
	for I = firstC+Displayacx to lastC+Displayacx do
	[ S>>CV↑I = (S>>CV↑I & 177B) + Flag
	]
]


//Called by MarkMenus, after building an item in ItemV
//This has been hand-coded in masm.asm
//and PaintItem(L,TV,charX) be
//[	let S = ScreenTV!L
//	let Offset = charX+Displayacx
//No range check necessary here because MarkMenus makes one
//	for I = 1 to TV!0 do
//	[ S>>CV↑(I+Offset) = TV!I
//	]
//]


//Called by PaintDirtyRegions & by FormCmdmenuText
and PaintRgnLine(R,rlx,TV) be
[	let S = ScreenTV!(PaintSetup(R,rlx))
	for I = 1 to TV!0 do S>>CV↑(I+Displayacx) = TV!I
	let f,l = TV!0+Displayacx+1,DisplayMaxrcx+Displayacx
	if f > l then return
//In case odd byte at beginning or even byte at end
	S>>CV↑f = $ ; S>>CV↑l = $ 
	f,l = (f+1) rshift 1,(l+1) rshift 1
	if l > f then SetBlock(S+f,20040B,l-f)
]


//For text regions, this moves the characters out of the TV into ScreenTV
//It doesn't do anything for menu regions.
and PaintDirtyRegions() be
	for Rn = 0 to NRegions do
	[ let R = RegionTable!Rn
	  if R>>Rgn.DispDirty ne 0 then
	  [ if R>>Rgn.Type eq TextRgn do
		PaintRgnLine(R,0,R+(size Rgn/16)+(size ST/16))
	    R>>Rgn.DispDirty = 0
	  ]
	]


and UpdateEveryTime() be
[	for I = 0 to MaxEveryTime-1 do
		ErrorProtect(EveryTimeP+I,EveryTimeA!I)
]


and AddToEveryTimeList(P,A) = valof
[	for I = 0 to MaxEveryTime-1 do
	[ if EveryTimeP!I eq Noop do
	  [ EveryTimeP!I,EveryTimeA!I = P,A
	    resultis I
	  ]
	]
	MidasSwat(ETLOvf)
]


and RemoveFromEveryTimeList(I) be
[	if (I ge MaxEveryTime) % (I < 0) then MidasSwat(BadArgforRETL)
	EveryTimeP!I = Noop
]