**************************************************************************
*                                                                        *
*  Memory Observer    -    ©1995 Simone Tellini Software                 *
*                                                                        *
*  13/06/95    First Version  by Simone Tellini                          *
*                                                                        *
*  Feel free to modify (and improve ;) this source, but please           *
*  leave my name in it!                       - Thanks, Simone           *
*                                                                        *
**************************************************************************


	opt !	; for PhxAss: all optimizations on

	incdir /include/

	include dos/dos_lib.i
	include exec/exec_lib.i
	include exec/nodes.i
	include intuition/intuition.i
	include intuition/intuition_lib.i
	include gadtools_lib.i
	include libraries/gadtools.i
	include mymacros.i

	include sources/mo/MemoryObs_gui.i


	SECTION	MemObserver,CODE

	include iconstartup.i	; let us start from WB

	move.l	4,a6	; let's open some libs...
	openlib	dos,0,exit
	openlib	gad,0,closedos
	openlib	int,0,closegad

	call	CreateMsgPort	; for timer.device's messages
	move.l	d0,timerport
	beq	closeint
	move.l	d0,a0
	move.b	MP_SIGBIT(a0),d1
	moveq	#1,d0
	lsl.l	d1,d0
	move.l	d0,bitmask
	move.l	d0,timermask

	move.l	#IOTV_SIZE,d0
	call	CreateIORequest
	move.l	d0,timerreq
	beq	deleteport

	move.l	d0,a1	; open the timer device
	move.w	#TR_ADDREQUEST,IO_COMMAND(a1)
	move.l	#20,IO_SIZE+TV_MICRO(a1)
	lea	timer_name(pc),a0
	moveq	#UNIT_VBLANK,d0	; 1/50 sec is what I need
	cldat	d1
	call	OpenDevice
	tst.l	d0
	bne	deletereq

* I call the routines of the module MemoryObs_gui.o with 'bsr', so
* I _MUST_ link the two modules together with the SmallCode parameter.

	bsr	SetupScreen	; lock it & get VisualInfos
	tst.l	d0	; success?
	bne	closescr	; nope...

	move.l	Scr,a0	; compute the value for the
	move.b	sc_WBorTop(a0),d0	; height of the window
	ext.w	d0	; "zipped"
	inc.w	d0	; height = sc_WBorTop + Font.ta_YSize + 1
	move.l	sc_Font(a0),a0
	add.w	ta_YSize(a0),d0
	move.w	d0,MainZoom+6

	sub.w	#10,d0	; fit the height of the window
	lea	MainWTags,a0	; to the height of the screen font
	add.w	d0,WT_HEIGHT(a0)

	bsr	OpenMainWindow	; open my window
	tst.l	d0
	bne	closewnd

	move.l	MainWnd,a0	; get its signal bit...
	move.l	wd_UserPort(a0),a0
	move.b	MP_SIGBIT(a0),d1
	moveq	#1,d0
	lsl.l	d1,d0
	or.l	d0,bitmask	; ...and join it with the timer's one

	move.l	timerreq,a1	; start the timer
	call	SendIO

mainloop	move.l	4,a6	; wait for a signal
	move.l	bitmask,d0
	call	Wait

	move.l	timermask,d1	; is it the timer that calls? :)
	and.l	d0,d1
	beq.s	.notimer	; nope...

	btst	#MONITOR,flags	; we should monitor the
	beq.s	.nomon	; memory?

	move.l	address,a0
	move.w	size+2(pc),d1
	cmp.w	#2,d1	; if we aren't monitoring
	beq.s	.noround	; a single byte, we must
	move.l	a0,d0	; round to an even address
	and.l	#~1,d0
	move.l	d0,a0
.noround	cldat	d0
	lea	size_routines(pc),a1
	add.w	d1,d1
	add.w	d1,d1
	move.l	0(a1,d1.w),a1
	jsr	(a1)	; get the value!
	move.l	d0,d7

	cmp.l	last,d0	; did it change since last read?
	beq.s	.nomon

	btst	#_BEEP,flags	; is the beeper active?
	beq.s	.nobeep
	beep		; a simple but useful macro ;-)

.nobeep	move.l	d7,hex

	move.l	HexGadget,a0		; update the displays...
	lea	hex_tag(pc),a3
	bsr	SetTag

	move.l	DecGadget,a0
	lea	hex_tag(pc),a3
	bsr	SetTag

	move.l	d7,last

	lea	last2,a0	; Replace the 0's with
	move.l	d7,(a0)	; a space, so the gadget
	moveq	#3,d0	; displays all the bytes
.search	tst.b	(a0)+	; I want it to...
	bne.s	.next
	move.b	#' ',-1(a0)
.next	dbra	d0,.search

	move.l	AscGadget,a0
	lea	asc_tag(pc),a3
	bsr	SetTag

	bsr	Binary

.nomon	move.l	4,a6	; re-start timer...
	move.l	timerreq,a1
	add.l	#20,IO_SIZE+TV_MICRO(a1)
	call	SendIO

.notimer	bsr	HandleMainIDCMP	; check messages from the window

	btst	#QUIT,flags	; should we quit?
	beq	mainloop

