;
; repeater.s
;
; Handles things that should auto-repeat
;
;  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.

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

		GET	libs:header
		GET	libs:swis

		GET	libs:stream

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

		GET	sapphire:divide
		GET	sapphire:idle
		GET	sapphire:sapphire
		GET	sapphire:win

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

		AREA	|Sapphire$$Code|,CODE,READONLY

; --- repeater ---
;
; On entry:	R0 == pointer to routine to call
;		R1 == R10 value to pass to routine
;		R2 == R12 value to pass to routine
;
; On exit:	--
;
; Use:		Calls a routine (a) immediately, (b) after the configured
;		keyboard delay rate and (c) repeatedly after the configured
;		keyboard repeat rate.  Calls stop when the user stops
;		pressing the mouse button.
;
;		The routine is called with R0 containing either the number
;		of missed calls since the last one (normally this is 1) --
;		this is intended to be used to implement a kind of buffering
;		of repeats if the operation being performed is a lengthy one
;		-- and with 0 to indicate that the operation is now
;		completed.

		EXPORT	repeater
repeater	ROUT

		STMFD	R13!,{R0-R4,R12,R14}	;Save some registers
		WSPACE	rpt__wSpace		;Locate my workspace nicely

		; --- Set up the workspace ---

		STMIA	R12,{R0-R2}		;Save the call information
		MOV	R0,#1			;This is the first call
		BL	rpt__call		;Call the user's routine

		; --- Now we have to work out the repeat times ---

		MOV	R0,#196			;Read keybd repeat and delay
		MOV	R1,#0			;Set up registers to read...
		MOV	R2,#255			;... without corrupting
		SWI	OS_Byte			;Read the autorepeat info
		STR	R2,rpt__repeat		;Save repeat rate in wspace
		MOVS	R4,R1			;Look after delay time

		MOVEQ	R0,#0			;If no delay, end op now
		BLEQ	rpt__call		;Tell client it's over
		BEQ	%90repeater		;And exit nicely

		; --- Set up the drag box ---
		;
		; We do this for several reasons:
		;
		; * It informs us when the mouse button is finally released.
		;
		; * It keeps the mouse pointer bounded for the duration.
		;
		; * It stops things going wrong if for some reason the mouse
		;   gets unbounded (e.g. by doing a Wimp_SetMode).

		SUB	R13,R13,#56		;Get a drag box block
		MOV	R1,R13			;Point to the block
		SWI	Wimp_GetPointerInfo	;Get the current mouse state
		LDMIA	R13,{R2,R3}		;Read mouse position
		MOV	R1,#7			;This drag is a user type
		STMIA	R13,{R0,R1}		;Save them in the block
		ADD	R14,R13,#24		;Point to parent rectangle
		STMIA	R14!,{R2,R3}		;Save the current mouse...
		STMIA	R14!,{R2,R3}		;... posn to lock in place
		MOV	R1,R13			;Point to the block
		SWI	Wimp_DragBox		;Start the drag operation
		ADD	R13,R13,#56		;Restore the stack now

		; --- Now set up the unknown handler to catch the drag ---

		ADR	R0,rpt__ukEvents	;Point to my handler routine
		MOV	R1,#0			;Don't care about R4
		MOV	R2,#0			;Nothing to pass in R10
		MOV	R3,R12			;Pass workspace in R12
		BL	win_unknownHandler	;Add the handler
		BVS	%90repeater		;If it failed, skip to end

		; --- Set up the alarm for the first repeat ---

		SWI	OS_ReadMonotonicTime	;Read the current time
		ADD	R0,R4,R0		;Add time to keyboard delay
		STR	R0,rpt__alarm		;Store the time of the alarm
		STR	R0,rpt__last		;And this is correct time
		ADR	R1,rpt__alarms		;Point to my alarm handler
		MOV	R2,#0			;Nothing to pass in R10
		MOV 	R3,R12			;Pass workspace in R12
		BL	idle_setAlarm		;Set the alarm call then

90repeater	LDMFD	R13!,{R0-R4,R12,PC}^	;Return to caller if all OK

		LTORG

; --- rpt__call ---
;
; On entry:	R0 == value to pass client in R0
;
; On exit:	--
;
; Use:		Calls client routine.

rpt__call	ROUT

		STMFD	R13!,{R1,R10,R12,R14}	;Save some registers
		LDMIA	R12,{R1,R10,R12}	;Load client's routine data
		MOV	R14,PC			;Set up return address
		MOV	PC,R1			;And call the routine
		LDMFD	R13!,{R1,R10,R12,PC}^	;And return to caller

		LTORG

; --- rpt__alarms ---
;
; On entry:	--
;
; On exit:	--
;
; Use:		Handles alarms while a repeater is in operation.

