// MenuEdit.bcpl -- design, create and edit a BCPL menu file
// bldr MenuEdit MenuEditMore MenuEditReadWrite ReadUserCmItem gp Menu MenuBoxStreams MenuBox MenuBoxUtils Window
// by Keith Knox  last modified March 8, 1978  by L. Stewart


get "MenuDefs.d"


external		// incoming OS procedures
	[
	Ws
	Wns
	Gets
	GetFixed
	GetFont
	InitializeZone
	Allocate
	Free
	Zero
	Endofs
	MoveBlock
	]

external		// incoming gp procedure
	[
	ReadString
	]

external		// incoming and outgoing procedures
	[
	ReadFile
	WriteFile
	Font
	getnumber
	getname
	MenuInitHelp	// needed for menu.bcpl
			// usually supplied with a menu table file
	Sensitize		// in menueditmore.bcpl
	ShowSensitize
	DotCorners
	JustifyText
	Instructions
	Boundary	
	DoDCBPress
	]

external		// incoming OS statics
	[
	keys
	dsp
	]

external		// outgoing statics
	[
	dcb
	systemdcb
	buffer
	zone
	stringlist
	boxnames
	highestname
	fontPtr
	menu
	selected
	]


static		// internal statics
	[
	dcb
	systemdcb
	buffer
	zone
	menu
	stringlist
	boxnames
	tempbox
	highestname=false
	selected
	fontPtr
	]

manifest anykey=#377