closewnd	bsr	CloseMainWindow
closescr	bsr	CloseDownScreen	; unlock & free vi
closetimer	move.l	4,a6	; close timer.device
	move.l	timerreq,a1
	call	CloseDevice
deletereq	move.l	4,a6	; delete the io request
	move.l	timerreq,a0
	call	DeleteIORequest
deleteport	move.l	4,a6	; delete the msg port
	move.l	timerport,a0
	call	DeleteMsgPort
closeint	closelib	int	; close all the libs
closegad	closelib	gad
closedos	closelib	dos
exit	rts

SetTag	move.l	gad_base,a6	; little sub that sets a tag... ;)
	move.l	MainWnd,a1
	cladd	a2
	call	GT_SetGadgetAttrsA
	rts

long	move.l	(a0),d0
	rts
word	move.w	(a0),d0
	rts
byte	move.b	(a0),d0
	rts

******** Routines called by HandleMainIDCMP

	XDEF	MainCloseWindow
MainCloseWindow	bset	#QUIT,flags
	rts

	XDEF	MainVanillaKey
MainVanillaKey	move.w	im_Code(a5),d5	; a5 = ^IntuiMsg
	cmp.b	#$20,d5
	blt.s	.raw
	cmp.b	#$7E,d5
	bgt.s	.raw
	or.b	#$20,d5		; lowercase
.raw	moveq	#KEYS-1,d1
	move.w	d1,d0
	lea	keytable(pc),a0
.search	cmp.b	(a0)+,d5	; search the key in the table
	dbeq	d1,.search
	tst.w	d1	; found?
	bge.s	.ok	; yes!
	rts		; do nothing.
.ok	sub.w	d1,d0	; get the right function...
	lsl.w	#2,d0
	lea	funtable(pc),a0
	move.l	0(a0,d0.w),a0
	jmp	(a0)	; ...and execute it!


	XDEF	MonClicked
MonClicked	lea	checked_tag(pc),a3	; toggle the check
	bchg	#MONITOR,flags
	seq	7(a3)
	move.l	MonGadget,a0
	move.l	a3,a4
	bra	SetTag

	XDEF	BeepClicked
BeepClicked	lea	checked_tag(pc),a3
	bchg	#_BEEP,flags
	seq	7(a3)
	move.l	BeepGadget,a0
	bra	SetTag

	XDEF	SignClicked
SignClicked	lea	checked_tag(pc),a3
	bchg	#SIGN,flags
	seq	7(a3)
	move.l	SignGadget,a0
	move.l	a3,a4
	bsr	SetTag
	tst.b	7(a4)	; if checked...
	beq.s	.unsigned
	move.b	#'d',sign	; ...set the "d" fmt type...
	bra.s	.ok
.unsigned	move.b	#'u',sign	; ...else set the "u"
.ok	move.l	DecGadget,a0
	lea	format_tag(pc),a3
	move.l	#FormatStr,4(a3)
	bra	SetTag

	XDEF	AdrClicked
AdrClicked	move.l	AdrGadget,a0
	move.l	gg_SpecialInfo(a0),a0
	move.l	si_Buffer(a0),a0
	moveq	#0,d1
	push.l	a0
.search_end	tst.b	(a0)+	; count chars
	beq.s	.found
	inc.w	d1
	bra.s	.search_end
.found	pop.l	a0
	move.b	d1,d3	; store it
	lsr.w	#1,d1	; divide by 2
	lea	address,a1
	clr.l	(a1)	; clear the address
	moveq	#4,d2	; how many bytes shall we skip?
	sub.b	d1,d2
	add.l	d2,a1
	dec.w	d1	; because of dbra
	and.b	#1,d3	; the number of chars is even?
	beq.s	.loop	; yes, alright!
	inc.w	d1	; no, there's a lone nibble...
	cldat	d2
	dec.l	a1
	bra.s	.secondnibble
.loop	move.b	(a0)+,d0	; get the first nibble
	bsr.s	ConvertNibble	; convert it
	move.b	d0,d2	; store it
.secondnibble	move.b	(a0)+,d0	; get the second nibble
	bsr.s	ConvertNibble	; convert it
	lsl.b	#4,d2	; a little shift... (e.g. 0f  ->  f0)
	or.b	d2,d0	; join the nibbles (e.g. f0 0f -> ff)
	move.b	d0,(a1)+	; store the number
	dbra	d1,.loop	; let's do it again! ;)
	rts

ConvertNibble	cmp.b	#'0',d0	; is it between '0' and '9'?
	blt.s	.error
	cmp.b	#'9',d0
	bgt.s	.hex
	sub.b	#'0',d0	; ok, transform to binary
	rts
.hex	or.b	#$20,d0	; lovercase
	cmp.b	#'a',d0	; is it between 'a' and 'f'?
	blt.s	.error
	cmp.b	#'f',d0
	bgt.s	.error
	sub.b	#'a'-10,d0	; ok, trasform to binary
	rts
.error	beep
	addq.l	#4,sp	; remove the return address of
	rts		; the call so to return to mainloop

	XDEF	SizeClicked