rpt__alarms	ROUT

		STMFD	R13!,{R0-R4,R14}	;Save some registers

		LDR	R0,rpt__routine		;Load the current addr
		CMP	R0,#-1			;Have we ended?
		BEQ	%70rpt__alarms		;Yes -- do special things

		; --- Things go odd if zero repeat rate set up ---

		LDR	R2,rpt__repeat		;Load current repeat rate
		CMP	R2,#0			;Is it currently zero?
		BEQ	%50rpt__alarms		;Yes -- handle specially

		; --- Work out how many we missed ---
		;
		; We also add in the next alarm, and resynch to the
		; expected time.

		SWI	OS_ReadMonotonicTime	;Get the current time
		MOV	R4,R0			;Look after this value
		LDR	R3,rpt__last		;Get the time I was expecting
		SUB	R0,R4,R3		;Find how late I was called
		MOV	R1,R2			;Get the repeat rate ready
		BL	divide			;Find out how many I missed
		ADD	R0,R0,#1		;Add in one extra repeat
		BL	rpt__call		;Call client code again
		SUB	R14,R2,R1		;Work out next repeat time
		ADD	R0,R4,R14		;Add that to current time
		STR	R0,rpt__last		;This is the new alarm time

		SWI	OS_ReadMonotonicTime	;Get the current time
		MOV	R4,R0			;Look after this now
		SUB	R0,R4,R3		;Find how late I was called
		MOV	R1,R2			;Get the repeat rate ready
		BL	divide			;Find out how many I missed
		SUB	R14,R2,R1		;Work out next repeat time
		ADD	R0,R4,R14		;Add that to current time
		STR	R0,rpt__alarm		;This is the new alarm time

		ADR	R1,rpt__alarms		;Point to me again
		MOV	R2,R10			;Pass the dialogue handle
		MOV	R3,R12			;And my workspace pointer
		BL	idle_setAlarm		;Add in next alarm handler

		LDMFD	R13!,{R0-R4,PC}^	;And return to caller

		; --- If no repeat rate, stop the whole operation ---

50rpt__alarms	MOV	R0,#1			;Just send one call
		BL	rpt__call		;Send that off to the client
		MOV	R1,#-1			;Tell Wimp to stop dragging
		SWI	Wimp_DragBox		;Send that off nicely
		ADR	R0,rpt__ukEvents	;Point to the unknown handler
		MOV	R1,#0			;I'm passing 0 in R4
		MOV	R2,#0			;And 0 in R10 too
		MOV	R3,R12			;But R12 is my workspace
		BL	win_removeUnknownHandler ;Remove it from the list
		MOV	R0,#0			;Tell client it's all over
		BL	rpt__call		;Finish that off nicely
		LDMFD	R13!,{R0-R4,PC}^	;And return to caller

		; --- The repeater was ended before its time ---
		
70rpt__alarms	MOV	R1,#-1			;Tell Wimp to stop dragging
		SWI	Wimp_DragBox		;Send that off nicely
		ADR	R0,rpt__ukEvents	;Point to the unknown handler
		MOV	R1,#0			;I'm passing 0 in R4
		MOV	R2,#0			;And 0 in R10 too
		MOV	R3,R12			;But R12 is my workspace
		BL	win_removeUnknownHandler ;Remove it from the list
		LDMFD	R13!,{R0-R4,PC}^	;And return to caller

		LTORG

; --- rpt_end ---
;
; On entry:	--
;
; On exit:	--
;               
; Use:		Ends a repeater before the drag is released. No final
;		0 is sent to the handler.

		EXPORT	rpt_end
rpt_end		ROUT

		STMFD	R13!,{R12,R14}		;Stack registers
		WSPACE	rpt__wSpace		;Locate my workspace nicely
		MOV	R14,#-1			;Get a silly routine value
		STR	R14,rpt__routine	;Store in routine address
		LDMFD	R13!,{R12,PC}^		;Return to caller
		
		LTORG

; --- rpt__ukEvents ---
;
; On entry:	R0 == event code
;		R1 == pointer to event information
;
; On exit:	--
;
; Use:		Picks up end-of-drag events from the WindowMangler and
;		diables the alarm for the repeat op.

rpt__ukEvents	ROUT

		CMP	R0,#7			;Is this a drag event?
		MOVNES	PC,R14			;No -- return right away
		STMFD	R13!,{R0-R3,R14}	;Save some registers away

		; --- Remove the alarm and unknown handlers ---

		LDR	R0,rpt__alarm		;Get the time it's due for
		ADR	R1,rpt__alarms		;Point to my alarms routine
		MOV	R2,#0			;I passed 0 as it's R10
		MOV	R3,R12			;Get its R12 value
		BL	idle_removeAlarm	;And remove it from the list
		ADR	R0,rpt__ukEvents	;Point to the unknown handler
		MOV	R1,#0			;I'm passing 0 in R4
		BL	win_removeUnknownHandler ;Remove it from the list

		; --- Tell the client it's over ---

		MOV	R0,#0			;Say this is it then
		BL	rpt__call		;Send the event over
		LDMFD	R13!,{R0-R3,PC}^	;Return to caller

		LTORG

rpt__wSpace	DCD	0

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

		^	0,R12
rpt__wStart	#	0

rpt__routine	#	4			;Client routine to call
rpt__R10	#	4			;R10 value to pass routine
rpt__R12	#	4			;R12 value to pass routine
rpt__repeat	#	4			;Current autorepeat rate
rpt__alarm	#	4			;Time for next alarm pop
rpt__last	#	4			;When last pop expected

rpt__wSize	EQU	{VAR}-rpt__wStart

		AREA	|Sapphire$$LibData|,CODE,READONLY

		DCD	rpt__wSize
		DCD	rpt__wSpace
		DCD	0
		DCD	0

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

		END