let main() be
	[
	// set up the necessary arrays and statics etc.
	Setup()

	// loop looking for commands
	let key,char=nil,nil
	let number=nil
	let autodeselect=true
	[
	// check mouse keys and keyset
	key=(not @#177030) & anykey
	switchon key into
		[
		case 128: case 64: case 32:
		case 16:	number=ScanMenu(menu,false,false,true)
			if number then AddToSelections(number)
				endcase
		case 4:	number=ScanMenu(menu,false,false,true)
			test number ifnot MakeMenuWindow()
				ifso AddToSelections(number)
				endcase
		case 2:	ChangeMenuWindow() ; endcase
		case 1:	MoveMenuWindow() ; endcase
		]

	// check keyboard
	if Endofs(keys) then loop
	char=Gets(keys) //  & #177737  cv Uppercase no longer!
	if char eq $q then
		[
		@#420=systemdcb
		Ws("*N*N*N*N*N*NQuit  [ confirm with CR ]")
		char=Gets(keys)
		if char eq $*N then [ Ws("*N*N*N*N*N*N");finish ]
		char=$*T
		@#420=dcb
		]

	// if no boxes then keyboard commands meaningless
	if (menu!0 eq 0) & (char ne $r) & (char ne #37) then loop
	switchon char into
		[
		case $a:		autodeselect=true ; endcase
		case $A:		autodeselect=false ; endcase
		case $b:		Boundary() ; endcase
		case $c:		Change() ; endcase
		case $d:		DeleteList(selected) ; endcase
		case $e:		Sensitize(true) ; endcase
		case $E:		Sensitize(false) ; endcase
		case $f:		Font() ; endcase
		case $g:		Gap() ; endcase
		case $i:		Insert() ; endcase
		case $j:		JustifyText(true) ; endcase
		case $J:		JustifyText(false) ; endcase
		case $l:		LineUp() ; endcase
		case $n:		Name() ; endcase
		case $o:		Outline() ; endcase
		case $p:		DoDCBPress() ; endcase
		case $*T:	endcase
		case $r:		ReadFile() ; endcase
		case $s:		Same() ; endcase
		case $v:		ShowSensitize() ; endcase
		case $w:		WriteFile() ; endcase
		case $?:		Instructions() ; endcase		// ($? & #177737)
		case #137: case #177:	endcase	// <DEL> -- de-selects
		default:		loop
		]
	if autodeselect%(char eq #177)
 do [ unless (char eq $?)%(char eq $*T)%(char eq $v) do Zero(selected,MaxLength) ]
	Refresh()
	] repeat

	]


and Setup() be
	[
	// setup a big buffer
	dcb=GetFixed(30800)
	Zero(dcb,30800)
	dcb=dcb+(dcb & 1)
	dcb!1=38 ; dcb!2=dcb+4 ; dcb!3=404
	buffer=dcb+4
	systemdcb=@#420

	// setup menu and string list
	menu=GetFixed(MaxLength)
	stringlist=GetFixed(MaxLength)
	boxnames=GetFixed(MaxLength)
	selected=GetFixed(MaxLength)
	Zero(menu,MaxLength)
	Zero(stringlist,MaxLength)
	Zero(boxnames,MaxLength)
	Zero(selected,MaxLength)

	// setup display
	Ws("*N*N*N*N*N*N")
	Ws("MenuEdit  1.1  November 11, 1977")
	Ws("*NType any char to continue or ? for help.")
	while Endofs(keys) do loop
	if Gets(keys) eq $? then Instructions()
	@#420=dcb
	fontPtr=GetFont(dsp)

	// setup zones for allocating boxes and strings
	let length=MaxLength*(lBOX+10)
	zone=InitializeZone(GetFixed(length),length)
	tempbox=CreateBox(0,0,10,10,zone)
	]


and MakeMenuWindow() be
	[
	// select a window with the mouse
	if menu!0 ge MaxLength-1 then return
	let box=nil
	let choice=SelectWindow(tempbox)
	test choice ifso
		[
		// make record of box in zone
		menu!0=menu!0+1
		box=CreateBox(0,0,10,10,zone)
		menu!(menu!0)=box
		MoveBlock(box,tempbox,lBOX)

		// update stringlist and boxnames
		stringlist!0=stringlist!0+1
		boxnames!0=boxnames!0+1
		boxnames!(boxnames!0)=defaultname()
		]
		ifnot
		[
		tempbox>>BOX.xcorner=tempbox>>BOX.xorigin+1
		tempbox>>BOX.ycorner=tempbox>>BOX.yorigin+1
		]
	]


and ChangeMenuWindow() be ChangeMoveMenuWindow(true)

and MoveMenuWindow() be ChangeMoveMenuWindow(false)

and ChangeMoveMenuWindow(testflag) be
	[
	// move a window with the mouse
	let number=NearestBox(menu)
	let box=menu!number
	let bits=box>>BOX.bits

	// remove the outline and any text
	let flipped=0
	for n=1 to selected!0 do if number eq selected!n then flipped=true
	OutlineBox(box,bits)	// remove old outline
	OutlineBox(box,1,0)	// remove old dots
	if stringlist!number % flipped then FlipBox(box,0)
	OutlineBox(box)				// outline by one bit

	// now change or move it
	test testflag ifnot MoveWindow(box) ifso ChangeWindow(box)
	OutlineBox(box)				// remove one bit outline

	// if zero width or height box then delete it
	let deltaX=box>>BOX.xcorner-box>>BOX.xorigin
	let deltaY=box>>BOX.ycorner-box>>BOX.yorigin
	if deltaX eq 0 % deltaY eq 0 then [ Delete(number);return ]

	// put back the outline and any text
	OutlineBox(box,bits)		// re-outline
	DotCorners(box)				// if no outline
	WriteBox(box,stringlist!number,fontPtr)
	for n=1 to selected!0 do if number eq selected!n do FlipBox(box)
	]


and Change() be
	[
	// use first window for default
	if selected!0 le 0 then return
	let number=selected!1

	// now get proper size
	Ws("*N*N*N*N*N*N")
	@#420=systemdcb
	let box=menu!number
	let defwidth=box>>BOX.xcorner-box>>BOX.xorigin+1
	let defheight=box>>BOX.ycorner-box>>BOX.yorigin+1
	let width=getnumber("*NWindow width",defwidth)
	let height=getnumber("*NWindow height",defheight)
	if (width le 1) % (width gr 606) then width=defwidth
	if (height le 1) % (height gr 808) then height=defheight

	// change the origin and corner for all windows
	for n=1 to selected!0 do
		[
		box=menu!(selected!n)
		box>>BOX.xcorner=box>>BOX.xorigin+width-1
		box>>BOX.ycorner=box>>BOX.yorigin+height-1
		checkbox(box)
		]
	]

and DeleteList(list) be
	[
	// delete all listed
	for n=1 to list!0 do
		[
		let number=list!n
		if menu!number then Free(zone,menu!number)
		if stringlist!number then Free(zone,stringlist!number)
		if boxnames!number then Free(zone,boxnames!number)
		menu!number=-1;stringlist!number=-1;boxnames!number=-1
		]
	compress(menu);compress(stringlist);compress(boxnames)
	]

and compress(array) be
	[
	// anything that is -1 is removed
	// elements on stringlist may be zero
	// and shouldn't be removed
	let ptr=1
	for n=1 to array!0 do if array!n ne -1 then [ array!ptr=array!n; ptr=ptr+1 ]
	for n=ptr to array!0 do array!n=0
	array!0=ptr-1
	]

and Delete(number) be
	[
	if menu!number then Free(zone,menu!number)
	if stringlist!number then Free(zone,stringlist!number)
	if boxnames!number then Free(zone,boxnames!number)
	DeleteFromMenu(menu,number)
	DeleteFromMenu(stringlist,number)
	DeleteFromMenu(boxnames,number)
	]

and Gap() be
	[
	// get proper spacing
	if selected!0 le 1 then return
	Ws("*N*N*N*N*N")
	@#420=systemdcb
	let defgap=0
	let gap=getnumber("*NGap between windows in bits",defgap)
	if (gap ls 0) % (gap gr 606) then gap=defgap
	Ws("*NType V or H to space vertically or horizontally.")
	let char=nil
		[
		char=Gets(keys) & #177737
		if char eq $V % char eq $H then break
		] repeat

	// now do the spacing
	let box=nil
	let refbox=nil
	let width,height=nil,nil
	for n=1 to selected!0-1 do
		[
		refbox=menu!(selected!n)
		box=menu!(selected!(n+1))
		width=box>>BOX.xcorner-box>>BOX.xorigin
		height=box>>BOX.ycorner-box>>BOX.yorigin
		switchon char into
			[
			case $V:
				box>>BOX.yorigin=refbox>>BOX.ycorner+gap+1
				box>>BOX.ycorner=box>>BOX.yorigin+height
				endcase
			case $H:
				box>>BOX.xorigin=refbox>>BOX.xcorner+gap+1
				box>>BOX.xcorner=box>>BOX.xorigin+width
				endcase
			]
		checkbox(box)
		]
	]




and Insert() be InsertName("*NString to be inserted",stringlist)

and InsertName(string,array) be
	[
	// get name or string
	if selected!0 le 0 then return

	// now get string
	Ws("*N*N*N*N*N*N")
	@#420=systemdcb
	for n=1 to selected!0 do
		[
		let defstring=array!(selected!n)
		let name=getname(string,defstring)
		if name then
			[
			if array!(selected!n) then Free(zone,array!(selected!n))
			array!(selected!n)=name
			]
		]
	]


and LineUp() be
	[
	// line up selected windows
	if selected!0 le 1 then return
	let number=selected!1

	// get direction
	Ws("*N*N*N*N*N")
	@#420=systemdcb
	Ws("*NType V or H to line up vertically or horizontally.")
	let char=nil
		[
		char=Gets(keys) & #177737
		if char eq $V % char eq $H then break
		] repeat

	// now do the line up
	let box=nil
	let refbox=menu!number
	let deltaX,deltaY=nil,nil
	for n=2 to selected!0 do
		[
		box=menu!(selected!n)
		deltaX=box>>BOX.xcorner-box>>BOX.xorigin
		deltaY=box>>BOX.ycorner-box>>BOX.yorigin
		switchon char into
			[
			case $V:
				box>>BOX.xcorner=deltaX+refbox>>BOX.xorigin
				box>>BOX.xorigin=refbox>>BOX.xorigin
				endcase
			case $H:
				box>>BOX.ycorner=deltaY+refbox>>BOX.yorigin
				box>>BOX.yorigin=refbox>>BOX.yorigin
				endcase
			]
		checkbox(box)
		]
	]



and Name() be InsertName("*NName of box",boxnames)


and Outline() be
	[
	// outline all selected windows
	if selected!0 le 0 return

	// now get outline width
	Ws("*N*N*N*N*N*N")
	@#420=systemdcb
	let box=menu!(selected!1)
	let defbits=box>>BOX.bits
	let width=box>>BOX.xcorner-box>>BOX.xorigin+1
	let height=box>>BOX.ycorner-box>>BOX.yorigin+1
	let bits=getnumber("*NOutline width in bits",defbits)
	if (bits ls 0)%(2*bits gr width)%(2*bits gr height) then bits=defbits
	for n=1 to selected!0 do OutlineBox(menu!(selected!n),bits)
	]


and Refresh() be
	[
	// redo the screen
	Zero(buffer,38*808)
	checklists()
	for n=1 to menu!0 do
		[
		let box=(menu!n)
		let bits=box>>BOX.bits
		OutlineBox(box,bits)
		DotCorners(box)
		WriteBox(box,stringlist!n,fontPtr)
		]
	for n=1 to selected!0 do FlipBox(menu!(selected!n))
	@#420=dcb
	]


and Same() be
	[
	// make all windows same size as first
	if selected!0 le 1 then return
	let number=selected!1

	// now make them all the same size
	let box=nil
	let refbox=menu!number
	let deltaX=refbox>>BOX.xcorner-refbox>>BOX.xorigin
	let deltaY=refbox>>BOX.ycorner-refbox>>BOX.yorigin
	for n=2 to selected!0 do
		[
		box=menu!(selected!n)
		box>>BOX.xcorner=deltaX+box>>BOX.xorigin
		box>>BOX.ycorner=deltaY+box>>BOX.yorigin
		checkbox(box)
		]
	]


and getnumber(string,defaultValue;numargs na) = valof
	[
	Ws(string)
	if na gr 1 then [ Ws(" [");Wns(dsp,defaultValue);Ws("] ") ]
	let v=vec 127
	ReadString(v)
	if v!0 eq 0 then [ if na gr 1 then Wns(dsp,defaultValue);resultis defaultValue ]

	let number=0
	for n=1 to v!0 do
		[
		if (v!n ls $0) % (v!n gr $9) then break
		if number gr 3276 then resultis 32767
		number=number*10+v!n-$0
		]
	resultis number
	]


and getname(string,defaultString) = valof
	[
	Ws(string)
	Ws(" [");if defaultString then Ws(defaultString);Ws("] ")
	let v=vec 127
	ReadString(v)
	if v!0 eq 0 then [ if defaultString then Ws(defaultString) ; resultis false ]

	let name=Allocate(zone,v!0/2+1)
	name>>STRING.length=v!0
	for n=1 to v!0 do name>>STRING.char↑n=v!n
	resultis name
	]

and defaultname() = valof
	[
	// assign a new name to the box
	let name=Allocate(zone,8)
	name>>STRING.length=3
	name>>STRING.char↑1=$b
	name>>STRING.char↑2=$o
	name>>STRING.char↑3=$x
	highestname=highestname+1
	addnumber(name,highestname)
	resultis name
	]

and addnumber(name,number) be
	[
	let R=number rem 10
	if number/10 then addnumber(name,number/10)
	name>>STRING.length=name>>STRING.length+1
	name>>STRING.char↑(name>>STRING.length)=R+$0
	]

and MenuInitHelp() = valof resultis false

and checkbox(box) be
	[
	if box>>BOX.xcorner gr 605 then
		[
		box>>BOX.xorigin=box>>BOX.xorigin-box>>BOX.xcorner+605
		box>>BOX.xcorner=605
		]
	if box>>BOX.ycorner gr 807 then
		[
		box>>BOX.yorigin=box>>BOX.yorigin-box>>BOX.ycorner+807
		box>>BOX.ycorner=807
		]
	]


and AddToSelections(number) be
	[
	selected!0=selected!0+1
	selected!(selected!0)=number
	for n=1 to selected!0-1 do if selected!n eq number then
		[
		DeleteFromMenu(selected,selected!0)
		DeleteFromMenu(selected,n)
		break
		]
	]

and checklists() be
	[
	let veclist=vec MaxLength
	Zero(veclist,MaxLength)
	for n=1 to menu!0 do
		[
		let box=menu!n
		checkbox(box)
		let Xo=box>>BOX.xorigin
		let Yo=box>>BOX.yorigin
		let Xc=box>>BOX.xcorner
		let Yc=box>>BOX.ycorner
		if Xc le Xo % Yc le Yo % Xo ls 0 % Yo ls 0 then
			[
			veclist!0=veclist!0+1
			veclist!(veclist!0)=n
			]
		]
	if veclist!0 then Zero(selected,MaxLength)
	DeleteList(veclist)
	]