SizeClicked	lea	size_tag(pc),a3
	move.w	im_Code(a5),6(a3)
	move.l	SizeGadget,a0
	bsr	SetTag
	lea	setsize_routines(pc),a0
	move.w	im_Code(a5),d0
	add.w	d0,d0
	add.w	d0,d0
	move.l	0(a0,d0.w),a0
	jmp	(a0)

LClicked	clr.w	size+2
	move.l	SizeGadget,a0
	lea	size_tag(pc),a3
	bsr	SetTag
	bra.s	SetLong
WClicked	move.w	#1,size+2
	move.l	SizeGadget,a0
	lea	size_tag(pc),a3
	bsr	SetTag
	bra.s	SetWord
BClicked	move.w	#2,size+2
	move.l	SizeGadget,a0
	lea	size_tag(pc),a3
	bsr	SetTag

SetByte	lea	format_tag(pc),a3	; these routines change the
	move.l	#FmtByteHex,4(a3)	; /look/ of the gadgets... :)
	move.l	HexGadget,a0
	bsr	SetTag
	move.l	AscGadget,a0
	lea	asc_tag(pc),a3
	move.l	#last2+3,4(a3)
	bsr	SetTag
	bra.s	Binary

SetLong	lea	format_tag(pc),a3
	move.l	#FmtLongHex,4(a3)
	move.l	HexGadget,a0
	bsr	SetTag
	move.l	AscGadget,a0
	lea	asc_tag(pc),a3
	move.l	#last2,4(a3)
	bsr	SetTag
	bra.s	Binary

SetWord	lea	format_tag(pc),a3
	move.l	#FmtWordHex,4(a3)
	move.l	HexGadget,a0
	bsr	SetTag
	move.l	AscGadget,a0
	lea	asc_tag(pc),a3
	move.l	#last2+2,4(a3)
	bsr	SetTag

Binary	move.w	size+2(pc),d0	; how many bytes must we convert?
	add.w	d0,d0
	lea	lenght(pc),a0
	move.w	0(a0,d0.w),d3
	moveq	#4,d4
	sub.w	d3,d4
	dec.w	d4
	lea	bin_space,a1
	move.b	#'%',(a1)+
	lea	last,a2
	add.w	d4,a2	; skip the bytes not used
.loop	move.b	(a2)+,d1
	moveq	#7,d2	; Bits in a byte - 1
.convertloop	btst	d2,d1	; it's as easy as that... :)
	beq.s	.zero
	move.b	#'1',(a1)+
	bra.s	.next
.zero	move.b	#'0',(a1)+
.next	dbra	d2,.convertloop
	move.b	#'.',(a1)+
	dbra	d3,.loop
	clr.b	-(a1)
	move.l	BinGadget,a0
	lea	bin_tag(pc),a3
	bra	SetTag

ActivateAdr	move.l	AdrGadget,a0
	move.l	MainWnd,a1
	cladd	a2
	move.l	int_base,a6
	jmp	_LVOActivateGadget(a6)


gad_name	dc.b 'gadtools.library',0
int_name	dc.b 'intuition.library',0
dos_name	dc.b 'dos.library',0

timer_name	dc.b 'timer.device',0

version	dc.b '$VER: Memory_Observer 1.0 (14.6.95) by Simone Tellini',0

FormatStr	dc.b '#%010l'
sign	dc.b 'u',0

FmtLongHex	dc.b '$%08lx',0
FmtWordHex	dc.b '$%04lx',0
FmtByteHex	dc.b '$%02lx',0

keytable	dc.b 'm','e','s','l','w','b','a'
KEYS	EQU	*-keytable

	cnop 0,4
funtable	dc.l MonClicked,BeepClicked,SignClicked
	dc.l LClicked,WClicked,BClicked
	dc.l ActivateAdr

lenght	dc.w 3,1,0

size_routines	dc.l long,word,byte
setsize_routines	dc.l SetLong,SetWord,SetByte

checked_tag	dc.l GTCB_Checked,0,TAG_END

format_tag	dc.l GTNM_Format,FormatStr,TAG_END

size_tag	dc.l GTMX_Active
size	dc.l 0,TAG_END

hex_tag	dc.l GTNM_Number
hex	dc.l 0,TAG_END

asc_tag	dc.l GTTX_Text,last2,TAG_END

bin_tag	dc.l GTTX_Text,bin_space,TAG_END


	SECTION	MemObserver_DataSpace,BSS

	XDEF	gad_base
	XDEF	int_base
gad_base	ds.l 1
dos_base	ds.l 1
int_base	ds.l 1

timerport	ds.l 1
timerreq	ds.l 1

bitmask	ds.l 1
timermask	ds.l 1

address	ds.l 1
last	ds.l 1
last2	ds.l 1	; for the ASCII text gadget
	ds.b 1	; end of string

bin_space	ds.b (4*8)+5

flags	ds.b 1
QUIT	EQU	0	; flags bits
MONITOR	EQU	1
SIGN	EQU	2
_BEEP	EQU	3


	END