;
; nopoll.s
;
; Handling nonpolling dialogue boxes (MDW)
;
;  1994-1998 Straylight
;

;----- Licensing note -------------------------------------------------------
;
; This file is part of Straylight's Sapphire library.
;
; Sapphire is free software; you can redistribute it and/or modify
; it under the terms of the GNU General Public License as published by
; the Free Software Foundation; either version 2, or (at your option)
; any later version.
;
; Sapphire is distributed in the hope that it will be useful,
; but WITHOUT ANY WARRANTY; without even the implied warranty of
; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
; GNU General Public License for more details.
;
; You should have received a copy of the GNU General Public License
; along with Sapphire.  If not, write to the Free Software Foundation,
; 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.

;----- Some notes before we start -------------------------------------------
;
; This is utterly different to the STEEL nopoll.  It nabs a prehandler, and
; then uses it to direct fake events at the nonpolling window, which it then
; can pick up and do whatever it wants with.  Fake events generated so far:
;
;   Click -- for any click on an icon
;   Key -- for *only* Escape and Return -- we can't risk people using
;      Wimp_ProcessKey or strange caret-moving calls
;   Redraw -- when the thing gets opened, we fake a redraw for it, so that
;      it gets painted nicely on the screen
;
; Basically, you get much more control over the dialogue box than you would
; with STEEL, but at the small price of a little more responsibility for
; what actually happens.  So your nonpolling dialogue boxes can be that much
; cleverer!

;----- Standard header ------------------------------------------------------

		GET	libs:header
		GET	libs:swis

;----- External dependencies ------------------------------------------------

		GET	sapphire:defHandler
		GET	sapphire:event
		GET	sapphire:hour
		GET	sapphire:sapphire
		GET	sapphire:screen

;----- Main code ------------------------------------------------------------

		AREA	|Sapphire$$Code|,CODE,READONLY

; --- nopoll_open ---
;
; On entry:	R0 == a window handle to take over
;
; On exit:	--
;
; Use:		Sets up the window with the given handle to be a nonpolling
;		dialogue box.  The window must already be open on the screen.
;		This call will force it to be painted on the screen, and
;		then start faking events for it.

		EXPORT	nopoll_open
nopoll_open	ROUT

		STMFD	R13!,{R12,R14}		;Save some registers
		WSPACE	nopoll__wSpace		;Load my workspace pointer

		; --- Make sure there's only one window ---

		LDR	R14,nopoll__window	;Load the nonpolling window
		CMP	R14,#0			;Is it nonzero?
		LDMNEFD	R13!,{R12,PC}^		;Yes -- ignore the new one

		; --- Remember this window handle ---

		STR	R0,nopoll__window	;Store this handle away
		LDR	R14,nopoll__flags	;Load my flags word
		ORR	R14,R14,#npFlag__redraw	;Window needs painting
		STR	R14,nopoll__flags	;Store flags away again
		TST	R0,#1<<31		;Is the top bit set?
		LDMNEFD	R13!,{R12,PC}^		;Yes -- do nothing else

		; --- Now read the initial mouse state ---

		STMFD	R13!,{R0-R6}		;Save some more registers
		SWI	OS_Mouse		;Read the mouse position
		STR	R2,nopoll__buttons	;Store the button state away

		; --- Read the window position ---

		LDR	R0,[R13,#0]		;Reread the window handle
		SUB	R13,R13,#36		;Make space for window blk
		STR	R0,[R13,#0]		;Store handle in the block
		MOV	R1,R13			;Point to the block
		SWI	Wimp_GetWindowState	;Get the window information

		; --- Remember the origin position ---

		LDMIB	R13,{R1-R6}		;Load the coordinates out
		SUB	R5,R1,R5		;Get the x origin position
		SUB	R6,R4,R6		;Get the y origin position
		ADR	R14,nopoll__ox		;Point to the origin cache
		STMIA	R14,{R5,R6}		;Store these values away

		; --- Constrain the mouse ---

		BL	screen_getInfo		;Get the screen information
		ADD	R0,R0,#screen_dx	;Point to the pixel sizes
		LDMIA	R0,{R5,R6}		;Load the pixel sizes
		SUB	R1,R1,R5		;Knock a pixel off the left
		SUB	R2,R2,R6		;And off the bottom

		; --- Build the constrain block ---
		;
		; We build the parameter block in registers using lots of
		; barrel shifting and then stuff it in the stack.  This is
		; much quicker and also takes less space.  We assume that
		; all the coordinates are two bytes wide already.

		MOV	R0,#1			;Subreason code
		ORR	R0,R0,R1,LSL #8		;Move in left hand side
		ORR	R0,R0,R2,LSL #24	;Move in the bottom byte
		MOV	R1,R2,LSR #8		;Get top byte of bottom edge
		ORR	R1,R1,R3,LSL #8		;Get right hand side in
		ORR	R1,R1,R4,LSL #24	;Get bottom byte of top edge
		MOV	R2,R4,LSR #8		;And the top byte of top edge
		STMFD	R13!,{R0-R2}		;Save them on the stack
		MOV	R0,#21			;OS_Word mouse operations
		MOV	R1,R13			;Point to the block
		SWI	XOS_Word		;Perform the constrain

		ADD	R13,R13,#48		;Return the block nicely
		LDMFD	R13!,{R0-R6,R12,PC}^	;Return to caller now

		LTORG

; --- nopoll_close ---
;
; On entry:	R0 == return value for nopoll_process (can be anything)
;
; On exit:	--
;
; Use:		Tells nopoll that the nonpolling window has been killed,
;		and hence that polling can return to normal again.  You can
;		specify a return value to give from nopoll_process (if that
;		system is being used).

		EXPORT	nopoll_close
nopoll_close	ROUT

		STMFD	R13!,{R0-R4,R12,R14}	;Save some registers
		WSPACE	nopoll__wSpace		;Find my workspace
		STR	R0,nopoll__return	;Save the return value away

		; --- Clear the window handle ---

		LDR	R0,nopoll__window	;Load the old window
		MOV	R14,#0			;No nonpolling window
		STR	R14,nopoll__window	;Store it over the window
		TST	R0,#1<<31		;Is the top bit set?
		LDMNEFD	R13!,{R0-R4,R12,PC}^	;Yes -- do nothing else then

		; --- Now release the mouse pointer ---

		BL	screen_getInfo		;Read the screen information
		ADD	R0,R0,#screen_width	;Point to the right bit
		LDMIA	R0,{R1-R4}		;Load screen and pixel sizes
		SUB	R3,R1,R3		;Reduce screen width by 1
		SUB	R4,R2,R4		;Reduce screen height by 1

		MOV	R0,#&00000001		;Bottom word for constrain
		MOV	R1,R3,LSL #8		;Shift the width word in
		ORR	R1,R1,R4,LSL #24	;Move bottom byte of width in
		MOV	R2,R4,LSR #8		;And get top byte in R2
		STMFD	R13!,{R0-R2}		;Create the block on stack
		MOV	R1,R13			;Point to the block
		MOV	R0,#21			;Reason code
		SWI	XOS_Word		;Do the constraining

		ADD	R13,R13,#12		;Skip past the OS_Word block
		LDMFD	R13!,{R0-R4,R12,PC}^	;Return to caller

		LTORG

; --- nopoll_init ---
;
; On entry:	--
;
; On exit:	--
;
; Use:		Initialises nopoll so it can be used.

		EXPORT	nopoll_init
nopoll_init	ROUT

		STMFD	R13!,{R0,R1,R12,R14}	;Save some registers

		; --- Make sure I'm not already going ---

		WSPACE	nopoll__wSpace		;Load my workspace pointer
		LDR	R14,nopoll__flags	;Load my flags word
		TST	R14,#npFlag__inited	;Am I initialised yet?
		MOV	R0,#0			;Zero window handle
		STR	R0,nopoll__window	;No nonpolling window yet
		LDMNEFD	R13!,{R0,R1,R12,PC}^	;Yes -- return right now

		; --- Register my prefilter nicely ---

		BL	event_init		;Initialise the event system
		ADR	R0,nopoll__filter	;Point to my filter routine
		MOV	R1,R12			;Pass my workspace pointer
		BL	event_preFilter	;Register the prefilter
		LDMFD	R13!,{R0,R1,R12,PC}^	;Return to caller

nopoll__wSpace	DCD	0

		LTORG

; --- nopoll__filter ---
;
; On entry:	R0 == an event mask
;		R1 == pointer to event block
;		R2 == earliest time to return with null event
;		R3 == pointer to poll word if any
;
; On exit:	Carry clear and registers preserved, or carry set and:
;
;		R0 == WIMP event code
;		R1 preserved, block updated
;
; Use:		Generates WIMP-like events from the nonpolling window.

nopoll__filter	ROUT

		; --- Pass on quickly if nothing to do ---

		STMFD	R13!,{R14}		;Just save the link register
		LDR	R14,nopoll__window	;Is there a nonpolling window
		CMP	R14,#0			;Just check it's nonzero
		LDMEQFD	R13!,{PC}^		;No -- return right now

		; --- Now dispatch redraw events if necessary ---

		STMFD	R13!,{R10}		;Save another register
		BIC	R10,R14,#1<<31		;Look after the window handle
		LDR	R14,nopoll__flags	;Load my flags word
		TST	R14,#npFlag__redraw	;Do I have to fake a redraw?
		BEQ	%00nopoll__filter	;No -- skip ahead

		BIC	R14,R14,#npFlag__redraw	;Clear the flag
		STR	R14,nopoll__flags	;Store the new flags word
		MOV	R0,#1			;Pass a redraw reason code
		STR	R10,[R1,#0]		;Store window handle in block
		LDMFD	R13!,{R10,R14}		;Load the registers back
		ORRS	PC,R14,#C_flag		;Return the fake event

		; --- Find out if a key was pressed ---

00		STMFD	R13!,{R1-R7}		;Save some more registers

		SUB	R13,R13,#8		;Make space for hourglass blk
		MOV	R0,R13			;Point to the block
		BL	hour_suspend		;Turn hourglass off a while

01		MOV	R0,#&81			;Read keyboard status
		MOV	R1,#0			;No delay on key reading
		MOV	R2,#0			;No delay on key reading
		SWI	OS_Byte			;Read the key value
		CMP	R1,#&0D			;Was Return pressed?
		CMPNE	R1,#&1B			;Or was it Escape?
		BNE	%10nopoll__filter	;No -- handle mouse buttons

		; --- Build a dummy caret record in the block ---

		MOV	R0,R13			;Point to hourglass block
		BL	hour_resume		;Restore old hourglass state
		ADD	R13,R13,#8		;And restore the stack ptr

		MOV	R0,R1
		LDMFD	R13!,{R1}		;Restore the Wimp block ptr
		STR	R10,[R1,#0]		;Store window handle away
		STR	R0,[R1,#24]		;Save the key number too
						;Don't bother with the rest
		MOV	R0,#8			;Fake a key event
		LDMFD	R13!,{R2-R7,R10,R14}	;Restore registers
		ORRS	PC,R14,#C_flag		;And fake the event

		; --- Read the mouse status ---

10		SWI	OS_Mouse		;Read the mouse status
		LDR	R14,nopoll__buttons	;Read the old button state
		STR	R2,nopoll__buttons	;Store new button state
		BIC	R2,R2,R14		;Leave only newly clicked
		BICS	R2,R2,#2		;And clear the menu button
		BEQ	%01nopoll__filter	;If nothing clicked, loop

		LDR	R4,[R13,#8]		;Load the Wimp block pointer
		STMIA	R4,{R0-R2,R10,R12,R14}	;Save the registers away

		; --- Now find which icon got clicked ---

		ADR	R14,nopoll__ox		;Point to the origin coords
		LDMIA	R14,{R6,R7}		;Load them into registers
		SUB	R6,R0,R6		;Convert mouse coords to...
		SUB	R7,R1,R7		;... window-relative ones
		SUB	R13,R13,#40		;Create an icon block
		STR	R10,[R13,#0]		;Save the window handle in it
		MOV	R10,#-1			;No icons found yet
		MOV	R5,#0			;Start searching from icon 0

		; --- Loop through the icons nicely now ---

20		STR	R5,[R13,#4]		;Store it in the block
		MOV	R1,R13			;Point to my icon block
		SWI	Wimp_GetIconState	;Get the icon information
		LDR	R14,[R13,#24]		;Load the icon flags out
		CMP	R14,#&00800000		;Is it deleted-only?
		BEQ	%30nopoll__filter	;Yes -- we've scanned 'em all
		EOR	R14,R14,#&00DF0000	;Toggle deleted, shaded, ESG
		TST	R14,#&00800000		;Is it deleted?
		TSTNE	R14,#&00400000		;Is it shaded?
		TSTNE	R14,#&001F0000		;Or is the ESG all 1s?
		ADDEQ	R5,R5,#1		;Check the next icon along
		BEQ	%20nopoll__filter	;Yes -- skip this icon

		ADD	R1,R13,#8		;Point to icon coordinates
		LDMIA	R1,{R0-R3}		;Load the icon coordinates
		CMP	R0,R6
		CMPLE	R1,R7
		CMPLE	R6,R2
		CMPLT	R7,R3
		MOVLT	R10,R5			;If over icon, remember no.

		ADD	R5,R5,#1		;Check the next icon along
		B	%20nopoll__filter	;And carry on

		; --- Return a fake mouse click event ---

30		ADD	R13,R13,#40		;Recover the icon block
		MOV	R0,R13			;Point to hourglass block
		BL	hour_resume		;Restore hourglass state
		ADD	R13,R13,#8		;And restore stack block
		STR	R10,[R4,#16]		;Store the icon number away
		MOV	R0,#6			;Fake a mouse click event
		LDMFD	R13!,{R1-R7,R10,R14}	;Restore loads of registers
		ORRS	PC,R14,#C_flag		;Return to caller at last

		LTORG

; --- nopoll_process ---
;
; On entry:	--
;
; On exit:	R0 == value passed to nopoll_close
;
; Use:		Processes a nonpolling window until it calls nopoll_close.
;		It then returns the value passed to nopoll_close in R0,
;		which can be defined in any way you want.
;
;		Some notes on the use of this routine:
;
;		* It calls event_poll, so any functions that get called
;		  after the normal event_poll don't get called.  Since the
;		  Wimp isn't actually being polled at all, this isn't a
;		  real problem as long as your handlers are registered at the
;		  event filter level or higher (e.g. win event handlers or
;		  even dbox handlers).
;
;		* It uses up an extra 256 bytes of stack for a poll block.
;		  If you think you might miss this stack space, then you'd
;		  better not use this routine.
;
;		* It isn't reentrant, but then again, nor is the rest of the
;		  nopoll system -- you can only have one nonpolling box open
;		  at a time.

		EXPORT	nopoll_process
nopoll_process	ROUT

		STMFD	R13!,{R1-R3,R12,R14}	;Save some registers
		WSPACE	nopoll__wSpace		;Locate my workspace
		SUB	R13,R13,#256		;Make a poll block (!)

00		LDR	R14,nopoll__window	;Is there a nonpolling window
		CMP	R14,#0			;Quick check...
		BEQ	%10nopoll_process	;No -- jump ahead then
		MOV	R1,R13			;Point to the poll block
		BL	event_poll		;Get a dodgy event back
		BLCC	defHandler		;If not handled, handle it
		B	%00nopoll_process	;And loop back again

10		LDR	R0,nopoll__return	;Load the return value
		ADD	R13,R13,#256		;Restore the stack pointer
		LDMFD	R13!,{R1-R3,R12,PC}^	;Return to caller again

		LTORG

;----- Workspace ------------------------------------------------------------

		^	0,R12
nopoll__wStart	#	0

nopoll__flags	#	4			;Various interesting flags
nopoll__window	#	4			;The nonpolling window handle
nopoll__buttons	#	4			;Button state for icons
nopoll__ox	#	4			;Window origin x position
nopoll__oy	#	4			;Window origin y position
nopoll__return	#	4			;nopoll_process return value

nopoll__wSize	EQU	{VAR}-nopoll__wStart	;My workspace size

npFlag__inited	EQU	(1<<0)			;Am I initialised yet?
npFlag__redraw	EQU	(1<<1)			;Does window need redrawing?

		AREA	|Sapphire$$LibData|,CODE,READONLY

		DCD	nopoll__wSize
		DCD	nopoll__wSpace
		DCD	0
		DCD	nopoll_init

;----- That's all, folks ----------------------------------------------------

		END
