From: Bernie Innocenti <bernie@codewiz.org>
Date: Fri, 11 Mar 2011 06:02:27 +0000 (-0500)
Subject: Initial commit
X-Git-Url: https://codewiz.org/gitweb?a=commitdiff_plain;h=refs%2Fheads%2Fmaster;p=amiga%2FBoopsiListView.git

Initial commit
---

06ee78d1a44acc2d40194554674af6bacaff012b
diff --git a/BoopsiStubs.h b/BoopsiStubs.h
new file mode 100644
index 0000000..a8202c7
--- /dev/null
+++ b/BoopsiStubs.h
@@ -0,0 +1,181 @@
+#ifndef BOOPSISTUBS_H
+#define BOOPSISTUBS_H
+/*
+**	$VER: BoopsiStubs.h 1.2 (1.9.97)
+**
+**	Copyright (C) 1997 Bernardo Innocenti. All rights reserved.
+**
+**	Use 4 chars wide TABs to read this file
+**
+**	Using these inline versions of the amiga.lib boopsi support functions
+**	results in faster and smaller code against their linked library
+**	counterparts. When debug is active, these functions will also
+**	validate the parameters you pass in.
+**
+*/
+
+#ifndef COMPILERSPECIFIC_H
+#include "CompilerSpecific.h"
+#endif /* COMPILERSPECIFIC_H */
+
+#ifndef DEBUG_H
+#include "Debug.h"
+#endif /* DEBUG_H */
+
+/* This definition will prevent the redefinition of the following stubs with
+ * their amiga.lib equivalents. This only works if you are using a patched
+ * version of <clib/alib_protos.h>
+ */
+#define USE_BOOPSI_STUBS
+
+
+
+/* the _HookPtr type is a shortcut for a pointer to a hook function */
+
+typedef ASMCALL ULONG (*_HookPtr)
+	(REG(a0, Class *), REG(a2, Object *), REG(a1, APTR));
+
+INLINE ULONG CoerceMethodA (struct IClass *cl, Object *o, Msg message)
+{
+	ASSERT_VALIDNO0(cl)
+	ASSERT_VALID(o)
+
+	return ((_HookPtr)cl->cl_Dispatcher.h_Entry) ((APTR)cl, (APTR)o, (APTR)message);
+}
+
+INLINE ULONG DoSuperMethodA (struct IClass *cl, Object *o, Msg message)
+{
+	ASSERT_VALIDNO0(cl)
+	ASSERT_VALID(o)
+
+	cl = cl->cl_Super;
+	return ((_HookPtr)cl->cl_Dispatcher.h_Entry) ((APTR)cl, (APTR)o, (APTR)message);
+}
+
+INLINE ULONG DoMethodA (Object *o, Msg message)
+{
+	Class *cl;
+	ASSERT_VALIDNO0(o)
+	cl = OCLASS (o);
+	ASSERT_VALIDNO0(cl)
+
+	return ((_HookPtr)cl->cl_Dispatcher.h_Entry) ((APTR)cl, (APTR)o, (APTR)message);
+}
+
+
+
+/* Var-args versions of the above functions.  SAS/C is clever enough to inline these,
+ * while gcc and egcs refuse to inline a function with '...' (yikes!).  The GCC
+ * versions of these functions are macro blocks similar to those  used in the
+ * inline/#?.h headers.
+ */
+#if defined(__SASC) || defined (__STORM__)
+
+	INLINE ULONG CoerceMethod (struct IClass *cl, Object *o, ULONG MethodID, ...)
+	{
+		ASSERT_VALIDNO0(cl)
+		ASSERT_VALID(o)
+
+		return ((_HookPtr)cl->cl_Dispatcher.h_Entry) ((APTR)cl, (APTR)o, (APTR)&MethodID);
+	}
+
+	INLINE ULONG DoSuperMethod (struct IClass *cl, Object *o, ULONG MethodID, ...)
+	{
+		ASSERT_VALIDNO0(cl)
+		ASSERT_VALID(o)
+
+		cl = cl->cl_Super;
+		return ((_HookPtr)cl->cl_Dispatcher.h_Entry) ((APTR)cl, (APTR)o, (APTR)&MethodID);
+	}
+
+	INLINE ULONG DoMethod (Object *o, ULONG MethodID, ...)
+	{
+		Class *cl;
+
+		ASSERT_VALIDNO0(o)
+		cl = OCLASS (o);
+		ASSERT_VALIDNO0(cl)
+
+		return ((_HookPtr)cl->cl_Dispatcher.h_Entry) ((APTR)cl, (APTR)o, (APTR)&MethodID);
+	}
+
+	/* varargs stub for the OM_NOTIFY method */
+	INLINE void NotifyAttrs (Object *o, struct GadgetInfo *gi, ULONG flags, Tag attr1, ...)
+	{
+		ASSERT_VALIDNO0(o)
+		ASSERT_VALID(gi)
+
+		DoMethod (o, OM_NOTIFY, &attr1, gi, flags);
+	}
+
+	/* varargs stub for the OM_UPDATE method */
+	INLINE void UpdateAttrs (Object *o, struct GadgetInfo *gi, ULONG flags, Tag attr1, ...)
+	{
+		ASSERT_VALIDNO0(o)
+		ASSERT_VALID(gi)
+
+		DoMethod (o, OM_UPDATE, &attr1, gi, flags);
+	}
+
+#elif defined(__GNUC__)
+
+	#define CoerceMethod(cl, o, msg...)												\
+	({																				\
+		ULONG _msg[] = { msg };														\
+		ASSERT_VALIDNO0(cl)															\
+		ASSERT_VALID(o)																\
+		((_HookPtr)cl->cl_Dispatcher.h_Entry) ((APTR)cl, (APTR)o, (APTR)_msg);		\
+	})
+
+	#define DoSuperMethod(cl, o, msg...)											\
+	({																				\
+		Class *_cl;																	\
+		ULONG _msg[] = { msg };														\
+		ASSERT_VALID(o)																\
+		ASSERT_VALIDNO0(cl)															\
+		_cl = cl = cl->cl_Super;													\
+		ASSERT_VALIDNO0(_cl)														\
+		((_HookPtr)_cl->cl_Dispatcher.h_Entry) ((APTR)_cl, (APTR)o, (APTR)_msg);	\
+	})
+
+	#define DoMethod(o, msg...)														\
+	({																				\
+		Class *_cl;																	\
+		ULONG _msg[] = { msg };														\
+		ASSERT_VALIDNO0(o)															\
+		_cl = OCLASS(o);															\
+		ASSERT_VALIDNO0(_cl)														\
+		((_HookPtr)_cl->cl_Dispatcher.h_Entry) ((APTR)_cl, (APTR)o, (APTR)_msg);	\
+	})
+
+	/* Var-args stub for the OM_NOTIFY method */
+	#define NotifyAttrs(o, gi, flags, attrs...)										\
+	({																				\
+		Class *_cl;																	\
+		ULONG _attrs[] = { attrs };													\
+		ULONG _msg[] = { OM_NOTIFY, (ULONG)_attrs, (ULONG)gi, flags };				\
+		ASSERT_VALIDNO0(o)															\
+		_cl = OCLASS(o);															\
+		ASSERT_VALIDNO0(_cl)														\
+		ASSERT_VALID(gi)															\
+		((_HookPtr)_cl->cl_Dispatcher.h_Entry) ((APTR)_cl, (APTR)o, (APTR)_msg);	\
+	})
+
+	/* Var-args stub for the OM_UPDATE method */
+	#define UpdateAttrs(o, gi, flags, attrs...)										\
+	({																				\
+		Class *_cl;																	\
+		ULONG _attrs[] = { attrs };													\
+		ULONG _msg[] = { OM_UPDATE, (ULONG)_attrs, (ULONG)gi, flags };				\
+		ASSERT_VALIDNO0(o)															\
+		_cl = OCLASS(o);															\
+		ASSERT_VALIDNO0(_cl)														\
+		ASSERT_VALID(gi)															\
+		((_HookPtr)_cl->cl_Dispatcher.h_Entry) ((APTR)_cl, (APTR)o, (APTR)_msg);	\
+	})
+#endif
+
+/* Nobody else needs this anymore... */
+#undef _HookPtr
+
+#endif /* !BOOPSISTUBS_H */
diff --git a/CompilerSpecific.h b/CompilerSpecific.h
new file mode 100644
index 0000000..6041ece
--- /dev/null
+++ b/CompilerSpecific.h
@@ -0,0 +1,304 @@
+#ifndef COMPILERSPECIFIC_H
+#define COMPILERSPECIFIC_H
+/*
+**	$VER: CompilerSpecific.h 2.3 (26.10.97)
+**
+**	Copyright (C) 1997 Bernardo Innocenti. All rights reserved.
+**
+**	Compiler specific definitions is here. You can add support
+**	for other compilers in this header. Please return any changes
+**	you make to me, so I can add them to my personal copy of this file.
+**
+**	Here is a short description of the macros defined below:
+**
+**	LIBCALL
+**		Shared library entry point, with register args
+**
+**	HOOKCALL
+**		Hook or boopsi dispatcher entry point with arguments
+**		passed in registers
+**
+**	GLOBALCALL
+**		Attribute for functions to be exported to other modules for
+**		global access within the same executable file.
+**		Usually defined to "extern", but can be overridden for special
+**		needs, such as compiling all modules together in a single
+**		object module to optimize code better.
+**
+**	XDEF
+**		Attribute for symbols to be exported to other modules for
+**		global access within the same executable file.
+**		Usually defined to an empty value.
+**
+**	XREF
+**		Attribute for symbols to be imported from other modules
+**		within the same executable file.
+**		Usually defined to "extern".
+**
+**	INLINE
+**		Please put function body inline to the calling code
+**
+**	STDARGS
+**		Function uses standard C conventions for arguments
+**
+**	ASMCALL
+**		Function takes arguments in the specified 68K registers
+**
+**	REGCALL
+**		Function takes arguments in registers choosen by the compiler
+**
+**	CONSTCALL
+**		Function does not modify any global variable
+**
+**	FORMATCALL(archetype,string_index,first_to_check)
+**		Function uses printf or scanf-like formatting
+**
+**	SAVEDS
+**		Function needs to reload context for small data model
+**
+**	INTERRUPT
+**		Function will be called from within an interrupt
+**
+**	NORETURN
+**		Function does never return
+**
+**	ALIGNED
+**		Variable must be aligned to longword boundaries
+**
+**	CHIP
+**		Variable must be stored in CHIP RAM
+**
+**	REG(reg,arg)
+**		Put argument <arg> in 68K register <reg>
+**
+**	min(a,b)
+**		Return the minimum between <a> and <b>
+**
+**	max(a,b)
+**		Return the maximum between <a> and <b>
+**
+**	abs(a)
+**		Return the absolute value of <a>
+**
+**	_COMPILED_WITH
+**		A string containing the name of the compiler
+*/
+
+#ifdef __SASC
+	/* SAS/C 6.58 or better */
+
+	#define INLINE		static __inline
+	#define STDARGS		__stdargs
+	#define ASMCALL		__asm
+	#define REGCALL		__regcall
+	#define CONSTCALL	/* unsupported */
+	#define FORMATCALL	/* unsupported */
+	#define SAVEDS		__saveds
+	#define INTERRUPT	__interrupt
+	#define NORETURN	/* unsupported */
+	#define ALIGNED		__aligned
+	#define CHIP		__chip
+	#define REG(reg,arg) register __##reg arg
+	#define _COMPILED_WITH	"SAS/C"
+
+	/* For min(), max() and abs() */
+	#define USE_BUILTIN_MATH
+	#include <string.h>
+#else
+#ifdef __GNUC__
+	/* GeekGadgets GCC 2.7.2.1 or better */
+
+	#define INLINE		static inline
+	#define STDARGS		__attribute__((stkparm))
+	#define ASMCALL		/* nothing */
+	#define REGCALL		/* nothing */
+	#define CONSTCALL	__attribute__((const))
+	#define FORMATCALL(a,s,f)	__attribute__((format(a,s,f)))
+	#define SAVEDS		__attribute__((saveds))
+	#define INTERRUPT	__attribute__((interrupt))
+	#define NORETURN	__attribute__((noreturn))
+	#define ALIGNED		__attribute__((aligned(4)))
+	#define REG(reg,arg) arg __asm(#reg)
+	#define _COMPILED_WITH	"GCC"
+
+	#define min(a,b)	(((a)<(b))?(a):(b))
+	#define max(a,b)	(((a)>(b))?(a):(b))
+	#define abs(a)		(((a)>0)?(a):-(a))
+
+	/* Inline versions of some str*() and mem*() functions
+	 */
+	#define _ANSI_SOURCE
+	#include <string.h>
+	#undef _ANSI_SOURCE
+
+	INLINE STDARGS size_t strlen (const char *src)
+		{ const char *tmp = src; while (*tmp) tmp++; return tmp - src; }
+
+	INLINE STDARGS int memcmp (const void *src, const void *dest, size_t n)
+	{
+		while (n--)
+		{
+			if (*((char *)src) != *((char *)dest))
+				return (*((char *)src) - *((char *)dest));
+			((char *)src)++;
+			((char *)dest)++;
+		}
+		return 0;
+	}
+
+	INLINE STDARGS void * memset (void *m, int c, size_t n)
+	{
+		void *r = m;
+		n++;
+		while (--n > 0)
+			*(((unsigned char *) m)++) = (unsigned char) c;
+		return r;
+	}
+
+	/* GCC produces code which calls these two functions
+	 * to initialize and copy structures and arrays.
+	 *
+	 *	INLINE STDARGS void bzero (void *buf, size_t len)
+	 *		{ while (len--) *((char *)buf)++ = 0; }
+	 *
+	 *	INLINE STDARGS void bcopy (const void *src, void *dest, size_t len)
+	 *		{ while (len--) *((char *)dest)++ = *((const char *)src)++; }
+	 */
+
+#else
+#ifdef __STORM__
+	/* StormC 2.00.23 or better */
+	#define INLINE		__inline
+	#define STDARGS		/* nothing */
+	#define ASMCALL		/* nothing */
+	#define REGCALL		register
+	#define CONSTCALL	/* unsupported */
+	#define FORMATCALL	/* unsupported */
+	#define SAVEDS		__saveds
+	#define INTERRUPT	__interrupt
+	#define NORETURN	/* unsupported */
+	#define ALIGNED		/* unsupported */
+	#define CHIP		__chip
+	#define REG(reg,arg) register __##reg arg
+	#define _COMPILED_WITH	"StormC"
+
+	#define min(a,b)	(((a)<(b))?(a):(b))
+	#define max(a,b)	(((a)>(b))?(a):(b))
+	#define abs(a)		(((a)>0)?(a):-(a))
+
+	#define _INLINE_INCLUDES
+	#include <string.h>
+#else
+#ifdef __MAXON__
+	/* Maxon C/C++ 3.0 */
+
+	#define INLINE		static inline
+	#define STDARGS		/* ? */
+	#define ASMCALL		/* ? */
+	#define REGCALL		/* ? */
+	#define CONSTCALL	/* unsupported */
+	#define FORMATCALL	/* unsupported */
+	#define SAVEDS		/* unsupported */
+	#define INTERRUPT	/* unsupported */
+	#define NORETURN	/* unsupported */
+	#define ALIGNED		/* unsupported */
+	#define REG(reg,arg)	register __##reg arg
+	#define _COMPILED_WITH	"Maxon C"
+
+	/* For min(), max() and abs() */
+	#define USE_BUILTIN_MATH
+	#include <string.h>
+
+	#error Maxon C compiler support is untested. Please check all the above definitions
+#else
+#ifdef _DCC
+	/* DICE C 3.15 */
+
+	#define INLINE		static __inline
+	#define STDARGS		__stdargs
+	#define ASMCALL		/* nothing */
+	#define REGCALL		/* ? */
+	#define CONSTCALL	/* unsupported */
+	#define FORMATCALL	/* unsupported */
+	#define SAVEDS		__geta4
+	#define INTERRUPT	/* unsupported */
+	#define NORETURN	/* unsupported */
+	#define ALIGNED		__aligned
+	#define REG(reg,arg)	__##reg arg
+	#define _COMPILED_WITH	"DICE"
+
+	#define min(a,b)	(((a)<(b))?(a):(b))
+	#define max(a,b)	(((a)>(b))?(a):(b))
+	#define abs(a)		(((a)>0)?(a):-(a))
+
+	#error DICE compiler support is untested. Please check all the above definitions
+#else
+#ifdef AZTEC_C
+	/* Aztec/Manx C */
+
+	#define INLINE		static
+	#define STDARGS		/* ? */
+	#define ASMCALL		/* ? */
+	#define REGCALL		/* ? */
+	#define CONSTCALL	/* unsupported */
+	#define FORMATCALL	/* unsupported */
+	#define SAVEDS		__geta4
+	#define INTERRUPT	/* unsupported */
+	#define NORETURN	/* unsupported */
+	#define ALIGNED		__aligned
+	#define REG(reg,arg)	__##reg arg
+	#define _COMPILED_WITH	"Manx C"
+
+	#define min(a,b)	(((a)<(b))?(a):(b))
+	#define max(a,b)	(((a)>(b))?(a):(b))
+	#define abs(a)		(((a)>0)?(a):-(a))
+
+	#error Aztec/Manx C compiler support is untested. Please check all the above definitions
+#else
+	#error Please add compiler specific definitions for your compiler
+#endif
+#endif
+#endif
+#endif
+#endif
+#endif
+
+
+/* CONST_STRPTR is a typedef made by a patched version of <exec/types.h>.
+ * Passing "const char *" parameters will only work if the OS protos are
+ * patched accordingly, otherwise you will get a lot of compiler warnings
+ * for const to volatile conversions.
+ *
+ * Using "const" where it is appropriate helps the compiler optimizing
+ * code better, so this mess is probably worth it. I wish one day the OS
+ * headers will come with support for the const keyword.
+ */
+#ifndef _CONST_STRPTR_DEFINED
+typedef char *CONST_STRPTR;
+#endif
+
+
+/* Special function attributes */
+
+#define LIBCALL		ASMCALL SAVEDS
+#define HOOKCALL	ASMCALL SAVEDS
+#ifdef __cplusplus
+	#define GLOBALCALL extern "C"
+#else
+	#define GLOBALCALL
+#endif
+
+/* special variable attributes */
+#define XDEF
+#define XREF extern
+
+
+/* AROS Compatibility: IPTR is a type which can store a pointer
+ * as well as a long integer.
+ */
+#ifndef IPTR
+#define IPTR LONG
+#endif /* IPTR */
+
+
+#endif /* !COMPILERSPECIFIC_H */
diff --git a/Debug.h b/Debug.h
new file mode 100644
index 0000000..ba7b825
--- /dev/null
+++ b/Debug.h
@@ -0,0 +1,103 @@
+#ifndef DEBUG_H
+#define DEBUG_H
+/*
+**	$VER: Debug.h 2.2 (19.9.98)
+**
+**	Copyright (C) 1995,96,97 Bernardo Innocenti. All rights reserved.
+**
+**	Use 4 chars wide TABs to read this file
+**
+**	Some handy debug macros which are automatically excluded when the
+**	DEBUG preprocessor sysmbol is not defined. To make debug executables,
+**	link with debug.lib or any module containing the kprintf() function.
+**
+**	Here is a short description of the macros defined below:
+**
+**	ILLEGAL
+**		Output an inline "ILLEGAL" 68K opcode, which will
+**		be interpreted as a breakpoint by most debuggers.
+**
+**	DBPRINTF
+**		Output a formatted string to the debug console. This
+**		macro uses the debug.lib kprintf() function by default.
+**
+**	ASSERT(x)
+**		Do nothing if the expression <x> evalutates to a
+**		non-zero value, output a debug message otherwise.
+**
+**	ASSERT_VALID(x)
+**		Checks if the expression <x> points to a valid
+**		memory location, and outputs a debug message
+**		otherwise. A NULL pointer is considered VALID.
+**
+**	ASSERT_VALIDNO0(x)
+**		Checks if the expression <x> points to a valid
+**		memory location, and outputs a debug message
+**		otherwise. A NULL pointer is considered INVALID.
+**
+**	DB(x)
+**		Compile the expression <x> when making a debug
+**		executable, leave it out otherwise.
+*/
+
+#ifdef DEBUG
+
+	/* Needed for TypeOfMem() */
+	#ifndef  PROTO_EXEC_H
+	#include <proto/exec.h>
+	#endif /* PROTO_EXEC_H */
+
+	#if defined(__SASC)
+
+		extern void __builtin_emit (int);
+		// #define ILLEGAL __builtin_emit(0x4AFC)
+		#define ILLEGAL 0
+		STDARGS extern void kprintf (const char *, ...);
+
+	#elif defined(__GNUC__)
+
+		/* Currently, there is no kprintf() in libamiga.a */
+		#define kprintf printf
+
+		/* GCC doesn't accept asm statemnts in the middle of an
+		 * expression such as `a ? b : asm("something")'.
+		 */
+		#define ILLEGAL illegal()
+		static inline int illegal(void) { asm ("illegal"); return 0; }
+		extern void STDARGS FORMATCALL(printf,1,2) kprintf (const char *, ...);
+
+	#else
+		#error Please add compiler specific definitions for your compiler
+	#endif
+
+	#if defined(__SASC) || defined (__GNUC__)
+
+		/* common definitions for ASSERT and DB macros */
+
+		#define DBPRINTF kprintf
+
+		#define ASSERT(x) ( (x) ? 0 :									\
+			( DBPRINTF ("\x07%s, %ld: assertion failed: " #x "\n",		\
+			__FILE__, __LINE__) , ILLEGAL ) );
+
+		#define ASSERT_VALID(x) ( ((((APTR)(x)) == NULL) ||				\
+			(((LONG)(x) > 1024) &&	TypeOfMem ((APTR)(x)))) ? 0 :		\
+			( DBPRINTF ("\x07%s, %ld: bad address: " #x " = $%lx\n",	\
+			__FILE__, __LINE__, (APTR)(x)) , ILLEGAL ) );
+
+		#define ASSERT_VALIDNO0(x) ( (((LONG)(x) > 1024) &&				\
+			TypeOfMem ((APTR)(x))) ? 0 :								\
+			( DBPRINTF ("\x07%s, %ld: bad address: " #x " = $%lx\n",	\
+			__FILE__, __LINE__, (APTR)(x)) , ILLEGAL ) );
+
+		#define DB(x) x
+	#endif
+
+#else
+	#define ASSERT_VALID(x)
+	#define ASSERT_VALIDNO0(x)
+	#define ASSERT(x)
+	#define DB(x)
+#endif /* DEBUG */
+
+#endif /* !DEBUG_H */
diff --git a/GNUmakefile b/GNUmakefile
new file mode 100644
index 0000000..9b8bd82
--- /dev/null
+++ b/GNUmakefile
@@ -0,0 +1,92 @@
+##
+##	$VER: LVDemo_Makefile 2.1 (5.9.97)
+##
+##	Copyright (C) 1996,97 by Bernardo Innocenti
+##
+##	Makefile for GCC
+##
+
+###########################################################
+# Name of the main executable
+###########################################################
+#
+PROJ = LVDemo
+
+
+###########################################################
+# Package configuration
+###########################################################
+#
+
+# set to OS30_ONLY to leave out support for old V37
+# set to ANY_OS to make an executable for V37 with V39 support
+#
+OSVER = ANY_OS
+
+# cpu to compile for.
+#
+CPU = 68020
+
+
+###########################################################
+# Object files in this project
+###########################################################
+#
+OBJS = startup_gcc.o LVDemo.o ListViewHooks.o \
+ ListViewClass.o ListBoxClass.o ScrollButtonClass.o
+
+
+###########################################################
+# Make the project
+###########################################################
+#
+all: $(PROJ)
+
+
+###########################################################
+# Remove all targets and intermediate files
+###########################################################
+#
+clean:
+	-Delete $(PROJ) $(OBJS)
+
+
+###########################################################
+# Dependences
+###########################################################
+#
+LVDemo.c ListViewClass.c: ListViewClass.h
+
+
+###########################################################
+# GCC Release version should be compiled with these flags
+###########################################################
+#
+CC = gcc
+CFLAGS = -c -O0 -finline-functions -fno-implement-inlines \
+ -m$(CPU) -msmall-code -mregparm -fomit-frame-pointer \
+ -I/gg/include -I/include -Wunused -Wreturn-type -D$(OSVER)
+LFLAGS = -s
+LIBS = -noixemul -nostdlib
+
+
+###########################################################
+# GCC - Make the executable
+###########################################################
+#
+
+# Assemble startup code
+#
+startup_gcc.o: startup_gcc.s
+	$(AS) startup_gcc.s -o startup_gcc.o
+
+# Compile C sources and make the object files
+#
+.c.o: PIPClass.h
+	$(CC) $(*).c $(CFLAGS)
+
+# Link object files and make the executable
+#
+$(PROJ): $(OBJS)
+	$(CC) $(OBJS) -o $(PROJ) $(LFLAGS) $(LIBS)
+	@Protect $(PROJ) +e
diff --git a/GST.c b/GST.c
new file mode 100644
index 0000000..260f8e9
--- /dev/null
+++ b/GST.c
@@ -0,0 +1,45 @@
+/*
+**	GST.c
+**
+**	Copyright (C) 1997 by Bernardo Innocenti
+**
+**	This is a dummy source file used to make the Global Symbol Table
+**	for LVDemo.
+*/
+
+#include <exec/types.h>
+#include <exec/libraries.h>
+#include <exec/memory.h>
+#include <exec/execbase.h>
+#include <devices/timer.h>
+#include <intuition/classes.h>
+#include <intuition/intuition.h>
+#include <intuition/intuitionbase.h>
+#include <intuition/gadgetclass.h>
+#include <intuition/icclass.h>
+#include <intuition/imageclass.h>
+#include <intuition/screens.h>
+#include <graphics/gfxbase.h>
+#include <graphics/gfxmacros.h>
+
+#include <proto/dos.h>
+#include <proto/exec.h>
+#include <proto/intuition.h>
+#include <proto/graphics.h>
+#include <proto/layers.h>
+#include <proto/utility.h>
+#include <clib/alib_stdio_protos.h>
+
+#include "CompilerSpecific.h"
+#include "Debug.h"
+
+/* Can't include these in GST because they are defining some inline functions
+ * #include "BoopsiStubs.h"
+ * #include "ListMacros.h"
+ */
+
+#define LV_GADTOOLS_STUFF
+#include "ScrollButtonClass.h"
+#include "ListViewClass.h"
+#include "VectorGlyphIClass.h"
+#include "ListBoxClass.h"
diff --git a/LVDemo.c b/LVDemo.c
new file mode 100644
index 0000000..e6900e2
--- /dev/null
+++ b/LVDemo.c
@@ -0,0 +1,1043 @@
+/*	$VER: ListViewDemo 1.6 (15.12.98) by Bernardo Innocenti
+**
+**
+**	Introduction
+**	============
+**
+**	This program demonstrates how to use the `boopsi' ListView gadget.
+**
+**	The source code shows how to create a resizable window with sliders
+**	and how to write a custom `boopsi' class on top of the gadgetclass.
+**
+**
+**	Compiling
+**	=========
+**
+**	This project can be compiled with SAS/C 6.58 or better and
+**	GeekGadget's GCC 2.7.2.1 or better.
+**	You get the smallest executable with SAS/C. GCC will give
+**	you quite a lot of warnings with the tag calls. Don't worry about them.
+**
+**
+**	History
+**	=======
+**
+**	0.1 (23.6.96)	First try
+**	1.0 (21.1.97)	First alpha release
+**	1.1 (24.5.97)	Lotsa bugs fixed, implemented LVA_DoubleClick
+**	1.2 (31.8.97)	Lotsa bugs fixed, implemented LVA_Clipped
+**					Fixed memory leak with the test list
+**	1.3 (5.9.97)	Improved initial sliders notification
+**					Added multiple selection (LVA_DoMultiselect)
+**					Fixed scrolling problems in some unusual conditions
+**	1.4	(8.9.97)	Sets GMORE_SCROLLRASTER in OM_NEW
+**					Multiple demo windows showing the features of the class
+**	1.5 (14.9.97)	Added LVA_ItemSpacing
+**					Finally fixed the LVA_Clipped mode!
+**
+**	1.6 (2.10.97)	Added StormC support
+**					Implemented pixel-wise vertical scrolling for clipped mode
+**					Reworked the class istance data and some code
+**
+**	1.7 (15.12.98)	Added ListBoxClass
+**					Moved ScrollButtonClass in its own file
+**					Removed function OpenClass()
+**					Updated to latest version of VectorGlyphIClass
+**
+**
+**	Known Bugs
+**	==========
+**
+**		- This code has never been tested on V37.
+**
+**		- Middle mouse button scrolling does not work because
+**		  of a bug in input.device V40.
+**
+**
+**	Copyright Notice
+**	================
+**
+**	Copyright © 1996,97 by Bernardo Innocenti <bernardo.innocenti@usa.net>.
+**	Freely Distributable, as long as source code, documentation and
+**	executable are kept together.  Permission is granted to release
+**	modified versions of this program as long as all existing copyright
+**	notices are left intact.
+**
+*/
+
+#define USE_BUILTIN_MATH
+#define INTUI_V36_NAMES_ONLY
+#define __USE_SYSBASE
+#define  CLIB_ALIB_PROTOS_H	/* Avoid including this header file because of
+							 * conflicting definitions in BoopsiStubs.h
+							 */
+#include <exec/types.h>
+#include <exec/memory.h>
+#include <exec/execbase.h>
+
+#include <dos/dos.h>
+
+#include <intuition/intuition.h>
+#include <intuition/intuitionbase.h>
+#include <intuition/screens.h>
+#include <intuition/classes.h>
+#include <intuition/gadgetclass.h>
+#include <intuition/imageclass.h>
+#include <intuition/icclass.h>
+#include <devices/timer.h>
+
+#include <proto/exec.h>
+#include <proto/intuition.h>
+#include <proto/dos.h>
+#include <proto/utility.h>
+#include <proto/graphics.h>
+#include <proto/diskfont.h>
+
+#ifdef __STORM__
+	#pragma header
+#endif
+
+
+#include "CompilerSpecific.h"
+#include "Debug.h"
+#include "BoopsiStubs.h"
+#include "ListMacros.h"
+
+#include "ListViewClass.h"
+#include "ListBoxClass.h"
+#include "VectorGlyphIClass.h"
+#include "ScrollButtonClass.h"
+
+/* OS version */
+
+#ifdef OS30_ONLY
+ #define WANTEDLIBVER	39L
+#else
+ #define WANTEDLIBVER	37L
+#endif
+
+
+/* Local function prototypes */
+
+LONG SAVEDS					 main				(void);
+static struct MsgPort		*OpenDemoWindows	(struct List *winlist);
+static void					 CloseDemoWindows	(struct List *winlist);
+static struct LVHandle		*OpenLVWin			(CONST_STRPTR pubscreen,
+	struct MsgPort *winport, CONST_STRPTR title, ULONG mode, BOOL useListBox,
+	ULONG left, ULONG top, ULONG width, ULONG height, ULONG moreTags, ...);
+static void					 CloseLVWin			(struct LVHandle *lvhandle);
+static struct Gadget		*CreateLVGadgets	(struct LVHandle *lvhandle,
+	struct TagItem *moreTags);
+static void					 DisposeGadgets		(struct LVHandle *lvhandle);
+static void					 CreateItems		(struct LVHandle *lvhandle);
+static void					 CreateImages		(struct DrawInfo *dri);
+static void					 FreeImages			(void);
+
+
+/* Width and height for the demo vector images */
+#define IMAGES_WIDTH	56
+#define IMAGES_HEIGHT	48
+
+
+/* Gadgets IDs */
+enum
+{
+	GAD_LV, GAD_VSLIDER, GAD_HSLIDER,
+	GAD_UPBUTTON, GAD_DOWNBUTTON, GAD_LEFTBUTTON, GAD_RIGHTBUTTON,
+	GAD_COUNT
+};
+
+
+/* Images IDs */
+enum
+{
+	IMG_UP, IMG_DOWN, IMG_LEFT, IMG_RIGHT, IMG_COUNT
+};
+
+
+
+
+/* This structure describes an open ListView window */
+struct LVHandle
+{
+	struct MinNode	 Link;			/* Link LVHandle in a list of all windows	*/
+	struct Window	*Win;			/* Pointer to our window					*/
+	struct Screen	*Scr;			/* The screen we are opening our windows on	*/
+	struct DrawInfo	*DrawInfo;		/* DrawInfo for this screen 				*/
+	ULONG			 Mode;			/* ListView operating mode					*/
+	APTR			 Items;			/* Items attached to the ListView			*/
+	ULONG			 Total;			/* Number of items or -1 if unknown			*/
+	struct Gadget	*Gad[GAD_COUNT];/* All our gadgets							*/
+	APTR			 Model;			/* Make boopsi gadgets talk to each other	*/
+	struct List		 TestList;		/* Items list for LVA_#?List modes			*/
+	ULONG			*SelectArray;	/* Array for storing multiple selections	*/
+	BOOL			 UseListBox;	/* If TRUE, window uses the ListBox class	*/
+};
+
+
+
+/* Version tag */
+
+UBYTE versiontag[] = "$VER: ListViewDemo 1.7 (15.12.98) by Bernardo Innocenti"
+	" (compiled with " _COMPILED_WITH ")";
+
+
+
+/* Workaround a bug in StormC header file <proto/utility.h> */
+
+#ifdef __STORM__
+	#define UTILITYBASETYPE struct Library
+#else
+	#define UTILITYBASETYPE struct UtilityBase
+#endif
+
+/* Library bases */
+struct ExecBase				*SysBase;
+UTILITYBASETYPE				*UtilityBase;
+struct IntuitionBase		*IntuitionBase;
+struct GfxBase				*GfxBase;
+struct Library				*LayersBase;
+struct Library				*DiskfontBase;
+
+
+/* Our private `boopsi' classes */
+Class						*ListViewClass;
+static Class				*ListBoxClass;
+static Class				*ScrollButtonClass;
+
+
+/* `boopsi' images for all windows
+ *
+ * These variables must be NULL at startup time. We are not
+ * going to explicitly initialize them because otherwise
+ * Storm C 2.0 would generate a C++-style constructor to
+ * do it :-).  LoasSeg() will clear the BSS data section
+ * for us, so these variables are guaranteed to be NULL anyway.
+ */
+static struct Image		*Img[IMG_COUNT];
+static ULONG			 ImgWidth[IMG_COUNT];
+static ULONG			 ImgHeight[IMG_COUNT];
+static struct TextFont	*CustomFont;
+
+
+
+/* Attribute translations for object interconnections */
+
+static LONG MapLVToHSlider[] =
+{
+	LVA_PixelLeft,		PGA_Top,
+	LVA_PixelWidth,		PGA_Total,
+	LVA_PixelHVisible,	PGA_Visible,
+	TAG_DONE
+};
+
+static LONG MapHSliderToLV[] =
+{
+	PGA_Top,			LVA_PixelLeft,
+	TAG_DONE
+};
+
+/*
+static LONG MapLVToVSlider[] =
+{
+	LVA_Top,			PGA_Top,
+	LVA_Total,			PGA_Total,
+	LVA_Visible,		PGA_Visible,
+	TAG_DONE
+};
+*/
+
+static LONG MapLVToVSlider[] =
+{
+	LVA_PixelTop,		PGA_Top,
+	LVA_PixelHeight,	PGA_Total,
+	LVA_PixelVVisible,	PGA_Visible,
+	TAG_DONE
+};
+
+
+/*
+static LONG MapVSliderToLV[] =
+{
+	PGA_Top,	LVA_Top,
+	TAG_DONE
+};
+*/
+
+static LONG MapVSliderToLV[] =
+{
+	PGA_Top,	LVA_PixelTop,
+	TAG_DONE
+};
+
+
+
+static LONG MapUpButtonToLV[] =
+{
+	GA_ID,		LVA_MoveUp,
+	TAG_DONE
+};
+
+static LONG MapDownButtonToLV[] =
+{
+	GA_ID,		LVA_MoveDown,
+	TAG_DONE
+};
+
+static LONG MapLeftButtonToLV[] =
+{
+	GA_ID,		LVA_MoveLeft,
+	TAG_DONE
+};
+
+static LONG MapRightButtonToLV[] =
+{
+	GA_ID,		LVA_MoveRight,
+	TAG_DONE
+};
+
+
+
+/* Test Strings */
+
+/* StormC does not see that the expression "versiontag + 6" is constant
+ * and generates an initializer for it. The following definition
+ * works around this problem.
+ */
+#ifdef __STORM__
+	#define VERSIONTAG "ListViewDemo 1.7 by Bernardo Innocenti (compiled with " _COMPILED_WITH ")"
+#else
+	#define VERSIONTAG versiontag + 6
+#endif
+
+static STRPTR TestStrings[] =
+{
+	VERSIONTAG,
+	NULL,
+	"This `boopsi' ListView class supports all the features",
+	"of the Gadtools LISTVIEW_KIND, plus more stuff:",
+	NULL,
+	" + Easy to use (almost a drop-in replacement for LISTVIEW_KIND)",
+	" + Can be resized and supports GREL_#? flags",
+	" + Multiple selection of items",
+	" + Notifies your `boopsi' sliders",
+	" + Multiple columns (TODO)",
+	" + Redraws quickly without clearing (which is good for solid window sizing)",
+	" + Horizontal scrolling (TODO)",
+	" + Items with `boopsi' images",
+	" + Using arrays instead of exec lists",
+	" + You can use `boopsi' label images instead of plain text",
+	" + You can use your own custom rendering hook",
+	" + You can use your own item item-retriving callback hook",
+	" + List title (TODO)",
+	" + Full Keyboard control (all control, alt and shift key combinations supported)",
+	" + Asynchronous scrolling with inertia (TODO)",
+	" + OS 3.0 optimized (V39-only version also available)",
+	" + RTG friendly and optimized (no planar stuff in chunky bitmaps)",
+	" + Small code! (<10K)",
+	" + Written in C to be highly portable across compilers and CPUs",
+	" + Full commented source code included",
+	" + Source code compiles with SAS/C, StormC and GCC",
+	" + Subclasses can be easlily derived from the base listview class",
+	NULL,
+	"Please send comments to <bernardo.innocenti@usa.net>."
+};
+
+#define TESTSTRINGS_CNT (sizeof (TestStrings) / sizeof (CONST_STRPTR))
+
+
+
+LONG SAVEDS _main (void)
+
+/* Main program entry point.  When linking without startup code, this
+ * must be the first function in the first object module listed on the
+ * linker command line.  We also need to initialize SysBase and open
+ * all needed libraries manually.
+ */
+{
+	struct MinList	 winlist;
+	struct MsgPort	*winport;
+	struct Library	*VectorGlyphBase = NULL;
+	LONG			 sigwait, sigrcvd;
+	LONG			 retval	= RETURN_FAIL;	/* = RETURN_FAIL */
+	BOOL			 quit	= FALSE;
+
+
+	/* Initialize SysBase */
+	SysBase = *((struct ExecBase **)4UL);
+
+	/* Open system libraries */
+
+	if ((UtilityBase = (UTILITYBASETYPE *) OpenLibrary ("utility.library", 37L)) &&
+		(IntuitionBase = (struct IntuitionBase *)OpenLibrary ("intuition.library", WANTEDLIBVER)) &&
+		(GfxBase = (struct GfxBase *)OpenLibrary ("graphics.library", WANTEDLIBVER)) &&
+		(LayersBase = OpenLibrary ("layers.library", WANTEDLIBVER)))
+	{
+		if ((ListViewClass = MakeListViewClass ()) &&
+			(ListBoxClass = MakeListBoxClass ()) &&
+			(ScrollButtonClass = MakeScrollButtonClass ()) &&
+			(VectorGlyphBase = OpenLibrary ("images/vectorglyph.image", 0)))
+		{
+			NEWLIST ((struct List *)&winlist);
+
+			if (winport = OpenDemoWindows ((struct List *)&winlist))
+			{
+				/* Pre-calculate the signal mask for Wait() */
+				sigwait = (1 << winport->mp_SigBit) |
+					SIGBREAKF_CTRL_C;
+
+				/* Now for the main loop.  As you can see, it is really
+				 * very compact.  That's the magic of boopsi! :-)
+				 */
+				while (!quit)
+				{
+					/* Sleep until something interesting occurs */
+					sigrcvd = Wait (sigwait);
+
+					/* Now handle received signals */
+
+					/* Break signal? */
+					if (sigrcvd & SIGBREAKF_CTRL_C)
+						quit = TRUE;
+
+					/* IDCMP message? */
+					if (sigrcvd & (1 << winport->mp_SigBit))
+					{
+						struct IntuiMessage	*msg;
+
+						while (msg = (struct IntuiMessage *) GetMsg (winport))
+						{
+							switch (msg->Class)
+							{
+								case IDCMP_CLOSEWINDOW:
+									quit = TRUE;
+									break;
+
+								default:
+									break;
+							}
+							ReplyMsg ((struct Message *) msg);
+						}
+					}
+				} /* End while (!quit) */
+
+				retval = 0;	/* RETURN_OK */
+
+				CloseDemoWindows ((struct List *)&winlist);
+			}
+
+			FreeImages ();
+		}
+
+		/* These cannot fail. Passing NULL is ok. */
+		CloseLibrary ((struct Library *)VectorGlyphBase);
+		FreeScrollButtonClass (ScrollButtonClass);
+		FreeListBoxClass (ListBoxClass);
+		FreeListViewClass (ListViewClass);
+	}
+
+	/* Passing NULL to CloseLibrary() was illegal in pre-V37 Exec.
+	 * To avoid crashing when someone attempts to run this program
+	 * on an old OS, we need to test the first library base we tried
+	 * to open.
+	 */
+	if (UtilityBase)
+	{
+		CloseLibrary ((struct Library *)LayersBase);
+		CloseLibrary ((struct Library *)GfxBase);
+		CloseLibrary ((struct Library *)IntuitionBase);
+		CloseLibrary ((struct Library *)UtilityBase);
+	}
+
+	return retval;
+}
+
+
+
+static struct MsgPort *OpenDemoWindows (struct List *winlist)
+{
+	struct LVHandle	*lvhandle;
+	struct MsgPort	*winport;
+
+	if (DiskfontBase = OpenLibrary ("diskfont.library", 0L))
+	{
+		static struct TextAttr attr =
+		{
+			"times.font",
+			24,
+			FSB_ITALIC,
+			0
+		};
+
+		CustomFont = OpenDiskFont (&attr);
+		CloseLibrary (DiskfontBase);
+	}
+
+	/* Setup windows shared Message Port */
+	if (winport = CreateMsgPort())
+	{
+		if (lvhandle = OpenLVWin (NULL, winport,
+			"ListBox test",
+			LVA_StringList, TRUE, 600, 20, 320, 128,
+			TAG_DONE))
+			ADDTAIL (winlist, (struct Node *)lvhandle);
+
+		if (lvhandle = OpenLVWin (NULL, winport,
+			"LVA_TextFont = times/24/italic, LVA_Clipped = TRUE",
+			LVA_StringList, FALSE, 320, 320, 320, 64,
+			LVA_TextFont,		CustomFont,
+			LVA_Clipped,		TRUE,
+			TAG_DONE))
+			ADDTAIL (winlist, (struct Node *)lvhandle);
+
+		if (lvhandle = OpenLVWin (NULL, winport,
+			"LAYOUTA_Spacing = 4",
+			LVA_StringList, FALSE, 256, 256, 320, 64,
+			LAYOUTA_Spacing,	4,
+			TAG_DONE))
+			ADDTAIL (winlist, (struct Node *)lvhandle);
+
+		if (lvhandle = OpenLVWin (NULL, winport,
+			"GA_ReadOnly = TRUE; LVA_Selected = 3",
+			LVA_StringList, FALSE, 192, 192, 320, 64,
+			GA_ReadOnly,		TRUE,
+			LVA_Selected,		3,
+			TAG_DONE))
+			ADDTAIL (winlist, (struct Node *)lvhandle);
+
+		if (lvhandle = OpenLVWin (NULL, winport,
+			"Single selection image list, LVA_Clipped = TRUE",
+			LVA_ImageList, FALSE, 128, 128, 320, 128,
+			LVA_ItemHeight,		IMAGES_HEIGHT,
+			LVA_Clipped,		TRUE,
+			TAG_DONE))
+			ADDTAIL (winlist, (struct Node *)lvhandle);
+
+		if (lvhandle = OpenLVWin (NULL, winport,
+			"LVA_DoMultiSelect = TRUE; LVA_StringArray",
+			LVA_StringArray, FALSE, 64, 64, 320, 128,
+			LVA_DoMultiSelect,	TRUE,
+			TAG_DONE))
+			ADDTAIL (winlist, (struct Node *)lvhandle);
+
+		if (lvhandle = OpenLVWin (NULL, winport,
+			"Plain, single selection string list",
+			LVA_StringList, FALSE, 0, 20, 320, 128,
+			TAG_DONE))
+			ADDTAIL (winlist, (struct Node *)lvhandle);
+
+		/* Abort only if no windows could be opened */
+		if (IsListEmpty (winlist))
+		{
+			DeleteMsgPort (winport);
+			CloseFont (CustomFont);
+			return NULL;
+		}
+	}
+
+	return winport;
+}
+
+
+
+static void CloseDemoWindows (struct List *winlist)
+{
+	struct MsgPort	*winport = NULL;
+	struct LVHandle	*lvhandle;
+
+	while (lvhandle = (struct LVHandle *) REMHEAD (winlist))
+	{
+		/* Safe way to close a shared IDCMP port window */
+
+		Forbid();
+		{
+			struct Node *succ;
+			struct Message *msg;
+
+			winport = lvhandle->Win->UserPort;
+			msg = (struct Message *) winport->mp_MsgList.lh_Head;
+
+			/* Now remove any pending message from the shared IDCMP port */
+			while (succ = msg->mn_Node.ln_Succ)
+			{
+				/* Since we are closing all our windows at once,
+				 * we don't need to check to which window this
+				 * message was addressed.
+				 */
+				REMOVE ((struct Node *)msg);
+				ReplyMsg (msg);
+				msg = (struct Message *) succ;
+			}
+
+			/* Keep intuition from freeing our port... */
+			lvhandle->Win->UserPort = NULL;
+
+			/* ...and from sending us any more messages. */
+			ModifyIDCMP (lvhandle->Win, 0L);
+		}
+		Permit();
+
+		CloseLVWin (lvhandle);
+	}
+
+	DeleteMsgPort (winport); /* NULL is ok */
+
+	if (CustomFont)
+		CloseFont (CustomFont);
+}
+
+
+
+/* No-op backfilling hook.  Since we are going to redraw the whole window
+ * anyway, we can disable backfilling.  This avoids ugly flashing while
+ * resizing or revealing the window.
+ *
+ * This function does not need the __saveds attribute because it makes no
+ * references to external data.
+ */
+
+#ifndef OS30_ONLY
+
+static ULONG BFHookFunc (void)
+{
+	return 1;	/* Do nothing */
+}
+
+static struct Hook BFHook =
+{
+	NULL, NULL,
+	(ULONG(*)())BFHookFunc,
+};
+
+#endif /* !OS30_ONLY */
+
+
+
+static struct LVHandle *OpenLVWin (CONST_STRPTR pubscreen,
+	struct MsgPort *winport, CONST_STRPTR title, ULONG mode, BOOL useListBox,
+	ULONG left, ULONG top, ULONG width, ULONG height, ULONG moreTags, ...)
+{
+	struct LVHandle	*lvhandle;
+	struct Gadget	*glist;
+
+	if (lvhandle = AllocMem (sizeof (struct LVHandle), MEMF_ANY | MEMF_CLEAR))
+	{
+		if (lvhandle->Scr = LockPubScreen (pubscreen))
+		{
+			/* GetScreenDrawInfo() never fails */
+			lvhandle->DrawInfo = GetScreenDrawInfo (lvhandle->Scr);
+
+			/* Set listview operating mode and ListBox flag */
+			lvhandle->Mode			= mode;
+			lvhandle->UseListBox	= useListBox;
+
+			CreateItems (lvhandle);
+
+			if (glist = CreateLVGadgets (lvhandle, (struct TagItem *)&moreTags))
+			{
+
+/* I'm using this define because GCC does not support putting
+ * preprocessor directives (#ifdef/#endif) inside macro arguments.
+ */
+#ifndef OS30_ONLY
+ #define BACKFILL_TAG_VALUE	((IntuitionBase->LibNode.lib_Version < 39) ? &BFHook : LAYERS_NOBACKFILL)
+#else
+ #define BACKFILL_TAG_VALUE	LAYERS_NOBACKFILL
+#endif /* !OS30_ONLY */
+
+				if (lvhandle->Win = OpenWindowTags (NULL,
+					WA_Top,				top,
+					WA_Left,			left,
+					WA_InnerWidth,		width,
+					WA_InnerHeight,		height,
+					WA_PubScreen,		lvhandle->Scr,
+					WA_Gadgets,			glist,
+					WA_Title,			title,
+					WA_BackFill,		useListBox ? NULL : BACKFILL_TAG_VALUE,
+					WA_ScreenTitle,		versiontag + 6,
+					WA_Flags,			WFLG_DRAGBAR | WFLG_DEPTHGADGET | WFLG_SIZEGADGET | WFLG_SIZEBRIGHT | WFLG_SIZEBBOTTOM |
+										WFLG_CLOSEGADGET | WFLG_SIMPLE_REFRESH | WFLG_NOCAREREFRESH,
+					WA_PubScreenFallBack, TRUE,
+					WA_AutoAdjust,		TRUE,
+					WA_MinWidth,		64,
+					WA_MinHeight,		64,
+					WA_MaxWidth,		-1,
+					WA_MaxHeight,		-1,
+					TAG_DONE))
+#undef BACKFILL_TAG_VALUE
+				{
+					lvhandle->Win->UserPort = winport;
+					ModifyIDCMP (lvhandle->Win, IDCMP_CLOSEWINDOW);
+
+					/* We need to keep our screen locked all the time
+					 * because we want to free the associated DrawInfo
+					 * *after* the window has been closed and
+					 * FreeScreenDrawInfo() wants a pointer to a *valid*
+					 * Screen.
+					 */
+					return lvhandle;
+				}
+
+				DisposeGadgets (lvhandle);
+			}
+
+			FreeScreenDrawInfo (lvhandle->Scr, lvhandle->DrawInfo);
+			/* lvhandle->DrawInfo = NULL */
+
+			UnlockPubScreen (NULL, lvhandle->Scr);
+		}
+		FreeMem (lvhandle, sizeof (struct LVHandle));
+	}
+	return NULL;
+}
+
+
+
+static void CloseLVWin (struct LVHandle *lvhandle)
+{
+	/* Close our window. No need to reply queued messages,
+	 * Intuition is clever enough to care about this for us.
+	 */
+	CloseWindow (lvhandle->Win);
+	DisposeGadgets (lvhandle);
+	UnlockPubScreen (NULL, lvhandle->Scr);
+
+	FreeVec (lvhandle->SelectArray); /* NULL is ok */
+
+	if ((lvhandle->Mode == LVA_StringList) || (lvhandle->Mode == LVA_ImageList))
+	{
+		struct Node *node;
+
+		/* Free the test list */
+		while (node = REMHEAD (&lvhandle->TestList))
+		{
+			if (lvhandle->Mode == LVA_ImageList)
+				DisposeObject ((Object *)node->ln_Name);
+			FreeMem (node, sizeof (struct Node));
+		}
+	}
+
+	FreeMem (lvhandle, sizeof (struct LVHandle));
+}
+
+
+
+/*	Diagram of object interconnections for the ListView window
+ *	==========================================================
+ *
+ *	           ScrollButtonClass objects
+ *	+----------+ +------------+ +------------+ +-------------+
+ *	| UpButton | | DownButton | | LeftButton | | RightButton |
+ *	+----------+ +------------+ +------------+ +-------------+
+ *	 | GA_ID =     | GA_ID =       | GA_ID =       | GA_ID =
+ *	 | LVA_MoveUp  | LVA_MoveDown  | LVA_MoveLeft  | LVA_MoveRight
+ *	 |             |               |               |
+ *	 |  +----------+               |               |
+ *	 |  |  +-----------------------+               |
+ *	 |  |  |  +------------------------------------+
+ *	 |  |  |  |        propgclass object     icclass object
+ *	 |  |  |  |          +-----------+      +--------------+
+ *	 |  |  |  |          |  HSlider  |<-----| PIPToHSlider |
+ *	 |  |  |  |          +-----------+      +--------------+
+ *	 |  |  |  |     PGA_Top =  |                 ^ LVA_Top  = PGA_Top
+ *	 |  |  |  |     LVA_Left   |                 | LVA_Visible = PGA_Visible
+ *	 |  |  |  |                |                 |
+ *	 V  V  V  V                V                 |
+ *	+-----------+         ***********            |
+ *	|           |-------->*         *------------+
+ *	| ListView  |         *  Model  *
+ *	|           |<--------*         *------------+
+ *	+-----------+         ***********            |
+ *	                           ^                 |
+ *	               PGA_Top =   |                 |
+ *	               LVA_Top     |                 V  icclass object
+ *	                     +-----------+      +--------------+
+ *	                     |  VSlider  |<-----| PIPToVSlider |
+ *	                     +-----------+      +--------------+
+ *	                   propgclass object     LVA_Top     = PGA_Top
+ *	                                         LVA_Visible = PGA_Visible
+ */
+
+static struct Gadget *CreateLVGadgets (struct LVHandle *lvhandle,
+	struct TagItem *moreTags)
+{
+	struct Screen	*scr = lvhandle->Scr;
+	ULONG SizeWidth = 18, SizeHeight = 11;	/* Default size */
+	struct Image	*SizeImage;
+
+	/* Create a size image to get its... uhm... size */
+	if (SizeImage = NewObject (NULL, SYSICLASS,
+		SYSIA_Which,	SIZEIMAGE,
+		SYSIA_DrawInfo,	lvhandle->DrawInfo,
+		TAG_DONE))
+	{
+		/* Get size gadget geometry */
+		GetAttr (IA_Width, SizeImage, &SizeWidth);
+		GetAttr (IA_Height, SizeImage, &SizeHeight);
+
+		/* And then get rid of it... */
+		DisposeObject (SizeImage);
+	}
+
+
+	if (lvhandle->UseListBox)
+	{
+		lvhandle->Gad[GAD_LV] = NewObject (ListBoxClass, NULL,
+			GA_ID,				GAD_LV,
+			GA_Left,			scr->WBorLeft,
+			GA_Top,				scr->WBorTop + scr->Font->ta_YSize + 1,
+			GA_RelWidth,		- SizeWidth - scr->WBorLeft,
+			GA_RelHeight,		- (scr->WBorTop + scr->Font->ta_YSize + SizeHeight + 1),
+			GA_DrawInfo,		lvhandle->DrawInfo,
+			lvhandle->Mode,		lvhandle->Items,
+			LVA_Total,			lvhandle->Total,
+			LVA_SelectArray,	lvhandle->SelectArray,
+			TAG_MORE,			moreTags);
+
+		return lvhandle->Gad[GAD_LV];
+	}
+
+	/* Code to create normal ListView class and its gadgets */
+
+
+	/* No need to check this: in case of failure we would just
+	 * get no images in the scroll buttons, but we can still try
+	 * to open our window.
+	 */
+	CreateImages (lvhandle->DrawInfo);
+
+	if (lvhandle->Model = NewObjectA (NULL, MODELCLASS, NULL))
+		if (lvhandle->Gad[GAD_LV] = NewObject (ListViewClass, NULL,
+			GA_ID,				GAD_LV,
+			GA_Left,			scr->WBorLeft,
+			GA_Top,				scr->WBorTop + scr->Font->ta_YSize + 1,
+			GA_RelWidth,		- SizeWidth - scr->WBorLeft,
+			GA_RelHeight,		- (scr->WBorTop + scr->Font->ta_YSize + SizeHeight + 1),
+			GA_DrawInfo,		lvhandle->DrawInfo,
+			ICA_TARGET,			lvhandle->Model,
+			lvhandle->Mode,		lvhandle->Items,
+			LVA_Total,			lvhandle->Total,
+			LVA_SelectArray,	lvhandle->SelectArray,
+			TAG_MORE,			moreTags))
+			if (lvhandle->Gad[GAD_VSLIDER] = NewObject (NULL, PROPGCLASS,
+				GA_ID,			GAD_VSLIDER,
+				GA_Previous,	lvhandle->Gad[GAD_LV],
+				GA_RelRight,	- SizeWidth + 5,
+				GA_Top,			scr->WBorTop + scr->Font->ta_YSize + 2,
+				GA_Width,		SizeWidth - 8,
+				GA_RelHeight,	- (scr->WBorTop + scr->Font->ta_YSize +
+								SizeHeight + ImgHeight[IMG_DOWN] + ImgHeight[IMG_UP] + 4),
+				GA_RightBorder,	TRUE,
+				GA_DrawInfo,	lvhandle->DrawInfo,
+				PGA_Freedom,	FREEVERT,
+				PGA_Borderless,	((lvhandle->DrawInfo->dri_Flags & DRIF_NEWLOOK) &&
+								 (lvhandle->DrawInfo->dri_Depth != 1)),
+				PGA_NewLook,	TRUE,
+				ICA_TARGET,		lvhandle->Model,
+				ICA_MAP,		MapVSliderToLV,
+				TAG_DONE))
+				if (lvhandle->Gad[GAD_HSLIDER] = NewObject (NULL, PROPGCLASS,
+					GA_ID,			GAD_HSLIDER,
+					GA_Previous,	lvhandle->Gad[GAD_VSLIDER],
+					GA_RelBottom,	- SizeHeight + ((SizeHeight > 15) ? 4 : 3),
+					GA_Left,		scr->WBorLeft,
+					GA_Height,		SizeHeight - ((SizeHeight > 15)  ? 6 : 4),
+					GA_RelWidth,	- (SizeWidth + ImgWidth[IMG_RIGHT] + ImgWidth[IMG_LEFT] + scr->WBorLeft + 2),
+					GA_BottomBorder,TRUE,
+					GA_DrawInfo,	lvhandle->DrawInfo,
+					PGA_Freedom,	FREEHORIZ,
+					PGA_Borderless,	((lvhandle->DrawInfo->dri_Flags & DRIF_NEWLOOK) &&
+									 (lvhandle->DrawInfo->dri_Depth != 1)),
+					PGA_NewLook,	TRUE,
+					ICA_TARGET,		lvhandle->Model,
+					ICA_MAP,		MapHSliderToLV,
+					TAG_DONE))
+					if (lvhandle->Gad[GAD_UPBUTTON] = NewObject (ScrollButtonClass, NULL,
+						GA_ID,			GAD_UPBUTTON,
+						GA_Previous,	lvhandle->Gad[GAD_HSLIDER],
+						GA_RelBottom,	- SizeHeight - ImgHeight[IMG_DOWN] - ImgHeight[IMG_UP] + 1,
+						GA_RelRight,	- ImgWidth[IMG_DOWN] + 1,
+						GA_RightBorder,	TRUE,
+						GA_DrawInfo,	lvhandle->DrawInfo,
+						GA_Image,		Img[IMG_UP],
+						ICA_TARGET,		lvhandle->Gad[GAD_LV],
+						ICA_MAP,		MapUpButtonToLV,
+						TAG_DONE))
+						if (lvhandle->Gad[GAD_DOWNBUTTON] = NewObject (ScrollButtonClass, NULL,
+							GA_ID,			GAD_DOWNBUTTON,
+							GA_Previous,	lvhandle->Gad[GAD_UPBUTTON],
+							GA_RelBottom,	- SizeHeight - ImgHeight[IMG_DOWN] + 1,
+							GA_RelRight,	- ImgWidth[IMG_DOWN] + 1,
+							GA_RightBorder,	TRUE,
+							GA_DrawInfo,	lvhandle->DrawInfo,
+							GA_Image,		Img[IMG_DOWN],
+							ICA_TARGET,		lvhandle->Gad[GAD_LV],
+							ICA_MAP,		MapDownButtonToLV,
+							TAG_DONE))
+							if (lvhandle->Gad[GAD_LEFTBUTTON] = NewObject (ScrollButtonClass, NULL,
+								GA_ID,			GAD_LEFTBUTTON,
+								GA_Previous,	lvhandle->Gad[GAD_DOWNBUTTON],
+								GA_RelBottom,	- ImgHeight[IMG_LEFT] + 1,
+								GA_RelRight,	- SizeWidth - ImgWidth[IMG_RIGHT] - ImgWidth[IMG_LEFT] + 1,
+								GA_BottomBorder,TRUE,
+								GA_DrawInfo,	lvhandle->DrawInfo,
+								GA_Image,		Img[IMG_LEFT],
+								ICA_TARGET,		lvhandle->Gad[GAD_LV],
+								ICA_MAP,		MapLeftButtonToLV,
+								TAG_DONE))
+								if (lvhandle->Gad[GAD_RIGHTBUTTON] = NewObject (ScrollButtonClass, NULL,
+									GA_ID,			GAD_RIGHTBUTTON,
+									GA_Previous,	lvhandle->Gad[GAD_LEFTBUTTON],
+									GA_RelBottom,	- ImgHeight[IMG_RIGHT] + 1,
+									GA_RelRight,	- SizeWidth - ImgWidth[IMG_RIGHT] + 1,
+									GA_BottomBorder,TRUE,
+									GA_DrawInfo,	lvhandle->DrawInfo,
+									GA_Image,		Img[IMG_RIGHT],
+									ICA_TARGET,		lvhandle->Gad[GAD_LV],
+									ICA_MAP,		MapRightButtonToLV,
+									TAG_DONE))
+									{
+										APTR icobject;
+
+										/* Connect VSlider to Model */
+
+										if (icobject = NewObject (NULL, ICCLASS,
+											ICA_TARGET,	lvhandle->Gad[GAD_VSLIDER],
+											ICA_MAP,	MapLVToVSlider,
+											TAG_DONE))
+											if (!DoMethod (lvhandle->Model, OM_ADDMEMBER, icobject))
+												DisposeObject (icobject);
+
+										/* Connect HSlider to Model */
+
+										if (icobject = NewObject (NULL, ICCLASS,
+											ICA_TARGET,	lvhandle->Gad[GAD_HSLIDER],
+											ICA_MAP,	MapLVToHSlider,
+											TAG_DONE))
+											if (!DoMethod (lvhandle->Model, OM_ADDMEMBER, icobject))
+												DisposeObject (icobject);
+
+										/* Connect Model to ListView */
+
+										SetAttrs (lvhandle->Model,
+											ICA_TARGET, lvhandle->Gad[GAD_LV],
+											TAG_DONE);
+
+										return lvhandle->Gad[GAD_LV];
+									}
+	DisposeGadgets (lvhandle);
+
+	return NULL;
+}
+
+
+
+static void DisposeGadgets (struct LVHandle *lvhandle)
+{
+	ULONG i;
+
+	for (i = 0; i < GAD_COUNT; i++)
+	{
+		DisposeObject (lvhandle->Gad[i]);
+		/* lvhandle->Gad[i] = NULL; */
+	}
+
+	/* Freeing the Model will also free its two targets */
+	DisposeObject (lvhandle->Model);
+	/* lvhandle->Model = NULL */
+}
+
+
+
+static void CreateItems (struct LVHandle *lvhandle)
+{
+	if ((lvhandle->Mode == LVA_StringList) || (lvhandle->Mode == LVA_ImageList))
+	{
+		struct Node		*node;
+		ULONG			 i, cnt;
+
+
+		if (lvhandle->Mode == LVA_StringList)
+			cnt = TESTSTRINGS_CNT;
+		else /* LVA_ImageList */
+			cnt = VG_IMGCOUNT * 8;
+
+
+		/* Build a list of nodes to test the list */
+
+		NEWLIST (&lvhandle->TestList);
+
+		for (i = 0; i < cnt; i++)
+		{
+			if (node = AllocMem (sizeof (struct Node), MEMF_PUBLIC))
+			{
+				if (lvhandle->Mode == LVA_StringList)
+					node->ln_Name = TestStrings[i];
+				else
+					node->ln_Name = (STRPTR) NewObject (NULL, VECTORGLYPHCLASS,
+						SYSIA_Which,	i % VG_IMGCOUNT,
+						SYSIA_DrawInfo,	lvhandle->DrawInfo,
+						IA_Width,		IMAGES_WIDTH,
+						IA_Height,		IMAGES_HEIGHT,
+						TAG_DONE);
+
+				/* Unselect all items */
+				node->ln_Type = 0;
+
+				ADDTAIL (&lvhandle->TestList, node);
+
+				lvhandle->Total++;
+			}
+		}
+
+		lvhandle->Items = &lvhandle->TestList;
+	}
+	else if (lvhandle->Mode == LVA_StringArray)
+	{
+		lvhandle->Items = TestStrings;
+		lvhandle->Total = TESTSTRINGS_CNT;
+		lvhandle->SelectArray = AllocVec (TESTSTRINGS_CNT * sizeof (ULONG),
+			MEMF_CLEAR | MEMF_PUBLIC);
+	}
+	else /* (lvhandle->Mode == LVA_ImageArray) */
+	{
+		lvhandle->Items = NULL;	/* No items	*/
+		lvhandle->Total = -1;	/* Unknown	*/
+	}
+}
+
+
+
+static void CreateImages (struct DrawInfo *dri)
+
+/* Create 4 arrow images for the window scroll buttons.
+ *
+ * Why bother checking for failure? The arrow images are not
+ * life critical in our program...
+ */
+{
+	static ULONG imagetypes[IMG_COUNT] = { UPIMAGE, DOWNIMAGE, LEFTIMAGE, RIGHTIMAGE };
+	ULONG i;
+
+	for (i = 0; i < IMG_COUNT; i++)
+		if (!Img[i])
+			if (Img[i] = (struct Image *)NewObject (NULL, SYSICLASS,
+				SYSIA_Which,	imagetypes[i],
+				SYSIA_DrawInfo,	dri,
+				TAG_DONE))
+			{
+				/* Ask image width and height */
+				GetAttr (IA_Width, Img[i], &ImgWidth[i]);
+				GetAttr (IA_Height, Img[i], &ImgHeight[i]);
+			}
+}
+
+
+
+static void FreeImages (void)
+{
+	ULONG i;
+
+	for (i = 0; i < IMG_COUNT; i++)
+		DisposeObject ((APTR)Img[i]);	/* DisposeObject(NULL) is safe */
+}
diff --git "a/LVDemo.\266" "b/LVDemo.\266"
new file mode 100644
index 0000000..d3914ce
--- /dev/null
+++ "b/LVDemo.\266"
@@ -0,0 +1,147 @@
+Storm Shell Project (0010)
+Settings (Start)
+C/C++ Environment
+"StormC:include"
+"Include:"
+Includepath (End)
+0 "LVDemo.StormHeader" 128
+0 "objects"
+C/C++ Preprozessor
+1 "_INLINE_INCLUDES" ""
+Defines (End)
+1 1 1
+C/C++ Options
+0 1 1 0 0 0 0 0 0 1 1 0 1
+C/C++ Optimizer
+9
+C/C++ Warnings
+1 1 1 1 1 1 0 1
+Assembler
+0 ""
+0 0
+Linker
+0 2 "startup_storm.o" 1 0 1 0 1 1 0
+"StormC:lib" 0 "StormC:lib/logfile" 0 1 1
+0 "_WizardSurface"
+0 0 50 0 50 0 50 0 0
+0 0 0 0 0 0 0 0
+Run
+16 "" "" "" 0
+""
+
+1 "CON://400/180/Storm Console/AUTO/WAIT/SCREEN StormScreen" "RAM:Output" "RAM:Input"
+1 0
+0 0 0 "" ""
+Settings (End)
+File
+1 "LVDemo.c"
+"LVDemo.c"
+"CompilerSpecific.h"
+"Debug.h"
+"BoopsiStubs.h"
+"ListMacros.h"
+"ListViewClass.h"
+"ListBoxClass.h"
+"VectorGlyphIClass.h"
+Storm Shell Project (Dependencies)
+"LVDemo.o" "LVDemo.debug"
+""
+"LVDemo.o" "LVDemo.debug"
+File
+1 "ListViewClass.c"
+"ListViewClass.c"
+"CompilerSpecific.h"
+"Debug.h"
+"BoopsiStubs.h"
+"ListViewClass.h"
+Storm Shell Project (Dependencies)
+"ListViewClass.o" "ListViewClass.debug"
+""
+"ListViewClass.o" "ListViewClass.debug"
+File
+1 "ListViewHooks.c"
+"ListViewHooks.c"
+"CompilerSpecific.h"
+"Debug.h"
+"ListViewClass.h"
+Storm Shell Project (Dependencies)
+"ListViewHooks.o" "ListViewHooks.debug"
+""
+"ListViewHooks.o" "ListViewHooks.debug"
+File
+1 "ListBoxClass.c"
+"ListBoxClass.c"
+"CompilerSpecific.h"
+"Debug.h"
+"BoopsiStubs.h"
+"ListViewClass.h"
+"ListBoxClass.h"
+Storm Shell Project (Dependencies)
+"ListBoxClass.o" "ListBoxClass.debug"
+""
+"ListBoxClass.o" "ListBoxClass.debug"
+Section
+1 1 100
+File
+2 "Work:SC/src/Include/CompilerSpecific.h"
+"Work:SC/src/Include/CompilerSpecific.h"
+Storm Shell Project (Dependencies)
+"" ""
+""
+File
+2 "Work:SC/src/Include/Debug.h"
+"Work:SC/src/Include/Debug.h"
+Storm Shell Project (Dependencies)
+"" ""
+""
+File
+2 "Work:SC/src/Include/BoopsiStubs.h"
+"Work:SC/src/Include/BoopsiStubs.h"
+Storm Shell Project (Dependencies)
+"" ""
+""
+File
+2 "ListViewClass.h"
+"ListViewClass.h"
+Storm Shell Project (Dependencies)
+"" ""
+""
+File
+2 "ListBoxClass.h"
+"ListBoxClass.h"
+Storm Shell Project (Dependencies)
+"" ""
+""
+File
+2 "Work:SC/src/Include/ListMacros.h"
+"Work:SC/src/Include/ListMacros.h"
+Storm Shell Project (Dependencies)
+"" ""
+""
+File
+2 "VectorGlyphIClass.h"
+"VectorGlyphIClass.h"
+Storm Shell Project (Dependencies)
+"" ""
+""
+Section
+2 1 95
+File
+12 "startup_storm.s"
+"startup_storm.s"
+Storm Shell Project (Dependencies)
+"startup_storm.o" ""
+""
+"startup_storm.o"
+Section
+12 1 90
+File
+10 "LVDemo"
+"LVDemo"
+Storm Shell Project (Dependencies)
+"" ""
+""
+"LVDemo.link"
+Section
+10 1 40
+Storm Shell Project (End)
diff --git a/ListBoxClass.c b/ListBoxClass.c
new file mode 100644
index 0000000..6c88ee3
--- /dev/null
+++ b/ListBoxClass.c
@@ -0,0 +1,770 @@
+/*
+**	ListBoxClass.c
+**
+**	Copyright (C) 1997,98 Bernardo Innocenti
+**
+**	Use 4 chars wide TABs to read this file
+**
+**	GadTools-like `boopsi' ListView group class
+*/
+
+#define USE_BUILTIN_MATH
+#define INTUI_V36_NAMES_ONLY
+#define __USE_SYSBASE
+#define  CLIB_ALIB_PROTOS_H		/* Avoid dupe defs of boopsi funcs */
+
+#include <exec/types.h>
+#include <exec/memory.h>
+#include <intuition/intuition.h>
+#include <intuition/intuitionbase.h>
+#include <intuition/classes.h>
+#include <intuition/gadgetclass.h>
+#include <intuition/icclass.h>
+#include <intuition/imageclass.h>
+
+#include <proto/exec.h>
+#include <proto/intuition.h>
+#include <proto/utility.h>
+
+#ifdef __STORM__
+	#pragma header
+#endif
+
+#include "CompilerSpecific.h"
+#include "Debug.h"
+#include "BoopsiStubs.h"
+
+#define LV_GADTOOLS_STUFF
+#include "ScrollButtonClass.h"
+#include "ListViewClass.h"
+#include "VectorGlyphIClass.h"
+#include "ListBoxClass.h"
+
+
+
+#define VSLIDER_WIDTH	14
+#define HSLIDER_HEIGHT	14
+
+
+/* Per-object instance data */
+struct LBData
+{
+	/* struct Gadget *ThisGadget; (not used) */
+
+	/* Group children */
+	Object *ListView;
+	Object *HSlider;
+	Object *VSlider;
+	Object *UpButton;
+	Object *DownButton;
+	Object *LeftButton;
+	Object *RightButton;
+	Object *LVToVSliderIC;
+	Object *LVToHSliderIC;
+
+	Object *Model;	/* The ic object that makes our children talk to each other */
+	Object *Frame;	/* The frame to put around the listbox object */
+
+	struct Image *UpImage;
+	struct Image *DownImage;
+	struct Image *LeftImage;
+	struct Image *RightImage;
+
+	/* Frame size */
+	LONG	FrameWidth, FrameHeight;
+
+	/* These two have the same meaning, but we keep both updated
+	 * because the Rectangle structure (MinX, MinY, MaxX, MaxY)
+	 * is more handy in some cases, while the IBox structure
+	 * (Left/Top/Width/Height) is best for other cases.
+	 */
+	struct IBox		 GBox;
+	struct Rectangle GRect;
+};
+
+
+/* Global class data */
+struct LBClassData
+{
+	Class		*ScrollButtonClass;
+	struct Library	*VectorGlyphBase;
+};
+
+
+extern Class *ListViewClass;
+
+
+/* Local function prototypes */
+static void		LB_GMRender		(Class *cl, struct Gadget *g, struct gpRender *msg);
+static void		LB_GMLayout		(Class *cl, struct Gadget *g, struct gpLayout *msg);
+static ULONG	LB_OMSet		(Class *cl, struct Gadget *g, struct opUpdate *msg);
+static ULONG	LB_OMGet		(Class *cl, struct Gadget *g, struct opGet *msg);
+static ULONG	LB_OMNew		(Class *cl, struct Gadget *g, struct opSet *msg);
+static void		LB_OMDispose	(Class *cl, struct Gadget *g, Msg msg);
+
+static void		CreateVSlider	(Class *cl, struct Gadget *g, struct LBData *lb, struct DrawInfo *dri);
+static void		CreateHSlider	(Class *cl, struct Gadget *g, struct LBData *lb, struct DrawInfo *dri);
+static void		DeleteVSlider	(struct Gadget *g, struct LBData *lb);
+static void		DeleteHSlider	(struct Gadget *g, struct LBData *lb);
+
+INLINE void		GetGadgetBox	(struct GadgetInfo *ginfo, struct ExtGadget *g, struct IBox *box, struct Rectangle *rect);
+
+
+
+/* Attribute translations for object interconnections */
+
+static LONG MapLVToHSlider[] =
+{
+	LVA_PixelLeft,		PGA_Top,
+	LVA_PixelWidth,		PGA_Total,
+	LVA_PixelHVisible,	PGA_Visible,
+	TAG_DONE
+};
+
+static LONG MapHSliderToLV[] =
+{
+	PGA_Top,			LVA_PixelLeft,
+	TAG_DONE
+};
+
+/*
+static LONG MapLVToVSlider[] =
+{
+	LVA_Top,			PGA_Top,
+	LVA_Total,			PGA_Total,
+	LVA_Visible,		PGA_Visible,
+	TAG_DONE
+};
+*/
+
+static LONG MapLVToVSlider[] =
+{
+	LVA_PixelTop,		PGA_Top,
+	LVA_PixelHeight,	PGA_Total,
+	LVA_PixelVVisible,	PGA_Visible,
+	TAG_DONE
+};
+
+
+/*
+static LONG MapVSliderToLV[] =
+{
+	PGA_Top,	LVA_Top,
+	TAG_DONE
+};
+*/
+
+static LONG MapVSliderToLV[] =
+{
+	PGA_Top,	LVA_PixelTop,
+	TAG_DONE
+};
+
+
+
+static LONG MapUpButtonToLV[] =
+{
+	GA_ID,		LVA_MoveUp,
+	TAG_DONE
+};
+
+static LONG MapDownButtonToLV[] =
+{
+	GA_ID,		LVA_MoveDown,
+	TAG_DONE
+};
+
+static LONG MapLeftButtonToLV[] =
+{
+	GA_ID,		LVA_MoveLeft,
+	TAG_DONE
+};
+
+static LONG MapRightButtonToLV[] =
+{
+	GA_ID,		LVA_MoveRight,
+	TAG_DONE
+};
+
+
+
+static ULONG HOOKCALL LBDispatcher (
+	REG(a0, Class *cl),
+	REG(a2, struct Gadget *g),
+	REG(a1, Msg msg))
+{
+	ASSERT_VALIDNO0(cl)
+	ASSERT_VALIDNO0(g)
+	ASSERT_VALIDNO0(msg)
+
+	switch (msg->MethodID)
+	{
+		case GM_RENDER:
+			LB_GMRender (cl, g, (struct gpRender *)msg);
+			return TRUE;
+
+		case GM_LAYOUT:
+			/* This method is only supported on V39 and above */
+			LB_GMLayout (cl, g, (struct gpLayout *)msg);
+			return TRUE;
+
+		case OM_SET:
+		case OM_UPDATE:
+			return LB_OMSet (cl, g, (struct opUpdate *)msg);
+
+		case OM_GET:
+			return LB_OMGet (cl, g, (struct opGet *)msg);
+
+		case OM_NEW:
+			return LB_OMNew (cl, g, (struct opSet *)msg);
+
+		case OM_DISPOSE:
+			LB_OMDispose (cl, g, msg);
+			return TRUE;
+
+		default:
+			/* Unsupported method: let our superclass's dispatcher take
+			 * a look at it. This includes all gadget methods sent
+			 * by Intuition: GM_RENDER, GM_HANDLEINPUT, GM_GOACTIVE and
+			 * GM_GOINACTIVE. These methods are automatically forwarded
+			 * to our child gadgets by the groupgclass.
+			 */
+			/* DB(kprintf("ListBoxClass: passing unknown method 0x%lx to superclass...\n", msg->MethodID);)
+			 */
+			return DoSuperMethodA (cl, (Object *)g, msg);
+	}
+}
+
+
+
+static void LB_GMRender (Class *cl, struct Gadget *g, struct gpRender *msg)
+{
+	struct LBData		*lb = INST_DATA (cl, g);
+
+	ASSERT_VALIDNO0(lb)
+
+
+#ifndef OS30_ONLY
+	/* Pre-V39 Intuition won't call our GM_LAYOUT method, so we must
+	 * always call it before redrawing the gadget.
+	 */
+	if ((IntuitionBase->LibNode.lib_Version < 39) &&
+		(msg->gpr_Redraw == GREDRAW_REDRAW))
+		LB_GMLayout (cl, g, (struct gpLayout *)msg);
+#endif /* !OS30_ONLY */
+
+
+	/* The groupgclass does not render its imagery,
+	 * it only calls GM_RENDER for all its children
+	 */
+	if (msg->gpr_Redraw == GREDRAW_REDRAW)
+	{
+		DB (kprintf ("ListBoxClass: GM_RENDER: msg->gpr_Redraw = GREDRAW_REDRAW\n");)
+
+		DoMethod (lb->Frame, IM_DRAWFRAME,
+			msg->gpr_RPort,								/* imp_RPort		*/
+			(lb->GBox.Left << 16) | (lb->GBox.Top),		/* imp_Offset		*/
+			IDS_NORMAL,									/* imp_State		*/
+			msg->gpr_GInfo->gi_DrInfo,					/* imp_DrInfo		*/
+			(lb->GBox.Width << 16) | (lb->GBox.Height));/* imp_Dimensions	*/
+	}
+
+	/* Forward message to superclass so it will call it on all our children */
+	DoSuperMethodA (cl, (Object *)g, (Msg)msg);
+}
+
+
+
+static void LB_GMLayout (Class *cl, struct Gadget *g, struct gpLayout *msg)
+{
+	struct LBData	*lb = (struct LBData *) INST_DATA (cl, (Object *)g);
+
+	DB (kprintf ("ListBoxClass: GM_LAYOUT\n");)
+	ASSERT_VALIDNO0(lb)
+
+
+	/* Collect new group size */
+	GetGadgetBox (msg->gpl_GInfo, (struct ExtGadget *)g, &lb->GBox, &lb->GRect);
+
+	/* Size our children accordingly */
+	SetAttrs (lb->ListView,
+		// msg->gpl_GInfo->gi_Window, msg->gpl_GInfo->gi_Requester,
+		GA_Left,	lb->GBox.Left + 2,						// +lb->FrameWidth,
+		GA_Top,		lb->GBox.Top + 2,						// +lb->FrameHeight,
+		GA_Width,	lb->GBox.Width - lb->FrameWidth - VSLIDER_WIDTH,
+		GA_Height,	lb->GBox.Height - HSLIDER_HEIGHT,		// -lb->FrameHeight,
+		TAG_DONE);
+
+	SetAttrs (lb->VSlider,
+		// msg->gpl_GInfo->gi_Window, msg->gpl_GInfo->gi_Requester,
+		GA_Left,	lb->GRect.MaxX - 2 - VSLIDER_WIDTH,		// lb->FrameWidth,
+		GA_Top,		lb->GBox.Top + 2,						// lb->FrameHeight,
+		GA_Width,	VSLIDER_WIDTH,
+		GA_Height,	lb->GBox.Height - HSLIDER_HEIGHT * 3,	// g->Height - 10,
+		TAG_DONE);
+
+	SetAttrs (lb->UpButton,
+		GA_Left,	lb->GRect.MaxX - 2 - VSLIDER_WIDTH,		// lb->FrameWidth,
+		GA_Top,		lb->GRect.MaxY - 2 - HSLIDER_HEIGHT * 2,// lb->FrameHeight,
+		GA_Width,	VSLIDER_WIDTH,
+		GA_Height,	HSLIDER_HEIGHT,
+		TAG_DONE);
+
+	DB (kprintf ("ListBoxClass: GM_LAYOUT: upbutton left: %ld, upbutton top: %ld\n",
+		lb->GRect.MaxX - 2 - VSLIDER_WIDTH, lb->GRect.MaxY - 2 - HSLIDER_HEIGHT * 2);)
+
+
+	SetAttrs (lb->DownButton,
+		GA_Left,	lb->GRect.MaxX - 2 - VSLIDER_WIDTH,		// lb->FrameWidth,
+		GA_Top,		lb->GRect.MaxY - 2 - HSLIDER_HEIGHT,	// lb->FrameHeight,
+		GA_Width,	VSLIDER_WIDTH,
+		GA_Height,	HSLIDER_HEIGHT,
+		TAG_DONE);
+
+
+	/* NOTE: it seems that the groupgclass does not forward GM_LAYOUT
+	 * to its children, so we must handle this here.
+	 */
+
+	/* Forward GM_LAYOUT to embedded listview */
+	DoMethodA (lb->ListView, (Msg)msg);
+}
+
+
+
+static ULONG LB_OMSet (Class *cl, struct Gadget *g, struct opUpdate *msg)
+{
+	struct LBData	*lb = (struct LBData *) INST_DATA (cl, (Object *)g);
+
+	DB (kprintf ("ListBoxClass: OM_SET\n");)
+	ASSERT_VALIDNO0(lb)
+
+	if (lb->ListView)
+		/* Forward attributes to our listview */
+		DoMethodA (lb->ListView, (Msg)msg);
+
+	/* Also forward to our superclass */
+	return DoSuperMethodA (cl, (Object *)g, (Msg) msg);
+}
+
+
+
+static ULONG LB_OMGet (Class *cl, struct Gadget *g, struct opGet *msg)
+{
+	struct LBData	*lb = (struct LBData *) INST_DATA (cl, (Object *)g);
+
+	DB (kprintf ("ListBoxClass: OM_GET\n");)
+	ASSERT_VALIDNO0(lb)
+
+	/* Forward this method to our listview */
+	return DoMethodA (lb->ListView, (Msg)msg);
+}
+
+
+
+static ULONG LB_OMNew (Class *cl, struct Gadget *g, struct opSet *msg)
+{
+	struct LBData	*lb;
+	struct DrawInfo	*dri;
+
+
+	DB (kprintf ("ListBoxClass: OM_NEW:\n");)
+
+	if (g = (struct Gadget *)DoSuperMethodA (cl, (Object *)g, (Msg)msg))
+	{
+		/* Set the GMORE_SCROLLRASTER flag */
+		if (g->Flags & GFLG_EXTENDED)
+		{
+			DB (kprintf ("ListBoxClass: OM_NEW: Setting GMORE_SCROLLRASTER\n");)
+			((struct ExtGadget *)g)->MoreFlags |= GMORE_SCROLLRASTER;
+		}
+
+		lb = (struct LBData *) INST_DATA (cl, (Object *)g);
+		ASSERT_VALIDNO0(lb)
+
+
+		/* Clear the object instance */
+		memset (lb, 0, sizeof (struct LBData));
+
+
+		/* Store a pointer to this object (a Gadget) in the class private
+		 * instance. This way we can avoid passing it to all functions.
+		 */
+		/* lb->ThisGadget = g; (not used) */
+
+
+		/* May be NULL */
+		dri = (struct DrawInfo *) GetTagData (GA_DrawInfo, NULL, msg->ops_AttrList);
+
+
+		/* Create a model object. This will be the core of the boopsi attributes
+		 * network used by the sub-objects to talk each other. We pass our
+		 * initalization tags to the model so it will pick up the correct
+		 * ICA_TARGET and ICA_MAP, if specified with NewObject().
+		 */
+		if (lb->Model = NewObjectA (NULL, MODELCLASS, NULL))
+		{
+			/* Create the ListView and pass all creation time attributes to it.
+			 * Note that any GA_#? attributes are also passed to the listview,
+			 * so it will have the same size of its container.
+			 */
+			if (lb->ListView = NewObjectA (ListViewClass, NULL, msg->ops_AttrList))
+			{
+				/* From now no, the groupgclass will dispose this object for us */
+				DoMethod ((Object *)g, OM_ADDMEMBER, lb->ListView);
+
+				//SetAttrs (lb->Model,
+				//	ICA_TARGET, lb->ListView,
+				//	TAG_DONE);
+
+				/* Connect Model to ListView */
+				{
+					APTR icobject;
+
+					if (icobject = NewObject (NULL, ICCLASS,
+						ICA_TARGET,	lb->ListView,
+						TAG_DONE))
+						if (!DoMethod (lb->Model, OM_ADDMEMBER, icobject))
+							DisposeObject (icobject);
+				}
+
+				/* Connect ListView to Model */
+				SetAttrs (lb->ListView,
+					ICA_TARGET, lb->Model,
+					TAG_DONE);
+
+				/* Add sliders */
+				CreateVSlider (cl, g, lb, dri);
+				CreateHSlider (cl, g, lb, dri);
+
+
+				/* Create a frame to put around us */
+
+				if (lb->Frame = NewObject (NULL, FRAMEICLASS,
+					IA_EdgesOnly,	TRUE,
+					IA_FrameType,	FRAME_BUTTON,
+					TAG_DONE))
+				{
+					struct IBox FrameBox, ContentsBox = { 0, 0, 0, 0 };
+
+					/* Ask the frame about its nominal frame width and height */
+					DoMethod ((Object *)lb->Frame, IM_FRAMEBOX, &ContentsBox, &FrameBox, dri, 0);
+
+					/* Remember it later */
+					lb->FrameWidth = FrameBox.Width;
+					lb->FrameHeight = FrameBox.Height;
+				}
+
+
+				/* Set the gadget width and height because the groupgclass
+				 * always forces them to 0 on creation.
+				 */
+				{
+					struct TagItem *tag;
+
+					if (tag = FindTagItem (GA_RelWidth, msg->ops_AttrList))
+						SetAttrs (g,
+							GA_RelWidth, tag->ti_Data,
+							TAG_DONE);
+					else
+						SetAttrs (g,
+							GA_Width, GetTagData (GA_Width, g->Width, msg->ops_AttrList),
+							TAG_DONE);
+
+					if (tag = FindTagItem (GA_RelHeight, msg->ops_AttrList))
+						SetAttrs (g,
+							GA_RelHeight, tag->ti_Data,
+							TAG_DONE);
+					else
+						SetAttrs (g,
+							GA_Height, GetTagData (GA_Height, g->Height, msg->ops_AttrList),
+							TAG_DONE);
+
+					DB (kprintf ("ListBoxClass: OM_NEW: set size to L=%ld T=%ld W=%ld H=%ld\n",
+						g->LeftEdge, g->TopEdge, g->Width, g->Height);)
+				}
+
+
+				/* TODO: Handle creation-time attributes */
+
+				return (ULONG)g;	/* Return newly created istance */
+			}
+		}
+
+
+		/* Dispose object without disturbing the dispatchers of our sub-classes, if any */
+		CoerceMethod (cl, (Object *)g, OM_DISPOSE);
+	}
+
+	return 0;	/* Fail */
+}
+
+
+
+static void LB_OMDispose (Class *cl, struct Gadget *g, Msg msg)
+{
+	struct LBData	*lb = (struct LBData *) INST_DATA (cl, (Object *)g);
+
+	ASSERT_VALIDNO0(lb)
+	DB (kprintf ("ListBoxClass: OM_DISPOSE\n");)
+
+	DeleteHSlider(g, lb);
+	DeleteVSlider(g, lb);
+
+	/* Dispose our subobjects which are not freed automatically */
+	DisposeObject (lb->Frame);
+	DisposeObject (lb->Model);
+
+	/* Our superclass will cleanup everything else now */
+	DoSuperMethodA (cl, (Object *)g, (Msg) msg);
+
+	/* From now on, our instance data is no longer available */
+}
+
+
+
+#define SCROLLBUTTON_CLASS_PTR	( ((struct LBClassData *)(cl->cl_UserData))->ScrollButtonClass )
+
+
+static void CreateVSlider (Class *cl, struct Gadget *g, struct LBData *lb, struct DrawInfo *dri)
+{
+	if (lb->VSlider = NewObject (NULL, PROPGCLASS,
+		GA_ID,			g->GadgetID,	/* Same as our ID */
+		GA_DrawInfo,	dri,
+		PGA_Freedom,	FREEVERT,
+		PGA_NewLook,	TRUE,
+		PGA_Borderless,	TRUE,
+		ICA_TARGET,		lb->Model,
+		ICA_MAP,		MapVSliderToLV,
+		TAG_DONE))
+	{
+		/* From now on, the groupgclass will dispose this object for us */
+		DoMethod ((Object *)g, OM_ADDMEMBER, lb->VSlider);
+
+		/* Connect Model to ListView */
+		SetAttrs (lb->Model,
+			ICA_TARGET, lb->ListView,
+			TAG_DONE);
+
+		/* Connect VSlider to Model */
+		if (lb->LVToVSliderIC = NewObject (NULL, ICCLASS,
+			ICA_TARGET,	lb->VSlider,
+			ICA_MAP,	MapLVToVSlider,
+			TAG_DONE))
+			if (!DoMethod (lb->Model, OM_ADDMEMBER, lb->LVToVSliderIC))
+				DisposeObject (lb->LVToVSliderIC);
+	}
+
+	/* We won't bother checking for failure because the image is
+	 * not life-critical in our object
+	 */
+	lb->UpImage = NewObject (NULL, SYSICLASS,
+		SYSIA_Which,	UPIMAGE,
+		SYSIA_DrawInfo,	dri,
+		TAG_DONE);
+
+	if (lb->UpButton = NewObject (SCROLLBUTTON_CLASS_PTR, NULL,
+		GA_ID,			g->GadgetID,
+		GA_DrawInfo,	dri,
+		GA_Image,		lb->UpImage,
+		ICA_TARGET,		lb->ListView,
+		ICA_MAP,		MapUpButtonToLV,
+		TAG_DONE))
+	{
+		/* From now on, the groupgclass will dispose this object for us */
+		DoMethod ((Object *)g, OM_ADDMEMBER, lb->UpButton);
+	}
+
+	/* We won't bother checking for failure because the image is
+	 * not life-critical in our object
+	 */
+	lb->DownImage = NewObject (NULL, SYSICLASS,
+		SYSIA_Which,	DOWNIMAGE,
+		SYSIA_DrawInfo,	dri,
+		TAG_DONE);
+
+	if (lb->DownButton = NewObject (SCROLLBUTTON_CLASS_PTR, NULL,
+		GA_ID,			g->GadgetID,
+		GA_DrawInfo,	dri,
+		GA_Image,		lb->DownImage,
+		ICA_TARGET,		lb->ListView,
+		ICA_MAP,		MapDownButtonToLV,
+		TAG_DONE))
+	{
+		/* From now on, the groupgclass will dispose this object for us */
+		DoMethod ((Object *)g, OM_ADDMEMBER, lb->DownButton);
+	}
+}
+
+
+
+static void CreateHSlider (Class *cl, struct Gadget *g, struct LBData *lb, struct DrawInfo *dri)
+{
+	/* TODO */
+}
+
+
+static void DeleteVSlider (struct Gadget *g, struct LBData *lb)
+{
+	if (lb->DownButton)
+	{
+		ASSERT_VALID(lb->DownButton)
+
+		DoMethod ((Object *)g, OM_REMMEMBER, lb->DownButton);
+		DisposeObject (lb->DownButton);
+		lb->DownButton = NULL;
+	}
+
+	if (lb->DownImage)
+	{
+		ASSERT_VALID(lb->DownImage)
+
+		DisposeObject (lb->DownImage);
+		lb->DownImage = NULL;
+	}
+
+	if (lb->UpButton)
+	{
+		ASSERT_VALID(lb->UpButton)
+
+		DoMethod ((Object *)g, OM_REMMEMBER, lb->UpButton);
+		DisposeObject (lb->UpButton);
+		lb->UpButton = NULL;
+	}
+
+	if (lb->UpImage)
+	{
+		ASSERT_VALID(lb->UpImage)
+
+		DisposeObject (lb->UpImage);
+		lb->UpImage = NULL;
+	}
+
+	if (lb->LVToVSliderIC)
+	{
+		ASSERT_VALID(lb->LVToVSliderIC)
+		ASSERT_VALID(lb->Model)
+
+		DoMethod (lb->Model, OM_REMMEMBER, lb->LVToVSliderIC);
+		DisposeObject (lb->LVToVSliderIC);
+		lb->LVToVSliderIC = NULL;
+	}
+
+	if (lb->VSlider)
+	{
+		ASSERT_VALID(lb->VSlider)
+
+		DoMethod ((Object *)g, OM_REMMEMBER, lb->VSlider);
+		DisposeObject (lb->VSlider);
+		lb->VSlider = NULL;
+	}
+}
+
+
+
+static void DeleteHSlider (struct Gadget *g, struct LBData *lb)
+{
+	/* TODO */
+}
+
+
+
+INLINE void GetGadgetBox (struct GadgetInfo *ginfo, struct ExtGadget *g, struct IBox *box, struct Rectangle *rect)
+
+/* Gets the actual IBox where a gadget exists in a window.
+ * The special cases it handles are all the REL#? (relative positioning flags).
+ *
+ * This function returns the gadget size in both the provided IBox and
+ * Rectangle structures, computing the values from the coordinates of the
+ * gadget and the window where it lives.
+ */
+{
+	ASSERT_VALIDNO0(g)
+	ASSERT_VALIDNO0(ginfo)
+	ASSERT_VALIDNO0(box)
+	ASSERT_VALIDNO0(rect)
+
+	DB (if (g->Flags & GFLG_EXTENDED)
+		kprintf ("ListBoxClass: GetGadgetBox(): GFLG_EXTENDED is set\n");)
+
+	DB (if ((g->Flags & GFLG_EXTENDED) && (g->MoreFlags & GMORE_BOUNDS))
+		kprintf ("ListBoxClass: GetGadgetBox(): Gadget has valid bounds\n");)
+
+	box->Left = g->LeftEdge;
+	if (g->Flags & GFLG_RELRIGHT)
+		box->Left += ginfo->gi_Domain.Width - 1;
+
+	box->Top = g->TopEdge;
+	if (g->Flags & GFLG_RELBOTTOM)
+		box->Top += ginfo->gi_Domain.Height - 1;
+
+	box->Width = g->Width;
+	if (g->Flags & GFLG_RELWIDTH)
+		box->Width += ginfo->gi_Domain.Width;
+
+	box->Height = g->Height;
+	if (g->Flags & GFLG_RELHEIGHT)
+		box->Height += ginfo->gi_Domain.Height;
+
+	/* Convert IBox to Rectangle coordinates system */
+	rect->MinX = box->Left;
+	rect->MinY = box->Top;
+	rect->MaxX = box->Left + box->Width - 1;
+	rect->MaxY = box->Top + box->Height - 1;
+
+
+	DB (kprintf ("ListBoxClass: GetGadgetBox(): Left = %ld, Top = %ld, Width = %ld, Height = %ld\n",
+		box->Left, box->Top, box->Width, box->Height);)
+}
+
+
+
+Class *MakeListBoxClass (void)
+{
+	Class *class;
+	struct LBClassData *classdata;
+
+	if (class = MakeClass (NULL, GROUPGCLASS, NULL, sizeof (struct LBData), 0))
+	{
+		class->cl_Dispatcher.h_Entry = (ULONG (*)()) LBDispatcher;
+
+		/* Allocate storage for global class data */
+		if (classdata = AllocMem (sizeof (struct LBClassData), MEMF_PUBLIC | MEMF_CLEAR))
+		{
+			class->cl_UserData = (ULONG) classdata;
+
+			classdata->ScrollButtonClass = MakeScrollButtonClass();
+			classdata->VectorGlyphBase = OpenLibrary ("images/vectorglyph.image", 0);
+			return class;
+		}
+
+		FreeListBoxClass (class);
+	}
+
+	return NULL;
+}
+
+
+
+void FreeListBoxClass (Class *class)
+{
+	struct LBClassData *classdata;
+
+	if (class)
+	{
+		ASSERT_VALID(class)
+		if (classdata = (struct LBClassData *)class->cl_UserData)
+		{
+			ASSERT_VALID(classdata)
+			ASSERT_VALID(classdata->ScrollButtonClass)
+			ASSERT_VALID(classdata->VectorGlyphBase)
+
+			/* Cleanup global class data */
+			CloseLibrary (classdata->VectorGlyphBase); /* NULL is safe since V36 */
+			FreeScrollButtonClass (classdata->ScrollButtonClass);
+			FreeMem (classdata, sizeof (struct LBClassData));
+		}
+
+		FreeClass (class);
+	}
+}
diff --git a/ListBoxClass.h b/ListBoxClass.h
new file mode 100644
index 0000000..0d65bb8
--- /dev/null
+++ b/ListBoxClass.h
@@ -0,0 +1,36 @@
+#ifndef LISTBOXCLASS_H
+#define LISTBOXCLASS_H
+/*
+**	ListBoxClass.h
+**
+**	Copyright (C) 1997 by Bernardo Innocenti
+**
+**	ListBox class built on top of the "groupgclass".
+**
+*/
+
+#define LISTBOXCLASS	"listboxclass"
+#define LISTBOXVERS		1
+
+
+Class	*MakeListBoxClass (void);
+void	 FreeListBoxClass (Class *ListViewClass);
+
+
+
+/*****************/
+/* Class Methods */
+/*****************/
+
+/* This class does not define any new methods */
+
+/********************/
+/* Class Attributes */
+/********************/
+
+/* #define LBA_Dummy (TAG_USER | ('L'<<16) | ('B'<<8)) */
+
+/* This class does not define any new attributes */
+
+
+#endif /* !LISTBOXCLASS_H */
diff --git a/ListMacros.h b/ListMacros.h
new file mode 100644
index 0000000..4b7d808
--- /dev/null
+++ b/ListMacros.h
@@ -0,0 +1,98 @@
+#ifndef LISTMACROS_H
+#define LISTMACROS_H
+/*
+**	$VER: ListMacros.h 2.1 (1.9.97)
+**
+**	Copyright (C) 1996,97 Bernardo Innocenti. All rights reserved.
+**
+**	Use 4 chars wide TABs to read this source
+**
+**	Some handy macros for list operations.  Using these macros is faster
+**	than calling their exec.library equivalents, but they will eventually
+**	make your code a little bigger and are also subject to common macro
+**	side effects.
+*/
+
+
+#define NEWLIST(l)	( (l)->lh_TailPred = (struct Node *)(l),			\
+					(l)->lh_Tail = 0,									\
+					(l)->lh_Head = (struct Node *)(&((l)->lh_Tail)) )
+
+#define ADDHEAD(l,n) ( (n)->ln_Pred = (struct Node *)(l),				\
+					(n)->ln_Succ = (l)->lh_Head,						\
+					(l)->lh_Head->ln_Pred = (n),						\
+					(l)->lh_Head = (n) )
+
+#define ADDTAIL(l,n) ( (n)->ln_Succ = (struct Node *)(&((l)->lh_Tail)),	\
+					(n)->ln_Pred = (l)->lh_TailPred,					\
+					(l)->lh_TailPred->ln_Succ = (n),					\
+					(l)->lh_TailPred = (n) )
+
+#define REMOVE(n)	( (n)->ln_Succ->ln_Pred = (n)->ln_Pred,				\
+					(n)->ln_Pred->ln_Succ = (n)->ln_Succ )
+
+#define GETHEAD(l)	( (l)->lh_Head->ln_Succ ? (l)->lh_Head : (struct Node *)NULL )
+
+#define GETTAIL(l)  ( (l)->lh_TailPred->ln_Succ ? (l)->lh_TailPred : (struct Node *)NULL )
+
+#define GETSUCC(n)  ( (n)->ln_Succ->ln_Succ ? (n)->ln_Succ : (struct Node *)NULL )
+
+#define GETPRED(n)  ( (n)->ln_Pred->ln_Pred ? (n)->ln_Pred : (struct Node *)NULL )
+
+
+#ifdef __GNUC__
+
+#define REMHEAD(l)															\
+({																			\
+	struct Node *n = (l)->lh_Head;											\
+	n->ln_Succ ?															\
+		(l)->lh_Head = n->ln_Succ,											\
+			(l)->lh_Head->ln_Pred = (struct Node *)(l),						\
+			n :																\
+		NULL;																\
+})
+
+#define REMTAIL(l)															\
+({																			\
+	struct Node *n = (l)->lh_TailPred;										\
+	n->ln_Pred ?															\
+		(l)->lh_TailPred = n->ln_Pred,										\
+			(l)->lh_TailPred->ln_Succ = (struct Node *)(&((l)->lh_Tail)),	\
+			n :																\
+		NULL;																\
+})
+
+
+#else
+
+/* These two can't be implemented as macros without the GCC ({...}) language extension */
+
+INLINE struct Node *REMHEAD(struct List *l)
+{
+	struct Node *n = l->lh_Head;
+
+	if (n->ln_Succ)
+	{
+		l->lh_Head = n->ln_Succ;
+		l->lh_Head->ln_Pred = (struct Node *)l;
+		return n;
+	}
+	return NULL;
+}
+
+INLINE struct Node *REMTAIL(struct List *l)
+{
+	struct Node *n = l->lh_TailPred;
+
+	if (n->ln_Pred)
+	{
+		l->lh_TailPred = n->ln_Pred;
+		l->lh_TailPred->ln_Succ = (struct Node *)(&(l->lh_Tail));
+		return n;
+	}
+	return NULL;
+}
+
+#endif
+
+#endif /* !LISTMACROS_H */
diff --git a/ListViewClass.c b/ListViewClass.c
new file mode 100644
index 0000000..d4ceb17
--- /dev/null
+++ b/ListViewClass.c
@@ -0,0 +1,2181 @@
+/*	ListViewClass.c
+**
+**	Copyright (C) 1996,97 Bernardo Innocenti
+**
+**	Use 4 chars wide TABs to read this file
+**
+**	GadTools-like `boopsi' ListView gadget class
+*/
+
+#define USE_BUILTIN_MATH
+#define INTUI_V36_NAMES_ONLY
+#define __USE_SYSBASE
+#define  CLIB_ALIB_PROTOS_H		/* Avoid dupe defs of boopsi funcs */
+
+#include <exec/types.h>
+#include <exec/libraries.h>
+#include <intuition/intuition.h>
+#include <intuition/intuitionbase.h>
+#include <intuition/classes.h>
+#include <intuition/gadgetclass.h>
+#include <graphics/gfxbase.h>
+#include <graphics/gfxmacros.h>
+
+#include <proto/intuition.h>
+#include <proto/graphics.h>
+#include <proto/layers.h>
+#include <proto/utility.h>
+
+#ifdef __STORM__
+	#pragma header
+#endif
+
+#include "CompilerSpecific.h"
+#include "Debug.h"
+#include "BoopsiStubs.h"
+
+#define LV_GADTOOLS_STUFF
+#include "ListViewClass.h"
+
+
+
+/* ListView private instance data */
+
+
+/* Type of a listview hook function */
+typedef	ASMCALL APTR	LVHook(
+	REG(a0, struct Hook	*hook), REG(a1, APTR item), REG(a2, struct lvGetItem *lvg));
+typedef	ASMCALL APTR	LVDrawHook(
+	REG(a0, struct Hook	*hook), REG(a1, APTR item), REG(a2, struct lvDrawItem *lvdi));
+
+struct LVData
+{
+	APTR			 Items;				/* The list/array of items				*/
+	LONG			 Top;				/* Ordinal nr. of the top visible item	*/
+	APTR			 TopPtr;			/* Pointer to the top visible item		*/
+	LONG			 Total;				/* Total nr. of items in the list		*/
+	LONG			 Visible;			/* Number of items visible in the list	*/
+	LONG			 PixelTop;			/* Pixel-wise offset from the top		*/
+	LONG			 Selected;			/* Ordinal nr. of the selected item		*/
+	APTR			 SelectedPtr;		/* Pointer to the selected item			*/
+	ULONG			 SelectCount;		/* Number of items currently selected	*/
+	ULONG			 MaxSelect;			/* Maximum nr. of selections to allow	*/
+
+	/* Old values used to track scrolling amount in GM_RENDER */
+	LONG			 OldTop;
+	LONG			 OldPixelTop;
+	LONG			 OldSelected;
+	APTR			 OldSelectedPtr;
+
+	ULONG			 DragSelect;		/* Status of drag selection				*/
+	LONG			 ItemHeight;		/* Height of one item in pixels			*/
+	LONG			 Spacing;			/* Spacing between items in pixels		*/
+	LONG			 MaxScroll;			/* Redraw all when scrolling too much	*/
+	LONG			 ScrollRatio;		/* max visible/scrolled ratio			*/
+	ULONG			*SelectArray;		/* Array of selected items. May be NULL	*/
+	LONG			 BackupSelected;	/* Used by RMB undo 					*/
+	LONG			 BackupPixelTop;	/* Used by RMB undo						*/
+	WORD			 MiddleMouseY;		/* Initial Y position for MMB scrolling	*/
+	ULONG			 Flags;				/* See <listviewclass.h>				*/
+	ULONG			 MaxPen;			/* Highest pen number used				*/
+	ULONG			 DoubleClickSecs, DoubleClickMicros;
+
+	/* User or internal hooks */
+	LVHook			*GetItemFunc;
+	LVHook			*GetNextFunc;
+	LVHook			*GetPrevFunc;
+	LVHook			*DrawBeginFunc;
+	LVHook			*DrawEndFunc;
+	LVDrawHook		*DrawItemFunc;
+	struct Hook		*CallBack;			/* Callback hook provided by user	*/
+
+	struct TextFont	*Font;				/* Font used to render text labels	*/
+	struct Region	*ClipRegion;		/* Used in LVA_Clipped mode			*/
+
+	/* These two have the same meaning, but we keep both updated
+	 * because the Rectangle structure (MinX, MinY, MaxX, MaxY)
+	 * is more handy in some cases, while the IBox structure
+	 * (Left/Top/Width/Height) is best for other cases.
+	 */
+	struct IBox		 GBox;
+	struct Rectangle GRect;
+};
+
+
+
+/* Local function prototypes */
+
+static void		LV_GMRender		(Class *cl, struct Gadget *g, struct gpRender *msg);
+static ULONG	LV_GMGoActive	(Class *cl, struct Gadget *g, struct gpInput *msg);
+static ULONG	LV_GMHandleInput(Class *cl, struct Gadget *g, struct gpInput *msg);
+static void		LV_GMGoInactive	(Class *cl, struct Gadget *g, struct gpGoInactive *msg);
+static void		LV_GMLayout		(Class *cl, struct Gadget *g, struct gpLayout *msg);
+static ULONG	LV_OMSet		(Class *cl, struct Gadget *g, struct opUpdate *msg);
+static ULONG	LV_OMGet		(Class *cl, struct Gadget *g, struct opGet *msg);
+static ULONG	LV_OMNew		(Class *cl, struct Gadget *g, struct opSet *msg);
+static void		LV_OMDispose	(Class *cl, struct Gadget *g, Msg msg);
+
+static void		RedrawItems		(struct LVData *lv, struct gpRender *msg, ULONG first, ULONG last, APTR item);
+INLINE LONG		ItemHit			(struct LVData *lv, WORD x, WORD y);
+INLINE void		GetGadgetBox	(struct GadgetInfo *ginfo, struct ExtGadget *g, struct IBox *box, struct Rectangle *rect);
+INLINE	APTR	GetItem			(struct LVData *lv, ULONG num);
+INLINE	APTR	GetNext			(struct LVData *lv, APTR item, ULONG num);
+INLINE	APTR	GetPrev			(struct LVData *lv, APTR item, ULONG num);
+INLINE ULONG	CountNodes		(struct List *list);
+static ULONG	CountSelections	(struct LVData *lv);
+INLINE ULONG	IsItemSelected	(struct LVData *lv, APTR item, ULONG num);
+
+/* Definitions for the builtin List hooks */
+LVHook		ListGetItem;
+LVHook		ListGetNext;
+LVHook		ListGetPrev;
+LVDrawHook	ListStringDrawItem;
+LVDrawHook	ListImageDrawItem;
+
+/* Definitions for the builtin Array hooks */
+LVHook		ArrayGetItem;
+LVDrawHook	StringDrawItem;
+LVDrawHook	ImageDrawItem;
+
+
+
+static ULONG HOOKCALL LVDispatcher (
+	REG(a0, Class *cl),
+	REG(a2, struct Gadget *g),
+	REG(a1, Msg msg))
+
+/* ListView class dispatcher - Handles all supported methods */
+{
+	ASSERT_VALIDNO0(cl)
+	ASSERT_VALIDNO0(g)
+	ASSERT_VALIDNO0(msg)
+
+	switch (msg->MethodID)
+	{
+		case GM_RENDER:
+			LV_GMRender (cl, g, (struct gpRender *)msg);
+			return TRUE;
+
+		case GM_GOACTIVE:
+			return LV_GMGoActive (cl, g, (struct gpInput *)msg);
+
+		case GM_HANDLEINPUT:
+			return LV_GMHandleInput (cl, g, (struct gpInput *)msg);
+
+		case GM_GOINACTIVE:
+			LV_GMGoInactive (cl, g, (struct gpGoInactive *)msg);
+			return TRUE;
+
+		case GM_LAYOUT:
+			/* This method is only supported on V39 and above */
+			LV_GMLayout (cl, g, (struct gpLayout *)msg);
+			return TRUE;
+
+		case OM_SET:
+		case OM_UPDATE:
+			return LV_OMSet (cl, g, (struct opUpdate *)msg);
+
+		case OM_GET:
+			return LV_OMGet (cl, g, (struct opGet *)msg);
+
+		case OM_NEW:
+			return LV_OMNew (cl, g, (struct opSet *)msg);
+
+		case OM_DISPOSE:
+			LV_OMDispose (cl, g, msg);
+			return TRUE;
+
+		default:
+			/* Unsupported method: let our superclass's
+			 * dispatcher take a look at it.
+			 */
+			return DoSuperMethodA (cl, (Object *)g, msg);
+	}
+}
+
+
+
+INLINE void GetItemBounds (struct LVData *lv, struct Rectangle *rect, LONG item)
+
+/* Compute the bounding box to render the given item and store it in the passed
+ * Rectangle structure.
+ */
+{
+	ASSERT_VALIDNO0(lv)
+	ASSERT_VALIDNO0(rect)
+	ASSERT(item < lv->Total)
+	ASSERT(item >= 0)
+
+	rect->MinX = lv->GRect.MinX;
+	rect->MaxX = lv->GRect.MaxX;
+	rect->MinY = lv->ClipRegion ?
+		(lv->GRect.MinY + item * (lv->ItemHeight + lv->Spacing) - lv->PixelTop) :
+		(lv->GRect.MinY + (item - lv->Top) * (lv->ItemHeight + lv->Spacing));
+	rect->MaxY = rect->MinY + lv->ItemHeight - 1;
+}
+
+
+
+static void RedrawItems (struct LVData *lv, struct gpRender *msg, ULONG first, ULONG last, APTR item)
+
+/* Redraw items from <min> to <max>.  No sanity checks are performed
+ * to ensure that all items between <min> and <max> are really visible.
+ */
+{
+	struct lvDrawItem lvdi;
+	LONG selected;
+
+
+	ASSERT_VALIDNO0(lv)
+	ASSERT_VALIDNO0(msg)
+	ASSERT(first <= last)
+	ASSERT(last < lv->Total)
+
+	DB (kprintf ("  RedrawItems (first = %ld, last = %ld)\n", first, last);)
+
+
+	lvdi.lvdi_Current	= first;
+	lvdi.lvdi_Items		= lv->Items;
+	lvdi.lvdi_RastPort	= msg->gpr_RPort;
+	lvdi.lvdi_DrawInfo	= msg->gpr_GInfo->gi_DrInfo;
+	lvdi.lvdi_Flags		= lv->Flags;
+
+	GetItemBounds (lv, &lvdi.lvdi_Bounds, first);
+
+	if (!item)
+	{
+		lvdi.lvdi_MethodID = LV_GETITEM;
+		item = lv->GetItemFunc (lv->CallBack, NULL, (struct lvGetItem *)&lvdi);
+	}
+
+	if (lv->DrawBeginFunc)
+	{
+		lvdi.lvdi_MethodID	= LV_DRAWBEGIN;
+		lv->DrawBeginFunc (lv->CallBack, item, (struct lvDrawBegin *)&lvdi);
+	}
+
+	for (;;)
+	{
+		if (lv->Flags & LVF_DOMULTISELECT)
+		{
+			if (lv->SelectArray)
+				/* Array selection */
+				selected = lv->SelectArray[lvdi.lvdi_Current];
+			else
+				if (lv->Flags & LVF_LIST)
+					/* Node selection */
+					selected = (((struct Node *)item)->ln_Type);
+				else
+					selected = 0;
+		}
+		else
+			/* Single selection */
+			selected = (lvdi.lvdi_Current == lv->Selected);
+
+		lvdi.lvdi_State = selected ? LVR_SELECTED : LVR_NORMAL;
+
+		lvdi.lvdi_MethodID	= LV_DRAW;
+		lv->DrawItemFunc (lv->CallBack, item, &lvdi);
+
+		if (++lvdi.lvdi_Current > last)
+			break;
+
+		lvdi.lvdi_MethodID	= LV_GETNEXT;
+		item = lv->GetNextFunc (lv->CallBack, item, (struct lvGetNext *)&lvdi);
+
+		lvdi.lvdi_Bounds.MinY += lv->ItemHeight + lv->Spacing;
+		lvdi.lvdi_Bounds.MaxY += lv->ItemHeight + lv->Spacing;
+	}
+
+	if (lv->DrawEndFunc)
+	{
+		lvdi.lvdi_MethodID	= LV_DRAWEND;
+		lv->DrawEndFunc (lv->CallBack, item, (struct lvDrawEnd *)&lvdi);
+	}
+}
+
+
+
+static void LV_GMRender (Class *cl, struct Gadget *g, struct gpRender *msg)
+{
+	struct LVData		*lv = INST_DATA (cl, g);
+	struct RastPort		*rp = msg->gpr_RPort;
+
+	ASSERT_VALIDNO0(lv)
+	ASSERT_VALIDNO0(rp)
+
+	DB (kprintf ("GM_RENDER: msg->gpr_Redraw = %s\n",
+		(msg->gpr_Redraw == GREDRAW_TOGGLE) ? "GREDRAW_TOGGLE" :
+		((msg->gpr_Redraw == GREDRAW_REDRAW) ? "GREDRAW_REDRAW" :
+		((msg->gpr_Redraw == GREDRAW_UPDATE) ? "GREDRAW_UPDATE" :
+		"*** Unknown ***")) );)
+
+
+#ifndef OS30_ONLY
+	/* Pre-V39 Intuition won't call our GM_LAYOUT method, so we must
+	 * always call it before redrawing the gadget.
+	 */
+	if ((IntuitionBase->LibNode.lib_Version < 39) &&
+		(msg->gpr_Redraw == GREDRAW_REDRAW))
+		LV_GMLayout (cl, g, (struct gpLayout *)msg);
+#endif /* !OS30_ONLY */
+
+	if (lv->Flags & LVF_DONTDRAW)
+		return;
+
+	if (lv->Items && lv->Visible)
+	{
+		struct TextFont *oldfont = NULL;
+		struct Region *oldregion = NULL;
+
+		if (rp->Font != lv->Font)
+		{
+			oldfont = rp->Font;
+			SetFont (rp, lv->Font);
+		}
+
+		if (lv->ClipRegion)
+		{
+			ASSERT_VALIDNO0(lv->ClipRegion)
+			oldregion = InstallClipRegion (rp->Layer, lv->ClipRegion);
+		}
+
+		switch (msg->gpr_Redraw)
+		{
+			case GREDRAW_TOGGLE:	/* Toggle selected item */
+			{
+				BOOL	drawnew = (lv->Selected >= lv->Top) && (lv->Selected < lv->Top + lv->Visible),
+						drawold = (lv->OldSelected >= lv->Top) && (lv->OldSelected < lv->Top + lv->Visible);
+
+				if (drawold || drawnew)
+				{
+					struct lvDrawItem	 lvdi;
+					lvdi.lvdi_Items		= lv->Items;
+					lvdi.lvdi_RastPort	= rp;
+					lvdi.lvdi_DrawInfo	= msg->gpr_GInfo->gi_DrInfo;
+					lvdi.lvdi_Flags		= lv->Flags;
+
+
+					if (lv->DrawBeginFunc)
+					{
+						lvdi.lvdi_MethodID	= LV_DRAWBEGIN;
+						lv->DrawBeginFunc (lv->CallBack, NULL, (struct lvDrawBegin *)&lvdi);
+					}
+
+					lvdi.lvdi_MethodID	= LV_DRAW;
+
+					if (drawnew)
+					{
+						GetItemBounds (lv, &lvdi.lvdi_Bounds, lv->Selected);
+						lvdi.lvdi_State = IsItemSelected (lv, lv->SelectedPtr, lv->Selected) ?
+							LVR_SELECTED : LVR_NORMAL;
+						lvdi.lvdi_Current = lv->Selected;
+
+						lv->DrawItemFunc (lv->CallBack, lv->SelectedPtr, &lvdi);
+					}
+
+					if (drawold)
+					{
+						GetItemBounds (lv, &lvdi.lvdi_Bounds, lv->OldSelected);
+						lvdi.lvdi_State = IsItemSelected (lv, lv->OldSelectedPtr, lv->OldSelected) ?
+							LVR_SELECTED : LVR_NORMAL;
+						lvdi.lvdi_Current = lv->OldSelected;
+
+						lv->DrawItemFunc (lv->CallBack, lv->OldSelectedPtr, &lvdi);
+					}
+
+					if (lv->DrawEndFunc)
+					{
+						lvdi.lvdi_MethodID	= LV_DRAWEND;
+						lv->DrawEndFunc (lv->CallBack, NULL, (struct lvDrawEnd *)&lvdi);
+					}
+				}
+
+				lv->OldSelected = lv->Selected;
+				lv->OldSelectedPtr = lv->SelectedPtr;
+
+				break;
+			}
+
+			case GREDRAW_REDRAW:	/* Redraw everything */
+			{
+				LONG	ycoord;
+
+				/* Set the background pen */
+				SetAPen (rp, msg->gpr_GInfo->gi_DrInfo->dri_Pens[BACKGROUNDPEN]);
+				/* SetAPen (rp, -1); Used to debug clearing code */
+
+				/* Now clear the spacing between the items */
+				if (lv->Spacing && lv->Items && lv->Visible)
+				{
+					LONG i, lastitem;
+
+					ycoord = lv->GRect.MinY + lv->ItemHeight;
+					lastitem = min (lv->Visible, lv->Total - lv->Top) - 1;
+
+					for (i = 0 ; i < lastitem; i++)
+					{
+						RectFill (rp, lv->GRect.MinX, ycoord,
+							lv->GRect.MaxX, ycoord + lv->Spacing - 1);
+
+						ycoord += lv->ItemHeight + lv->Spacing;
+					}
+				}
+				else
+					ycoord = lv->GRect.MinY + min (lv->Visible, lv->Total - lv->Top)
+						* lv->ItemHeight;
+
+				/* Now let's clear bottom part of gadget */
+				RectFill (rp, lv->GRect.MinX, ycoord,
+					lv->GRect.MaxX, lv->GRect.MaxY);
+
+				/* Finally, draw the items */
+				RedrawItems (lv, msg, lv->Top,
+					min (lv->Top + lv->Visible, lv->Total) - 1, lv->TopPtr);
+
+				break;
+			}
+
+			case GREDRAW_UPDATE:	/* Scroll ListView */
+			{
+				LONG scroll_dy, scroll_height;
+
+				if (lv->ClipRegion)
+				{
+					/* Calculate scrolling amount in pixels */
+					if (!(scroll_dy = lv->PixelTop - lv->OldPixelTop))
+						/* Do nothing if called improperly */
+						break;
+
+					/* Scroll everything */
+					scroll_height = lv->GBox.Height;
+				}
+				else
+				{
+					if (!(lv->Top - lv->OldTop))
+						/* Do nothing if called improperly */
+						break;
+
+					/* Calculate scrolling amount in pixels */
+					scroll_dy = (lv->Top - lv->OldTop) * (lv->ItemHeight + lv->Spacing);
+
+					/* Only scroll upto last visible item */
+					scroll_height = lv->Visible * (lv->ItemHeight + lv->Spacing) - lv->Spacing;
+				}
+
+				if (abs(scroll_dy) > lv->MaxScroll)
+				{
+					/* Redraw everything when listview has been scrolled too much */
+					RedrawItems (lv, msg, lv->Top,
+						min (lv->Top + lv->Visible, lv->Total) - 1, lv->TopPtr);
+				}
+				else
+				{
+#ifndef OS30_ONLY
+					if (GfxBase->LibNode.lib_Version >= 39)
+#endif /* OS30_ONLY */
+						/* Optimize scrolling on planar displays if possible */
+						SetMaxPen (rp, lv->MaxPen);
+
+					/* We use ClipBlit() to scroll the listview because it doesn't clear
+					 * the scrolled region like ScrollRaster() would do.  Unfortunately,
+					 * ClipBlit() does not scroll along the damage regions, so we also
+					 * call ScrollRaster() with the mask set to 0, which will scroll the
+					 * layer damage regions without actually modifying the display.
+					 */
+
+					if (scroll_dy > 0)	/* Scroll Down */
+					{
+						ClipBlit (rp, lv->GBox.Left, lv->GBox.Top + scroll_dy,
+							rp, lv->GBox.Left, lv->GBox.Top,
+							lv->GBox.Width, scroll_height - scroll_dy,
+							0x0C0);
+
+							if (lv->ClipRegion)
+							{
+								/* NOTE: We subtract 1 pixel to avoid an exact division which would
+								 *       render one item beyond the end when the slider is dragged
+								 *       all the way down.
+								 */
+								RedrawItems (lv, msg,
+									(lv->OldPixelTop + lv->GBox.Height) / (lv->ItemHeight + lv->Spacing),
+									(lv->PixelTop + lv->GBox.Height - 1) / (lv->ItemHeight + lv->Spacing),
+									NULL);
+							}
+							else
+								RedrawItems (lv, msg,
+									lv->Visible + lv->OldTop,
+									lv->Visible + lv->Top - 1,
+									NULL);
+					}
+					else				/* Scroll Up */
+					{
+						ClipBlit (rp, lv->GBox.Left, lv->GBox.Top,
+							rp, lv->GBox.Left, lv->GBox.Top - scroll_dy,
+							lv->GBox.Width, scroll_height + scroll_dy,
+							0x0C0);
+
+
+							if (lv->ClipRegion)
+								RedrawItems (lv, msg,
+									lv->PixelTop / (lv->ItemHeight + lv->Spacing),
+									lv->OldPixelTop / (lv->ItemHeight + lv->Spacing),
+									NULL);
+							else
+								RedrawItems (lv, msg,
+									lv->Top,
+									lv->OldTop - 1,
+									lv->TopPtr);
+					}
+
+
+					/* Some layers magic adapded from "MUI.undoc",
+					 * by Alessandro Zummo <azummo@ita.flashnet.it>
+					 */
+					#define LayerCovered(l) \
+						((!(l)->ClipRect) || memcmp (&(l)->ClipRect->bounds, \
+						&(l)->bounds, sizeof (struct Rectangle)))
+					#define LayerDamaged(l) \
+						((l)->DamageList && (l)->DamageList->RegionRectangle)
+					#define NeedZeroScrollRaster(l) (LayerCovered(l) || LayerDamaged(l))
+
+
+					/* This will scroll the layer damage regions without actually
+					 * scrolling the display, but only if our layer really needs it.
+					 */
+					if ((rp->Layer->Flags & LAYERSIMPLE) && NeedZeroScrollRaster (rp->Layer))
+					{
+						UBYTE oldmask = rp->Mask; /* Would GetRPAttr() be better? */
+
+						DB (kprintf ("  Calling ScrollRaster()\n");)
+#ifdef OS30_ONLY
+						SetWriteMask (rp, 0);
+#else
+						SafeSetWriteMask (rp, 0);
+#endif	/* OS30_ONLY */
+						ScrollRaster (rp, 0, scroll_dy,
+							lv->GRect.MinX, lv->GRect.MinY,
+							lv->GRect.MaxX,
+							lv->GRect.MaxY);
+
+#ifdef OS30_ONLY
+						SetWriteMask (rp, oldmask);
+#else
+						SafeSetWriteMask (rp, oldmask);
+#endif	/* OS30_ONLY */
+					}
+
+#ifndef OS30_ONLY
+					if (GfxBase->LibNode.lib_Version >= 39)
+#endif /* OS30_ONLY */
+						/* Restore MaxPen in our RastPort */
+						SetMaxPen (rp, -1);
+				}
+
+				/* Update OldTop to the current Top item and
+				 * OldPixelTop to the current PixelTop position.
+				 */
+				lv->OldTop = lv->Top;
+				lv->OldPixelTop = lv->PixelTop;
+
+				break;
+			}
+
+			default:
+				break;
+		}
+
+		if (lv->ClipRegion)
+		{
+			ASSERT_VALIDNO0(oldregion)
+			/* Restore old clipping region in our layer */
+			InstallClipRegion (rp->Layer, oldregion);
+		}
+
+		if (oldfont)
+			SetFont (rp, oldfont);
+	}
+	else if (msg->gpr_Redraw == GREDRAW_REDRAW)
+	{
+		/* Clear all gadget contents */
+		SetAPen (rp, msg->gpr_GInfo->gi_DrInfo->dri_Pens[BACKGROUNDPEN]);
+		RectFill (rp, lv->GRect.MinX, lv->GRect.MinY, lv->GRect.MaxX, lv->GRect.MaxY);
+	}
+}
+
+
+
+static ULONG LV_GMGoActive (Class *cl, struct Gadget *g, struct gpInput *msg)
+{
+	struct LVData		*lv = INST_DATA (cl, g);
+
+	ASSERT_VALIDNO0(lv)
+	DB (kprintf ("GM_GOACTIVE: gpi_IEvent = $%lx\n", msg->gpi_IEvent);)
+
+
+	if (!lv->Items)
+		return GMR_NOREUSE;
+
+	g->Flags |= GFLG_SELECTED;
+
+	/* Do not process InputEvent when the gadget has been
+	 * activated by ActivateGadget().
+	 */
+	if (!msg->gpi_IEvent)
+		return GMR_MEACTIVE;
+
+	/* Note: The input event that triggered the gadget
+	 * activation (usually a mouse click) should be passed
+	 * to the GM_HANDLEINPUT method, so we fall down to it.
+	 */
+	return LV_GMHandleInput (cl, g, msg);
+}
+
+
+
+INLINE LONG ItemHit (struct LVData *lv, WORD x, WORD y)
+
+/* Determine which item has been hit with gadget relative
+ * coordinates x and y.
+ */
+{
+	return ((y + lv->PixelTop) / (lv->ItemHeight + lv->Spacing));
+}
+
+
+
+static ULONG LV_GMHandleInput (Class *cl, struct Gadget *g, struct gpInput *msg)
+{
+	struct LVData		*lv = INST_DATA (cl, g);
+	struct InputEvent	*ie = msg->gpi_IEvent;
+	ULONG				 result = GMR_MEACTIVE;
+
+	ASSERT_VALIDNO0(lv)
+/*	DB (kprintf ("GM_HANDLEINPUT: ie_Class = $%lx, ie->ie_Code = $%lx, "
+		"gpi_Mouse.X = %ld, gpi_Mouse.Y = %ld\n",
+		ie->ie_Class, ie->ie_Code, msg->gpi_Mouse.X, msg->gpi_Mouse.Y);)
+*/
+	switch (ie->ie_Class)
+	{
+		case IECLASS_RAWKEY:
+		{
+			LONG tags[5];
+			LONG pos;
+
+			switch (ie->ie_Code)
+			{
+				case CURSORUP:
+					if ((lv->Flags & LVF_READONLY) || (ie->ie_Qualifier & IEQUALIFIER_CONTROL))
+					{
+						if (ie->ie_Qualifier & (IEQUALIFIER_LSHIFT | IEQUALIFIER_RSHIFT))
+							pos = lv->Top - lv->Visible / 2;
+						else
+							pos = lv->Top - 1;
+
+						if (pos < 0) pos = 0;
+
+						tags[0] = LVA_Top;
+						tags[1] = pos;
+						tags[2] = TAG_DONE;
+					}
+					else
+					{
+						if (ie->ie_Qualifier & (IEQUALIFIER_LALT | IEQUALIFIER_RALT))
+							pos = 0;
+						else if (ie->ie_Qualifier & (IEQUALIFIER_LSHIFT | IEQUALIFIER_RSHIFT))
+							pos = lv->Selected - lv->Visible + 1;
+						else
+							pos = lv->Selected - 1;
+
+						if (pos < 0) pos = 0;
+
+						tags[0] = LVA_Selected;
+						tags[1] = pos;
+						tags[2] = LVA_MakeVisible;
+						tags[3] = pos;
+						tags[4] = TAG_DONE;
+					}
+					break;
+
+				case CURSORDOWN:
+					if ((lv->Flags & LVF_READONLY) || (ie->ie_Qualifier & IEQUALIFIER_CONTROL))
+					{
+						if (ie->ie_Qualifier & (IEQUALIFIER_LSHIFT | IEQUALIFIER_RSHIFT))
+							pos = lv->Top + lv->Visible / 2;
+						else
+							pos = lv->Top + 1;
+
+						tags[0] = LVA_Top;
+						tags[1] = pos;
+						tags[2] = TAG_DONE;
+					}
+					else
+					{
+						if (ie->ie_Qualifier & (IEQUALIFIER_LALT | IEQUALIFIER_RALT))
+							pos = lv->Total - 1;
+						else if (ie->ie_Qualifier & (IEQUALIFIER_LSHIFT | IEQUALIFIER_RSHIFT))
+							pos = lv->Selected + lv->Visible - 1;
+						else
+							pos = lv->Selected + 1;
+
+						tags[0] = LVA_Selected;
+						tags[1] = pos;
+						tags[2] = LVA_MakeVisible;
+						tags[3] = pos;
+						tags[4] = TAG_DONE;
+					}
+					break;
+
+				default:
+					tags[0] = TAG_DONE;
+
+			} /* End switch (ie->ie_Code) */
+
+			if (tags[0] != TAG_DONE)
+				DoMethod ((Object *)g, OM_UPDATE, tags, msg->gpi_GInfo,
+					(ie->ie_Qualifier & IEQUALIFIERB_REPEAT) ? OPUF_INTERIM : 0);
+
+			break;
+		}
+
+		case IECLASS_RAWMOUSE:
+		{
+			LONG selected;
+
+			switch (ie->ie_Code)
+			{
+				case SELECTDOWN:
+
+					/* Check for click outside gadget box */
+
+					if ((msg->gpi_Mouse.X < 0) ||
+						(msg->gpi_Mouse.X >= lv->GBox.Width) ||
+						(msg->gpi_Mouse.Y < 0) ||
+						(msg->gpi_Mouse.Y >= lv->GBox.Height))
+					{
+						result = GMR_REUSE;
+						break;
+					}
+
+					/* Start dragging mode */
+					lv->Flags |= LVF_DRAGGING;
+
+					if (lv->Flags & LVF_READONLY)
+						break;
+
+					/* Select an item */
+					selected = ItemHit (lv, msg->gpi_Mouse.X, msg->gpi_Mouse.Y);
+
+					/* No action when selecting over blank space in the bottom */
+					if ((selected < 0) || (selected >= lv->Total))
+						break;
+
+					/* Backup current selection for RMB undo */
+					lv->BackupSelected = lv->Selected;
+					lv->BackupPixelTop = lv->PixelTop;
+
+					if (selected == lv->Selected)
+					{
+						/* Check for double click */
+						if (DoubleClick (lv->DoubleClickSecs, lv->DoubleClickMicros,
+							ie->ie_TimeStamp.tv_secs, ie->ie_TimeStamp.tv_micro))
+							UpdateAttrs ((Object *)g, msg->gpi_GInfo, 0,
+								LVA_DoubleClick, selected,
+								TAG_DONE);
+					}
+
+					if (lv->Flags & LVF_DOMULTISELECT)
+						/* Setup for multiple items drag selection */
+						lv->DragSelect = IsItemSelected (lv, NULL, selected) ?
+							LVA_DeselectItem : LVA_SelectItem;
+					else if (g->Activation & GACT_TOGGLESELECT)
+					{
+						/* Setup for single item toggle */
+						lv->DragSelect = LVA_Selected;
+						if (selected == lv->Selected)
+							selected = ~0;
+					}
+					else /* Single selection */
+						/* Setup for single item drag selection */
+						lv->DragSelect = LVA_Selected;
+
+					UpdateAttrs ((Object *)g, msg->gpi_GInfo, 0,
+						lv->DragSelect, selected,
+						TAG_DONE);
+
+					/* Save double click info */
+					lv->DoubleClickSecs = ie->ie_TimeStamp.tv_secs;
+					lv->DoubleClickMicros = ie->ie_TimeStamp.tv_micro;
+					break;
+
+				case MENUDOWN:
+					/* Undo selection & position when RMB is pressed */
+					if (lv->Flags & (LVF_DRAGGING | LVF_SCROLLING))
+					{
+						/* Stop dragging and scrolling modes */
+						lv->Flags &= ~(LVF_DRAGGING | LVF_SCROLLING);
+
+						if ((lv->BackupSelected != lv->Selected) ||
+							(lv->BackupPixelTop != lv->PixelTop))
+						{
+							UpdateAttrs ((Object *)g, msg->gpi_GInfo, 0,
+								(lv->Flags & LVF_READONLY) ?
+									TAG_IGNORE : LVA_Selected, lv->BackupSelected,
+								LVA_PixelTop, lv->BackupPixelTop,
+								TAG_DONE);
+						}
+					}
+					else
+						/* Deactivate gadget on menu button press */
+						result = GMR_REUSE;
+
+					break;
+
+				case MIDDLEDOWN:
+					/* Argh, input.device never sends this event in V40! */
+					DB (kprintf ("scrolling on\n");)
+
+					/* Start MMB scrolling */
+					lv->BackupPixelTop = lv->PixelTop;
+					lv->BackupSelected = lv->Selected;
+					lv->MiddleMouseY = msg->gpi_Mouse.Y;
+					lv->Flags |= LVF_DRAGGING;
+					break;
+
+				case SELECTUP:
+
+					/* Stop dragging mode */
+					lv->Flags &= ~LVF_DRAGGING;
+
+					if (g->Activation & GACT_RELVERIFY)
+					{
+						/* Send IDCMP_GADGETUP message to our parent window */
+						msg->gpi_Termination = &lv->Selected;
+						result = GMR_NOREUSE | GMR_VERIFY;
+					}
+					break;
+
+				case MIDDLEUP:
+					/* Argh, input.device never sends this event in V40! */
+					DB (kprintf ("scrolling off\n");)
+
+					/* Stop MMB scrolling */
+					lv->Flags &= ~LVF_SCROLLING;
+					break;
+
+				default: /* Mouse moved */
+
+					/* Holding LMB? */
+					if (lv->Flags & LVF_DRAGGING)
+					{
+						/* Select an item */
+						selected = ItemHit (lv, msg->gpi_Mouse.X, msg->gpi_Mouse.Y);
+
+						/* Moved over another item inside the currently displayed list? */
+						if ((selected != lv->Selected) && !(lv->Flags & LVF_READONLY)
+							&& (selected >= lv->Top) && (selected < lv->Top + lv->Visible))
+						{
+							/* Single selection */
+
+							/* Call our OM_UPDATE method to change the attributes.
+							 * This will also send notification to targets and
+							 * update the contents of the gadget.
+							 */
+							UpdateAttrs ((Object *)g, msg->gpi_GInfo, 0,
+								lv->DragSelect, selected,
+								TAG_DONE);
+						}
+					}
+
+					/* Holding MMB? */
+					if (lv->Flags & LVF_SCROLLING)
+					{
+						DB (kprintf ("  scrolling\n");)
+						selected = (msg->gpi_Mouse.Y - lv->MiddleMouseY)
+							+ lv->BackupPixelTop;
+
+						UpdateAttrs ((Object *)g, msg->gpi_GInfo, 0,
+							LVA_PixelTop, selected < 0 ? 0 : selected,
+							TAG_DONE);
+					}
+
+			} /* End switch (ie->ie_Code) */
+
+			break;
+		}
+
+		case IECLASS_TIMER:
+
+			/* Holding LMB? */
+			if (lv->Flags & LVF_DRAGGING)
+			{
+				/* Mouse above the upper item? */
+				if ((msg->gpi_Mouse.Y < 0) && lv->Top)
+				{
+					/* Scroll up */
+					UpdateAttrs ((Object *)g, msg->gpi_GInfo, 0,
+						LVA_MoveUp,	1,
+						(lv->Flags & LVF_READONLY) ? TAG_IGNORE : LVA_Selected, lv->Top - 1,
+						TAG_DONE);
+				}
+				/* Mouse below the bottom item? */
+				else if (msg->gpi_Mouse.Y / (lv->ItemHeight + lv->Spacing) >= lv->Visible)
+				{
+					/* Scroll down */
+					UpdateAttrs ((Object *)g, msg->gpi_GInfo, 0,
+						LVA_MoveDown,		1,
+						(lv->Flags & LVF_READONLY) ? TAG_IGNORE : LVA_Selected, lv->Top + lv->Visible,
+						TAG_DONE);
+				}
+			}
+			break;
+
+		default:
+			;
+
+	} /* End switch (ie->ie_Class) */
+
+	return result;
+}
+
+
+
+static void LV_GMGoInactive (Class *cl, struct Gadget *g, struct gpGoInactive *msg)
+{
+	struct LVData		*lv = INST_DATA (cl, g);
+	ASSERT_VALIDNO0(lv)
+
+	DB (kprintf ("GM_GOINACTIVE\n");)
+
+	/* Stop dragging and scrolling modes */
+	lv->Flags &= ~(LVF_DRAGGING | LVF_SCROLLING);
+
+	/* Mark gadget inactive */
+	g->Flags &= ~GFLG_SELECTED;
+}
+
+
+
+INLINE void GetGadgetBox (struct GadgetInfo *ginfo, struct ExtGadget *g, struct IBox *box, struct Rectangle *rect)
+
+/* Gets the actual IBox where a gadget exists in a window.
+ * The special cases it handles are all the REL#? (relative positioning flags).
+ *
+ * This function returns the gadget size in both the provided IBox and
+ * Rectangle structures, computing the values from the coordinates of the
+ * gadget and the window where it lives.
+ */
+{
+	ASSERT_VALIDNO0(g)
+	ASSERT_VALIDNO0(ginfo)
+	ASSERT_VALIDNO0(box)
+	ASSERT_VALIDNO0(rect)
+
+	DB (if ((g->Flags & GFLG_EXTENDED) && (g->MoreFlags & GMORE_BOUNDS))
+		kprintf ("  Gadget has valid bounds\n");)
+
+	box->Left = g->LeftEdge;
+	if (g->Flags & GFLG_RELRIGHT)
+		box->Left += ginfo->gi_Domain.Width - 1;
+
+	box->Top = g->TopEdge;
+	if (g->Flags & GFLG_RELBOTTOM)
+		box->Top += ginfo->gi_Domain.Height - 1;
+
+	box->Width = g->Width;
+	if (g->Flags & GFLG_RELWIDTH)
+		box->Width += ginfo->gi_Domain.Width;
+
+	box->Height = g->Height;
+	if (g->Flags & GFLG_RELHEIGHT)
+		box->Height += ginfo->gi_Domain.Height;
+
+	/* Convert IBox to Rectangle coordinates system */
+	rect->MinX = box->Left;
+	rect->MinY = box->Top;
+	rect->MaxX = box->Left + box->Width - 1;
+	rect->MaxY = box->Top + box->Height - 1;
+}
+
+
+
+static void LV_GMLayout (Class *cl, struct Gadget *g, struct gpLayout *msg)
+{
+	struct LVData *lv = INST_DATA (cl, g);
+	LONG visible;
+
+	DB (kprintf ("GM_LAYOUT\n");)
+	ASSERT_VALIDNO0(lv)
+	ASSERT_VALIDNO0(msg->gpl_GInfo)
+	ASSERT_VALIDNO0(msg->gpl_GInfo->gi_DrInfo)
+	ASSERT_VALIDNO0(msg->gpl_GInfo->gi_DrInfo->dri_Font)
+
+
+	/* We shouldn't draw inside the GM_LAYOUT method: the
+	 * GM_REDRAW method will be called by Intuition shortly after.
+	 */
+	lv->Flags |= LVF_DONTDRAW;
+
+	/* Collect new gadget size */
+	GetGadgetBox (msg->gpl_GInfo, (struct ExtGadget *)g, &lv->GBox, &lv->GRect);
+
+	/* Calculate clipping region for gadget LVA_Clipped mode */
+	if (lv->ClipRegion)
+	{
+		/* Remove previous clipping rectangle, if any */
+		ClearRegion (lv->ClipRegion);
+
+		/* Install a clipping rectangle around the gadget box.
+		 * We don't check for failure because we couldn't do
+		 * anything otherwise.
+		 */
+		OrRectRegion (lv->ClipRegion, &lv->GRect);
+	}
+
+	/* Setup Font if not yet done */
+	if (!lv->Font)
+	{
+		lv->Font = msg->gpl_GInfo->gi_DrInfo->dri_Font;
+		if (!lv->ItemHeight)
+			lv->ItemHeight = lv->Font->tf_YSize;
+	}
+
+	if (lv->ItemHeight)
+	{
+		if (lv->ClipRegion)
+			/* Allow displaying an incomplete item at the bottom of the listview,
+			 * plus one incomplete item at the top.
+			 */
+			visible = (lv->GBox.Height + lv->ItemHeight + lv->Spacing - 1) /
+				(lv->ItemHeight + lv->Spacing);
+		else
+			/* get maximum number of items fitting in the listview height.
+			 * Ignore spacing for the last visible item.
+			 */
+			visible = (lv->GBox.Height + lv->Spacing) / (lv->ItemHeight + lv->Spacing);
+	}
+	else
+		visible = 0;
+
+	lv->MaxScroll = lv->GBox.Height / lv->ScrollRatio;
+
+
+	/* Send initial notification to our sliders, or update them to
+	 * the new values. The slieders will get the correct size also
+	 * in the special case where the list is attached at creation
+	 * time and the sliders are attached later using a model object.
+	 *
+	 * The private class attribute LVA_Visible will handle everything for us.
+	 */
+	UpdateAttrs ((Object *)g, msg->gpl_GInfo, 0,
+		LVA_Visible,	visible,
+		TAG_DONE);
+
+	/* Re-enable drawing */
+	lv->Flags &= ~LVF_DONTDRAW;
+}
+
+
+
+static ULONG LV_OMSet (Class *cl, struct Gadget *g, struct opUpdate *msg)
+{
+	struct LVData	*lv = INST_DATA (cl, g);
+	struct TagItem	*ti,
+					*tstate	= msg->opu_AttrList;
+	ULONG	result;
+	UWORD	action = 0;	/* See flag definitions above */
+
+	ASSERT_VALIDNO0(lv)
+	ASSERT_VALID(tstate)
+
+	DB (kprintf ((msg->MethodID == OM_SET) ? "OM_SET:\n" : "OM_UPDATE:\n");)
+
+
+	/* Definitions for the ations to be taken right after
+	 * scanning the attributes list in OM_SET/OM_UPDATE.
+	 * For speed reasons we pack them together in a single variable,
+	 * so we can set and test multiple flags in once.
+	 */
+	#define LVF_DO_SUPER_METHOD	(1<<0)
+	#define LVF_REDRAW			(1<<1)
+	#define LVF_SCROLL			(1<<2)
+	#define LVF_TOGGLESELECT	(1<<3)
+	#define LVF_NOTIFY			(1<<4)
+	#define LVF_NOTIFYALL		(1<<5)
+
+
+	while (ti = NextTagItem (&tstate))
+		switch (ti->ti_Tag)
+		{
+			case GA_ID:
+				DB (kprintf ("  GA_ID, %ld\n", ti->ti_Data);)
+
+				/* Avoid sending all taglists to our superclass because of GA_ID */
+				g->GadgetID = ti->ti_Data;
+				break;
+
+			case LVA_Selected:
+				DB (kprintf ("  LVA_Selected, %ld\n", ti->ti_Data);)
+
+				if (lv->Items)
+				{
+					LONG newselected = ti->ti_Data;
+
+					if (newselected != ~0)
+						newselected = (newselected >= lv->Total) ?
+							(lv->Total - 1) : newselected;
+
+					if (lv->Selected != newselected)
+					{
+						if (((lv->Selected >= lv->Top) &&
+							(lv->Selected < lv->Top + lv->Visible)) ||
+							((newselected >= lv->Top) &&
+							(newselected < lv->Top + lv->Visible)))
+							action |= LVF_TOGGLESELECT;
+
+						lv->Selected = newselected;
+
+						if (newselected == ~0)
+							lv->SelectedPtr = NULL;
+						else
+							lv->SelectedPtr = GetItem (lv, newselected);
+
+						action |= LVF_NOTIFY;
+					}
+				}
+				break;
+
+			case LVA_Top:
+				DB (kprintf ("  LVA_Top, %ld\n", ti->ti_Data);)
+
+				if ((lv->Top != ti->ti_Data) && lv->Items)
+				{
+					/* This will scroll the listview contents when needed */
+
+					lv->Top = ((ti->ti_Data + lv->Visible) >= lv->Total) ?
+						((lv->Total <= lv->Visible) ? 0 : (lv->Total - lv->Visible))
+						: ti->ti_Data;
+					lv->PixelTop = lv->Top * (lv->ItemHeight + lv->Spacing);
+
+					/* TODO: optimize for some special cases:
+					 * Top == oldtop + 1 and Top == oldtop - 1
+					 */
+					lv->TopPtr = GetItem (lv, lv->Top);
+					action |= LVF_SCROLL | LVF_NOTIFY;
+				}
+				break;
+
+			case LVA_Total:
+				DB (kprintf ("  LVA_Total, %ld\n", ti->ti_Data);)
+
+				/* We don't hhandle LVA_Total except when setting a new
+				 * list or array of items.
+				 */
+				break;
+
+			case LVA_SelectItem:
+				DB (kprintf ("  LVA_SelectItem, %ld\n", ti->ti_Data);)
+
+				/* Check LVA_MaxSelect */
+				if (lv->SelectCount >= lv->MaxSelect)
+					DisplayBeep (msg->opu_GInfo ? msg->opu_GInfo->gi_Screen : NULL);
+				else if (lv->Items)
+				{
+					LONG newselected = (ti->ti_Data >= lv->Total) ?
+						(lv->Total - 1) : ti->ti_Data;
+
+					if (((lv->Selected >= lv->Top) &&
+						(lv->Selected < lv->Top + lv->Visible)) ||
+						((newselected >= lv->Top) &&
+						(newselected < lv->Top + lv->Visible)))
+						action |= LVF_TOGGLESELECT;
+
+					lv->Selected = newselected;
+					lv->SelectedPtr = GetItem (lv, newselected);
+
+					if (!IsItemSelected (lv, lv->SelectedPtr, newselected))
+					{
+						lv->SelectCount++;
+
+						if (lv->SelectArray)
+							lv->SelectArray[newselected] = lv->SelectCount;
+						else if (lv->Flags & LVF_LIST)
+							((struct Node *)lv->SelectedPtr)->ln_Type = lv->SelectCount;
+					}
+					action |= LVF_NOTIFY;
+				}
+				break;
+
+			case LVA_DeselectItem:
+				DB (kprintf ("  LVA_DeselectItem, %ld\n", ti->ti_Data);)
+
+				if (lv->Items)
+				{
+					LONG newselected = (ti->ti_Data >= lv->Total) ?
+						(lv->Total - 1) : ti->ti_Data;
+
+					if (((lv->Selected >= lv->Top) &&
+						(lv->Selected < lv->Top + lv->Visible)) ||
+						((newselected >= lv->Top) &&
+						(newselected < lv->Top + lv->Visible)))
+						action |= LVF_TOGGLESELECT;
+
+					lv->Selected = newselected;
+					lv->SelectedPtr = GetItem (lv, newselected);
+
+					if (IsItemSelected (lv, lv->SelectedPtr, newselected))
+					{
+						lv->SelectCount--;
+
+						if (lv->SelectArray)
+							lv->SelectArray[lv->Selected] = 0;
+						else if (lv->Flags & LVF_LIST)
+							((struct Node *)lv->SelectedPtr)->ln_Type = 0;
+
+						action |= LVF_NOTIFY;
+					}
+				}
+				break;
+
+			case LVA_ToggleItem:
+				DB (kprintf ("  LVA_ToggleItem, %ld\n", ti->ti_Data);)
+
+				if (lv->Items)
+				{
+					LONG newselected = newselected = (ti->ti_Data >= lv->Total) ?
+						(lv->Total - 1) : ti->ti_Data;
+
+					if (((lv->Selected >= lv->Top) &&
+						(lv->Selected < lv->Top + lv->Visible)) ||
+						((newselected >= lv->Top) &&
+						(newselected < lv->Top + lv->Visible)))
+						action |= LVF_TOGGLESELECT;
+
+					lv->Selected = newselected;
+					lv->SelectedPtr = GetItem (lv, newselected);
+
+					if (IsItemSelected (lv, lv->SelectedPtr, lv->Selected))
+					{
+						/* Deselect */
+						lv->SelectCount--;
+
+						if (lv->SelectArray)
+							lv->SelectArray[lv->Selected] = 0;
+						else if (lv->Flags & LVF_LIST)
+							((struct Node *)lv->SelectedPtr)->ln_Type = 0;
+					}
+					else
+					{
+						/* Check LVA_MaxSelect */
+						if (lv->SelectCount >= lv->MaxSelect)
+							DisplayBeep (msg->opu_GInfo ? msg->opu_GInfo->gi_Screen : NULL);
+						else
+						{
+							/* Select */
+							lv->SelectCount++;
+
+							if (lv->SelectArray)
+								lv->SelectArray[lv->Selected] = lv->SelectCount;
+							else if (lv->Flags & LVF_LIST)
+								((struct Node *)lv->SelectedPtr)->ln_Type = lv->SelectCount;
+						}
+					}
+
+					action |= LVF_NOTIFY;
+				}
+				break;
+
+			case LVA_ClearSelected:
+				DB (kprintf ("  LVA_ClearSelected, %ld\n", ti->ti_Data);)
+
+				if (lv->Items)
+				{
+					LONG newselected = ti->ti_Data;
+					LONG i;
+
+					if (((lv->Selected >= lv->Top) &&
+						(lv->Selected < lv->Top + lv->Visible)) ||
+						((newselected >= lv->Top) &&
+						(newselected < lv->Top + lv->Visible)))
+						action |= LVF_TOGGLESELECT;
+
+					lv->Selected = ~0;
+					lv->SelectedPtr = NULL;
+					lv->SelectCount = 0;
+
+
+					/* Clear the selections */
+
+					if (lv->SelectArray)
+						for (i = 0; i < lv->Total; i++)
+							lv->SelectArray[i] = 0;
+					else if (lv->Flags & LVF_LIST)
+					{
+						struct Node *node;
+
+						for (node = ((struct List *)lv->Items)->lh_Head;
+							node = node->ln_Succ;
+							node->ln_Type = 0)
+							ASSERT_VALID(node);
+					}
+
+					/* TODO: check if total redraw is really needed */
+					action |= LVF_REDRAW | LVF_NOTIFY;
+				}
+				break;
+
+			case LVA_MakeVisible:
+			{
+				LONG itemnum = ti->ti_Data;
+
+				DB (kprintf ("  LVA_MakeVisible, %ld\n", ti->ti_Data);)
+
+				if (itemnum < 0)
+					itemnum = 0;
+
+				if (itemnum >= lv->Total)
+					itemnum = lv->Total - 1;
+
+				if (itemnum < lv->Top)
+				{
+					/* Scroll up */
+
+					lv->Top = itemnum;
+					lv->TopPtr = GetItem (lv, lv->Top);
+					action |= LVF_SCROLL | LVF_NOTIFY;
+				}
+				else if (itemnum >= lv->Top + lv->Visible)
+				{
+					/* Scroll down */
+
+					lv->Top = itemnum - lv->Visible + 1;
+					lv->TopPtr = GetItem (lv, lv->Top);
+					action |= LVF_SCROLL | LVF_NOTIFY;
+				}
+				break;
+			}
+
+			case LVA_MoveUp:
+				DB (kprintf ("  LVA_MoveUp, %ld\n", ti->ti_Data);)
+
+				if ((lv->Top > 0) && lv->Items)
+				{
+					lv->Top--;
+					lv->TopPtr = GetPrev (lv, lv->TopPtr, lv->Top);
+					action |= LVF_SCROLL | LVF_NOTIFY;
+				}
+				break;
+
+			case LVA_MoveDown:
+				DB (kprintf ("  LVA_MoveDown, %ld\n", ti->ti_Data);)
+
+				if ((lv->Top + lv->Visible < lv->Total) && lv->Items)
+				{
+					lv->Top++;
+					lv->TopPtr = GetNext (lv, lv->TopPtr, lv->Top);
+					action |= LVF_SCROLL | LVF_NOTIFY;
+				}
+				break;
+
+			case LVA_MoveLeft:
+				DB (kprintf ("  Unimplemented attr: LVA_MoveLeft\n");)
+				break;
+
+			case LVA_MoveRight:
+				DB (kprintf ("  Unimplemented attr: LVA_MoveRight\n");)
+				break;
+
+			case LVA_StringList:
+				DB (kprintf ("  LVA_StringList, $%lx\n", ti->ti_Data);)
+
+				if (ti->ti_Data == ~0)
+					lv->Items = NULL;
+				else
+				{
+					ASSERT_VALID(ti->ti_Data)
+
+					lv->Items = (void *) ti->ti_Data;
+					lv->GetItemFunc		= ListGetItem;
+					lv->GetNextFunc		= ListGetNext;
+					lv->GetPrevFunc		= ListGetPrev;
+					lv->DrawItemFunc	= ListStringDrawItem;
+					lv->Flags |= LVF_LIST;
+
+					lv->Total = GetTagData (LVA_Total, ~0, msg->opu_AttrList);
+					if (lv->Total == ~0)
+						lv->Total = CountNodes (lv->Items);
+
+					lv->SelectCount = CountSelections (lv);
+
+					action |= LVF_REDRAW | LVF_NOTIFYALL;
+				}
+				break;
+
+			case LVA_StringArray:
+				DB (kprintf ("  LVA_StringArray, $%lx\n", ti->ti_Data);)
+
+				if (ti->ti_Data == ~0)
+					lv->Items = NULL;
+				else
+				{
+					ASSERT_VALID(ti->ti_Data)
+
+					lv->Items = (void *) ti->ti_Data;
+					lv->GetItemFunc		= ArrayGetItem;
+					lv->GetNextFunc		= ArrayGetItem;
+					lv->GetPrevFunc		= ArrayGetItem;
+					lv->DrawItemFunc	= StringDrawItem;
+					lv->Flags &= ~LVF_LIST;
+
+					lv->Total = GetTagData (LVA_Total, ~0, msg->opu_AttrList);
+					if ((lv->Total == ~0) && lv->Items)
+					{
+						/* Count items */
+						ULONG i = 0;
+						while (((APTR *)lv->Items)[i]) i++;
+						lv->Total = i;
+					}
+
+					lv->SelectCount = CountSelections(lv);
+
+					action |= LVF_REDRAW | LVF_NOTIFYALL;
+				}
+				break;
+
+			case LVA_ImageList:
+				DB (kprintf ("  LVA_ImageList, $%lx\n", ti->ti_Data);)
+
+				if (ti->ti_Data == ~0)
+					lv->Items = NULL;
+				else
+				{
+					ASSERT_VALID(ti->ti_Data)
+
+					lv->Items = (void *) ti->ti_Data;
+					lv->GetItemFunc		= ListGetItem;
+					lv->GetNextFunc		= ListGetNext;
+					lv->GetPrevFunc		= ListGetPrev;
+					lv->DrawItemFunc	= ListImageDrawItem;
+					lv->Flags |= LVF_LIST;
+
+					lv->Total = GetTagData (LVA_Total, ~0, msg->opu_AttrList);
+					if (lv->Total == ~0)
+						lv->Total = CountNodes (lv->Items);
+
+					lv->SelectCount = CountSelections(lv);
+
+					action |= LVF_REDRAW | LVF_NOTIFYALL;
+				}
+				break;
+
+			case LVA_ImageArray:
+				DB (kprintf ("  LVA_ImageArray, $%lx\n", ti->ti_Data);)
+
+				if (ti->ti_Data == ~0)
+					lv->Items = NULL;
+				else
+				{
+					ASSERT_VALID(ti->ti_Data)
+
+					lv->Items = (void *) ti->ti_Data;
+					lv->GetItemFunc		= ArrayGetItem;
+					lv->GetNextFunc		= ArrayGetItem;
+					lv->GetPrevFunc		= ArrayGetItem;
+					lv->DrawItemFunc	= ImageDrawItem;
+					lv->Flags &= ~LVF_LIST;
+
+					lv->Total = GetTagData (LVA_Total, ~0, msg->opu_AttrList);
+					if ((lv->Total == ~0) && lv->Items)
+					{
+						/* Count items */
+						ULONG i = 0;
+						while (((APTR *)lv->Items)[i]) i++;
+						lv->Total = i;
+					}
+
+					action |= LVF_REDRAW | LVF_NOTIFYALL;
+				}
+				break;
+
+			case LVA_CustomList:
+				DB (kprintf ("  LVA_CustomList, $%lx\n", ti->ti_Data);)
+
+				if (ti->ti_Data == ~0)
+					lv->Items = NULL;
+				else
+				{
+					ASSERT_VALID(ti->ti_Data)
+
+					lv->Items = (void *) ti->ti_Data;
+					lv->SelectCount = CountSelections (lv);
+
+					action |= LVF_REDRAW | LVF_NOTIFYALL;
+				}
+				break;
+
+			case LVA_Visible:
+				DB (kprintf ("  LVA_Visible, %ld\n", ti->ti_Data);)
+
+				/* This attribute can only be set internally, and will
+				 * trigger a full slider notification.
+				 */
+				lv->Visible = ti->ti_Data;
+				action |= LVF_NOTIFYALL;
+
+
+				/* Also scroll the ListView if needed. */
+				if (lv->ClipRegion)
+				{
+					LONG height = lv->Total * (lv->ItemHeight + lv->Spacing);
+					LONG newtop;
+
+					if (lv->PixelTop + lv->GBox.Height >= height)
+					{
+						lv->PixelTop = height - lv->GBox.Height;
+						if (lv->PixelTop < 0)
+							lv->PixelTop = 0;
+
+						newtop = lv->PixelTop / (lv->ItemHeight + lv->Spacing);
+						if (newtop != lv->Top)
+						{
+							lv->Top = newtop;
+							lv->TopPtr = GetItem (lv, newtop);
+						}
+						action |= LVF_SCROLL;
+					}
+				}
+				else if (lv->Top + lv->Visible >= lv->Total)
+				{
+					lv->Top = (lv->Total <= lv->Visible) ? 0 : (lv->Total - lv->Visible);
+					lv->TopPtr = GetItem (lv, lv->Top);
+					lv->PixelTop = lv->Top * (lv->ItemHeight + lv->Spacing);
+					action |= LVF_SCROLL;
+				}
+				break;
+
+			case LVA_SelectArray:
+				DB (kprintf ("  LVA_SelectArray, $%lx\n", ti->ti_Data);)
+				ASSERT_VALID(ti->ti_Data)
+
+				lv->SelectArray = (ULONG *) ti->ti_Data;
+				lv->SelectCount = CountSelections (lv);
+				action |= LVF_REDRAW;
+				break;
+
+			case LVA_MaxSelect:
+				DB (kprintf ("  LVA_MaxSelect, %ld\n", ti->ti_Data);)
+
+				lv->MaxSelect = ti->ti_Data;
+				/* NOTE: We are not checking lv->SelectCount */
+				break;
+
+			case LVA_PixelTop:	/* Handle pixel-wise scrolling */
+				DB (kprintf ("  LVA_PixelTop, %ld\n", ti->ti_Data);)
+
+				if (ti->ti_Data != lv->PixelTop && lv->Items && lv->ItemHeight)
+				{
+					LONG newtop;
+
+					lv->PixelTop = ti->ti_Data;
+					action |= LVF_SCROLL;
+
+					newtop = lv->PixelTop / (lv->ItemHeight + lv->Spacing);
+					newtop = ((newtop + lv->Visible) >= lv->Total) ?
+						((lv->Total <= lv->Visible) ? 0 : (lv->Total - lv->Visible))
+						: newtop;
+
+					if (newtop != lv->Top)
+					{
+						/* TODO: optimize GetItem for some special cases:
+						 * Top = oldtop + 1 and Top = oldtop - 1
+						 */
+						lv->Top = newtop;
+						lv->TopPtr = GetItem (lv, newtop);
+						action |= LVF_NOTIFY | LVF_SCROLL;
+					}
+				}
+				break;
+
+			case LVA_ScrollRatio:
+				DB (kprintf ("  LVA_ScrollRatio, %ld\n", ti->ti_Data);)
+				ASSERT(ti->ti_Data != 0)
+
+				lv->ScrollRatio = ti->ti_Data;
+				lv->MaxScroll = lv->GBox.Height / lv->ScrollRatio;
+				break;
+
+			default:
+				DB (kprintf ("  Passing unknown tag to superclass: $%lx, %ld\n",
+					ti->ti_Tag, ti->ti_Data);)
+
+				/* This little optimization avoids forwarding the
+				 * OM_SET method to our superclass then there are
+				 * no unknown tags.
+				 */
+				action |= LVF_DO_SUPER_METHOD;
+				break;
+		}
+
+	DB(kprintf ("  TAG_DONE\n");)
+
+	/* Forward method to our superclass dispatcher, only if needed */
+
+	if (action & LVF_DO_SUPER_METHOD)
+		result = (DoSuperMethodA (cl, (Object *)g, (Msg) msg));
+	else
+		result = TRUE;
+
+
+	/* Update gadget imagery, only when needed */
+
+	if ((action & (LVF_REDRAW | LVF_SCROLL | LVF_TOGGLESELECT))
+		&& msg->opu_GInfo && !(lv->Flags & LVF_DONTDRAW))
+	{
+		struct RastPort *rp;
+
+		if (rp = ObtainGIRPort (msg->opu_GInfo))
+		{
+			/* Just redraw everything */
+			if (action & LVF_REDRAW)
+				DoMethod ((Object *)g, GM_RENDER, msg->opu_GInfo, rp, GREDRAW_REDRAW);
+			else
+			{
+				/* Both these may happen at the same time */
+
+				if (action & LVF_SCROLL)
+					DoMethod ((Object *)g, GM_RENDER, msg->opu_GInfo, rp,
+						GREDRAW_UPDATE);
+
+				if (action & LVF_TOGGLESELECT)
+					DoMethod ((Object *)g, GM_RENDER, msg->opu_GInfo, rp,
+						GREDRAW_TOGGLE);
+			}
+
+			ReleaseGIRPort (rp);
+		}
+		DB(else kprintf ("*** ObtainGIRPort() failed!\n");)
+	}
+
+
+	/* Notify our targets about changed attributes */
+
+	if (action & LVF_NOTIFYALL)
+	{
+		DB(kprintf("OM_NOTIFY: ALL\n");)
+		DB(kprintf("  LVA_Top,           %ld\n", lv->Top);)
+		DB(kprintf("  LVA_Total,         %ld\n", lv->Total);)
+		DB(kprintf("  LVA_Visible,       %ld\n", lv->Visible);)
+		DB(kprintf("  LVA_Selected,      %ld\n", lv->Selected);)
+		DB(kprintf("  LVA_PixelTop,      %ld\n", lv->PixelTop);)
+		DB(kprintf("  LVA_PixelHeight,   %ld\n", lv->Total * (lv->ItemHeight + lv->Spacing));)
+		DB(kprintf("  LVA_PixelVVisible, %ld\n", lv->GBox.Height);)
+		DB(kprintf("  TAG_DONE\n");)
+
+		NotifyAttrs ((Object *)g, msg->opu_GInfo,
+			(msg->MethodID == OM_UPDATE) ? msg->opu_Flags : 0,
+			LVA_Top,			lv->Top,
+			LVA_Total,			lv->Total,
+			LVA_Visible,		lv->Visible,
+			LVA_Selected,		lv->Selected,
+			LVA_PixelTop,		lv->PixelTop,
+			LVA_PixelHeight,	lv->Total * (lv->ItemHeight + lv->Spacing),
+			LVA_PixelVVisible,	lv->ClipRegion ?
+									lv->GBox.Height :
+									lv->Visible * (lv->ItemHeight + lv->Spacing),
+			GA_ID,				g->GadgetID,
+			TAG_DONE);
+	}
+	else if (action & LVF_NOTIFY)
+	{
+		IPTR tags[9];
+		int cnt = 0;
+
+		if (action & LVF_SCROLL)
+		{
+			tags[0] = LVA_Top;			tags[1] = lv->Top;
+			tags[2] = LVA_PixelTop;		tags[3] = lv->Top * (lv->ItemHeight + lv->Spacing);
+			cnt = 4;
+		}
+
+		if (action & LVF_TOGGLESELECT)
+		{
+			tags[cnt++] = LVA_Selected;	tags[cnt++] = lv->Selected;
+		}
+
+		tags[cnt++]	= GA_ID;
+		tags[cnt++]	= g->GadgetID;
+		tags[cnt]	= TAG_DONE;
+
+		DoMethod ((Object *)g, OM_NOTIFY, tags, msg->opu_GInfo,
+			(msg->MethodID == OM_UPDATE) ? msg->opu_Flags : 0);
+	}
+
+	return result;
+}
+
+
+
+static ULONG LV_OMGet (Class *cl, struct Gadget *g, struct opGet *msg)
+{
+	struct LVData *lv = INST_DATA (cl, g);
+
+	ASSERT_VALIDNO0(lv)
+	ASSERT_VALIDNO0(msg->opg_Storage)
+
+	DB (kprintf ("OM_GET\n");)
+
+
+	switch (msg->opg_AttrID)
+	{
+		case LVA_Selected:
+			*msg->opg_Storage = (ULONG) lv->Selected;
+			return TRUE;
+
+		case LVA_Top:
+			*msg->opg_Storage = (ULONG) lv->Top;
+			return TRUE;
+
+		case LVA_Total:
+			*msg->opg_Storage = (ULONG) lv->Total;
+			return TRUE;
+
+		case LVA_StringList:
+		case LVA_StringArray:
+		case LVA_ImageList:
+		case LVA_ImageArray:
+		case LVA_CustomList:
+			*msg->opg_Storage = (ULONG) lv->Items;
+			return TRUE;
+
+		case LVA_Visible:
+			*msg->opg_Storage = (ULONG) lv->Visible;
+			return TRUE;
+
+		case LVA_SelectedPtr:
+			*msg->opg_Storage = (ULONG) lv->SelectedPtr;
+			return TRUE;
+
+		case LVA_SelectArray:
+			*msg->opg_Storage = (ULONG) lv->SelectArray;
+			return TRUE;
+
+		default:
+			return DoSuperMethodA (cl, (Object *)g, (Msg) msg);
+	}
+}
+
+
+
+static ULONG LV_OMNew (Class *cl, struct Gadget *g, struct opSet *msg)
+{
+	struct LVData	*lv;
+	struct TagItem	*tag;
+	struct DrawInfo	*drawinfo;
+
+
+	DB (kprintf ("OM_NEW\n");)
+
+	if (g = (struct Gadget *)DoSuperMethodA (cl, (Object *)g, (Msg)msg))
+	{
+		/* Set the GMORE_SCROLLRASTER flag */
+		if (g->Flags & GFLG_EXTENDED)
+		{
+			DB (kprintf ("  Setting GMORE_SCROLLRASTER\n");)
+			((struct ExtGadget *)g)->MoreFlags |= GMORE_SCROLLRASTER;
+		}
+
+		lv = (struct LVData *) INST_DATA (cl, (Object *)g);
+		ASSERT_VALIDNO0(lv)
+
+		/* Handle creation-time attributes */
+
+		/* Map boolean attributes */
+		{
+			static IPTR boolMap[] =
+			{
+				GA_ReadOnly,		LVF_READONLY,
+				LVA_Clipped,		LVF_CLIPPED,
+				LVA_ShowSelected,	LVF_SHOWSELECTED,
+				LVA_DoMultiSelect,	LVF_DOMULTISELECT,
+				TAG_DONE
+			};
+
+			lv->Flags = PackBoolTags (
+				LVF_SHOWSELECTED,
+				msg->ops_AttrList,
+				(struct TagItem *)boolMap);
+		}
+
+
+		/* Select font to use when drawing the Listview labels */
+
+		/* First, try to get the font from our DrawInfo... */
+
+		if (drawinfo = (struct DrawInfo *)
+			GetTagData (GA_DrawInfo, NULL, msg->ops_AttrList))
+		{
+			ASSERT_VALID(drawinfo)
+			lv->Font = drawinfo->dri_Font;
+		}
+		else
+			lv->Font = NULL;
+
+
+		/* ...then override it with LVA_TextFont */
+
+		if (tag = FindTagItem (LVA_TextFont, msg->ops_AttrList))
+		{
+			if (tag->ti_Data)
+			{
+				lv->Font = (struct TextFont *)tag->ti_Data;
+				ASSERT_VALID(lv->Font)
+			}
+		}
+		else	/* Otherwise, try GA_TextAttr */
+		{
+			struct TextAttr *attr;
+			struct TextFont *font;
+
+			if (attr = (struct TextAttr *)GetTagData (GA_TextAttr,
+				NULL, msg->ops_AttrList))
+			{
+				if (font = OpenFont (attr))
+				{
+					/* Must remember to close this font later */
+					lv->Flags |= LVF_CLOSEFONT;
+					lv->Font = font;
+				}
+			}
+		}
+
+		/* Calculate ItemHeight */
+
+		if (lv->Font)
+			/* Get height from font Y size */
+			lv->ItemHeight = lv->Font->tf_YSize;
+		else
+			lv->ItemHeight = 0;
+
+		lv->ItemHeight = GetTagData (LVA_ItemHeight, lv->ItemHeight, msg->ops_AttrList);
+		lv->Spacing = GetTagData (LAYOUTA_Spacing, 0, msg->ops_AttrList);
+
+		if (tag = FindTagItem (LVA_MaxPen, msg->ops_AttrList))
+			lv->MaxPen = tag->ti_Data;
+		else
+		{
+			if (drawinfo)
+				lv->MaxPen = max (
+					max (drawinfo->dri_Pens[BACKGROUNDPEN],
+						drawinfo->dri_Pens[TEXTPEN]),
+					max (drawinfo->dri_Pens[FILLPEN],
+						drawinfo->dri_Pens[FILLTEXTPEN]));
+			else
+				lv->MaxPen = (ULONG)-1;
+		}
+
+
+		lv->Total = GetTagData (LVA_Total, ~0, msg->ops_AttrList);
+
+		if (lv->Items = (APTR) GetTagData (LVA_StringList, NULL, msg->ops_AttrList))
+		{
+			ASSERT_VALID(lv->Items)
+			lv->GetItemFunc = ListGetItem;
+			lv->GetNextFunc = ListGetNext;
+			lv->GetPrevFunc = ListGetPrev;
+			lv->DrawItemFunc = ListStringDrawItem;
+			lv->Flags |= LVF_LIST;
+
+			if (lv->Total == ~0)
+				lv->Total = CountNodes (lv->Items);
+		}
+		else if (lv->Items = (APTR) GetTagData (LVA_StringArray, NULL, msg->ops_AttrList))
+		{
+			ASSERT_VALID(lv->Items)
+			lv->GetItemFunc = ArrayGetItem;
+			lv->GetNextFunc = ArrayGetItem;
+			lv->GetPrevFunc = ArrayGetItem;
+			lv->DrawItemFunc = StringDrawItem;
+
+			if (lv->Total == ~0)
+			{
+				/* Count items */
+				ULONG i = 0;
+				while (((APTR *)lv->Items)[i]) i++;
+				lv->Total = i;
+			}
+		}
+		else if (lv->Items = (APTR) GetTagData (LVA_ImageList, NULL, msg->ops_AttrList))
+		{
+			ASSERT_VALID(lv->Items)
+			lv->GetItemFunc = ListGetItem;
+			lv->GetNextFunc = ListGetNext;
+			lv->GetPrevFunc = ListGetPrev;
+			lv->DrawItemFunc = ListImageDrawItem;
+			lv->Flags |= LVF_LIST;
+
+			if (lv->Total == ~0)
+				lv->Total = CountNodes (lv->Items);
+		}
+		else if (lv->Items = (APTR) GetTagData (LVA_ImageArray, NULL, msg->ops_AttrList))
+		{
+			ASSERT_VALID(lv->Items)
+			lv->GetItemFunc = ArrayGetItem;
+			lv->GetNextFunc = ArrayGetItem;
+			lv->GetPrevFunc = ArrayGetItem;
+			lv->DrawItemFunc = ImageDrawItem;
+
+			if (lv->Total == ~0)
+			{
+				/* Count items */
+				ULONG i = 0;
+				while (((APTR *)lv->Items)[i]) i++;
+				lv->Total = i;
+			}
+		}
+
+		lv->SelectArray = (ULONG *)GetTagData (LVA_SelectArray, NULL, msg->ops_AttrList);
+		lv->MaxSelect = GetTagData (LVA_MaxSelect, -1, msg->ops_AttrList);
+		lv->SelectCount = CountSelections(lv);
+
+		if (lv->Visible = GetTagData (LVA_Visible, 0, msg->ops_AttrList))
+		{
+			SetAttrs (g,
+				GA_Height, lv->Visible * (lv->ItemHeight + lv->Spacing),
+				TAG_DONE);
+		}
+
+		/* Initialize Top and all related values */
+
+		lv->OldTop = lv->Top = GetTagData (LVA_MakeVisible,
+			GetTagData (LVA_Top, 0, msg->ops_AttrList), msg->ops_AttrList);
+		lv->OldPixelTop = lv->PixelTop = lv->Top * (lv->ItemHeight + lv->Spacing);
+
+		if (lv->Items)
+			lv->TopPtr = GetItem (lv, lv->Top);
+
+		lv->ScrollRatio = GetTagData (LVA_ScrollRatio, 2, msg->ops_AttrList);
+		ASSERT(lv->ScrollRatio != 0)
+
+		if ((lv->OldSelected =
+			lv->Selected = GetTagData (LVA_Selected, ~0, msg->ops_AttrList)) != ~0)
+			lv->SelectedPtr = GetItem (lv, lv->Selected);
+
+		if (lv->CallBack = (struct Hook *)GetTagData (LVA_CallBack, NULL,
+			msg->ops_AttrList))
+		{
+			ASSERT_VALID(lv->CallBack->h_Entry)
+			lv->DrawItemFunc = (LVDrawHook *) lv->CallBack->h_Entry;
+		}
+
+		if (lv->Flags & LVF_CLIPPED)
+			lv->ClipRegion = NewRegion ();
+	}
+	return (ULONG)g;
+}
+
+
+
+static void LV_OMDispose (Class *cl, struct Gadget *g, Msg msg)
+{
+	struct LVData	*lv;
+
+	lv = (struct LVData *) INST_DATA (cl, (Object *)g);
+
+	ASSERT_VALIDNO0(lv)
+	DB (kprintf ("OM_DISPOSE\n");)
+
+	if (lv->ClipRegion)
+		DisposeRegion (lv->ClipRegion);
+
+	if (lv->Flags & LVF_CLOSEFONT)
+		CloseFont (lv->Font);
+
+	/* Our superclass will cleanup everything else now */
+	DoSuperMethodA (cl, (Object *)g, (Msg) msg);
+
+	/* From now on, our instance data is no longer available */
+}
+
+
+
+/* Misc support functions */
+
+INLINE ULONG CountNodes (struct List *list)
+
+/* Return the number of nodes in a list */
+{
+	struct Node *node;
+	ULONG count = 0;
+
+	if (list)
+	{
+		ASSERT_VALID(list)
+
+		for (node = list->lh_Head; node = node->ln_Succ; count++)
+			ASSERT_VALID(node);
+	}
+
+	return count;
+}
+
+
+
+static ULONG CountSelections (struct LVData *lv)
+
+/* Count the number of selections in a multiselect listview */
+{
+	ULONG count = 0;
+
+	ASSERT_VALIDNO0(lv)
+
+
+	if (lv->Flags & LVF_DOMULTISELECT)
+	{
+		if (lv->SelectArray)
+		{
+			int i;
+
+			ASSERT_VALID(lv->SelectArray)
+
+			for (i = 0; i < lv->Total; i++)
+				if (lv->SelectArray[i])
+					count++;
+		}
+		else if ((lv->Flags & LVF_LIST) && lv->Items)
+		{
+			struct Node *node;
+
+			ASSERT_VALID(lv->Items)
+
+			for (node = ((struct List *)lv->Items)->lh_Head; node = node->ln_Succ; count++)
+				ASSERT_VALID(node);
+		}
+	}
+
+	return count;
+}
+
+
+
+INLINE ULONG IsItemSelected (struct LVData *lv, APTR item, ULONG num)
+
+/* Checks if the given item is selected */
+{
+	ASSERT_VALIDNO0(lv)
+	ASSERT_VALID(item)
+	ASSERT(num >= 0)
+	ASSERT(num < lv->Total)
+
+
+	if (lv->Flags & LVF_DOMULTISELECT)
+	{
+		if (lv->SelectArray)
+		{
+			ASSERT(num < lv->Total)
+
+			return lv->SelectArray[num];
+		}
+		else if (lv->Flags & LVF_LIST)
+		{
+			if (!item)
+				item = GetItem (lv, num);
+
+			ASSERT_VALIDNO0(item)
+
+			return item ? (ULONG)(((struct Node *)item)->ln_Type) : 0;
+		}
+
+		return 0;
+	}
+	else
+		return ((ULONG)(num == lv->Selected));
+}
+
+
+
+INLINE APTR GetItem (struct LVData *lv, ULONG num)
+
+/* Stub for LV_GETITEM hook method */
+{
+	struct lvGetItem lvgi;
+
+
+	ASSERT_VALIDNO0(lv)
+	ASSERT_VALIDNO0(lv->Items)
+	ASSERT_VALIDNO0(lv->GetItemFunc)
+	ASSERT(num >= 0)
+	ASSERT(num < lv->Total)
+
+
+	lvgi.lvgi_MethodID	= LV_GETITEM;
+	lvgi.lvgi_Number	= num;
+	lvgi.lvgi_Items		= lv->Items;
+
+	return (lv->GetItemFunc (lv->CallBack, NULL, &lvgi));
+}
+
+
+
+INLINE APTR GetNext (struct LVData *lv, APTR item, ULONG num)
+
+/* Stub for LV_GETNEXT hook method */
+{
+	struct lvGetItem lvgi;
+
+
+	ASSERT_VALIDNO0(lv)
+	ASSERT_VALIDNO0(lv->GetNextFunc)
+	ASSERT_VALID(item)
+	ASSERT(num >= 0)
+	ASSERT(num < lv->Total)
+
+
+	lvgi.lvgi_MethodID	= LV_GETNEXT;
+	lvgi.lvgi_Number	= num;
+	lvgi.lvgi_Items		= lv->Items;
+
+	return (lv->GetNextFunc (lv->CallBack, item, &lvgi));
+}
+
+
+
+INLINE APTR GetPrev (struct LVData *lv, APTR item, ULONG num)
+
+/* Stub for LV_GETPREV hook method */
+{
+	struct lvGetItem lvgi;
+
+
+	ASSERT_VALIDNO0(lv)
+	ASSERT_VALIDNO0(lv->GetPrevFunc)
+	ASSERT_VALID(item)
+	ASSERT(num >= 0)
+	ASSERT(num < lv->Total)
+
+
+	lvgi.lvgi_MethodID	= LV_GETPREV;
+	lvgi.lvgi_Number	= num;
+	lvgi.lvgi_Items		= lv->Items;
+
+	return (lv->GetPrevFunc (lv->CallBack, item, &lvgi));
+}
+
+
+
+Class *MakeListViewClass (void)
+{
+	Class *LVClass;
+
+	if (LVClass = MakeClass (NULL, GADGETCLASS, NULL, sizeof (struct LVData), 0))
+		LVClass->cl_Dispatcher.h_Entry = (ULONG (*)()) LVDispatcher;
+
+	return LVClass;
+}
+
+
+
+void FreeListViewClass (Class *LVClass)
+{
+	ASSERT_VALID(LVClass)
+	FreeClass (LVClass);
+}
diff --git a/ListViewClass.h b/ListViewClass.h
new file mode 100644
index 0000000..98f6b19
--- /dev/null
+++ b/ListViewClass.h
@@ -0,0 +1,429 @@
+#ifndef LISTVIEWCLASS_H
+#define LISTVIEWCLASS_H
+/*
+**	ListViewClass.h
+**
+**	Copyright (C) 1996,97 by Bernardo Innocenti
+**
+**	ListView class built on top of the "gadgetclass".
+**
+*/
+
+#define LISTVIEWCLASS	"listviewclass"
+#define LISTVIEWVERS	1
+
+
+Class	*MakeListViewClass (void);
+void	 FreeListViewClass (Class *LVClass);
+
+
+
+/*****************/
+/* Class Methods */
+/*****************/
+
+/* This class does not define any new methods */
+
+
+
+/********************/
+/* Class Attributes */
+/********************/
+
+#define LVA_Dummy			(TAG_USER | ('L'<<16) | ('V'<<8))
+
+#define LVA_Selected		(LVA_Dummy+1)
+	/* (IGSNU) LONG - Selected item number. ~0 (-1) indicates that no
+	 * item is selected.
+	 */
+
+#define LVA_Top				(LVA_Dummy+2)
+	/* (IGSNU) LONG - Number of top displayed item. Default is 0.
+	 */
+
+#define LVA_Total			(LVA_Dummy+3)
+	/* (IGSN) LONG - Total number of items in list.
+	 * This attribute can be set when LVA_StringArray, LVA_ImageArray
+	 * or LVA_CustomList is used. If you pass -1 or omit this tag,
+	 * the ListView class will count the items in the array until it
+	 * finds a NULL entry. If you know the number of nodes in your list,
+	 * you will save some internal overhead by telling it to the
+	 * ListView with the LVA_Total tag, expecially when using the
+	 * LVA_StringList and LVA_ImageList modes. You must set LVA_Total
+	 * each time you provide a new list or array, and in the same
+	 * OM_SET call.
+	 * Be careful: no checks are made on the values you are passing!
+	 */
+
+#define LVA_SelectItem		(LVA_Dummy+4)
+	/* (SN) LONG - Add item specified by ti_Data to the
+	 * selection list.
+	 */
+
+#define LVA_DeselectItem	(LVA_Dummy+5)
+	/* (SN) LONG - Remove item specified by ti_Data from the
+	 * selection list.
+	 */
+
+#define LVA_ToggleItem		(LVA_Dummy+6)
+	/* (SN) LONG - Toggle item selection.
+	 */
+
+#define LVA_ClearSelected	(LVA_Dummy+7)
+	/* (SN) LONG - Remove the selected state to AALL items.
+	 */
+
+
+#define LVA_MakeVisible		(LVA_Dummy+8)
+	/* (ISU) Make this item visible by doing the minimum required scrolling.
+	 */
+
+#define LVA_MoveUp			(LVA_Dummy+9)
+#define LVA_MoveDown		(LVA_Dummy+10)
+#define LVA_MoveLeft		(LVA_Dummy+11)
+#define LVA_MoveRight		(LVA_Dummy+12)
+	/* (SU) Scroll the display up/down. left/right movement is not
+	 * yet supported.
+	 */
+
+#define LVA_StringList		(LVA_Dummy+13)
+#define LVA_StringArray		(LVA_Dummy+14)
+#define LVA_ImageList		(LVA_Dummy+15)
+#define LVA_ImageArray		(LVA_Dummy+16)
+#define LVA_CustomList		(LVA_Dummy+17)
+#define LVA_Labels			LVA_StringList
+	/* (ISG) List of items to display. All structures and strings
+	 * are referenced, not copied, so they must stay in memory
+	 * while the ListView gadget displays them.
+	 *
+	 * LVA_StringList (AKA LVA_Labels) passes a pointer to a
+	 * List (or MinList) of Node structures. The strings
+	 * pointed by ln_Name will be displayed as item labels.
+	 *
+	 * LVA_StringArray specifies a pointer to an array of STRPTR which
+	 * will be used as item labels.  The array must be NULL terminated
+	 * unless the LVA_Count is set.
+	 *
+	 * LVA_ImageList passes a pointer to a List (or MinList)
+	 * of Node structures. The ln_Name field of each Node structure
+	 * must point to a normal Image structure or to an instance of
+	 * the imageclass (or a subclass of it).
+	 *
+	 * LVA_ImageArray specifies a pointer to an array of pointers to
+	 * Image structures or imageclass objects.  The array must be NULL
+	 * terminated unless the LVA_Count attribute is used.
+	 *
+	 * LVA_CustomList can be used to provide a data structure which
+	 * is neither a list nor an array.  Custom user functions will
+	 * be called to retrieve the items. The data passed with this
+	 * tag will be passed to the user functions.
+	 *
+	 * Setting one of these attributes to NULL causes the contents
+	 * of the ListView gadget to be cleared.
+	 *
+	 * Setting one of these attributes to ~0 (-1) detaches the
+	 * items from the ListView.  You must detach your list before
+	 * adding or removing items.  This isn't required when using
+	 * array oriented item lists.
+	 */
+
+#define LVA_Visible			(LVA_Dummy+18)
+	/* (IGN) ULONG - Number of visible items. When this attribute is
+	 * passed on intialization, the ListView gadget will resize
+	 * itself to make the desired number of lines visible. this
+	 * feature is incompatible with GA_RelHeight. LVA_Visible at
+	 * creation time requires a valid DrawInfo passed with the
+	 * GA_DrawInfo tag.
+	 */
+
+#define LVA_SelectedPtr		(LVA_Dummy+19)
+	/* (GN) Selected item pointer. Will be NULL when no items are selected.
+	 */
+
+#define LVA_SelectArray		(LVA_Dummy+20)
+	/* (ISGNU) ULONG * - This array of ULONGs is only used in
+	 * LVA_#?Array modes, when multiple selection is active. It will
+	 * hold the selection status of all items in the list. Each
+	 * element will be 0 if the related item is unselected, and
+	 * it will indicate the selection order when the item is
+	 * selected.
+	 */
+
+#define LVA_CallBack		(LVA_Dummy+21)
+	/* (I) struct Hook * - Callback hook for various listview operations.
+	 * The call back hook is called with:
+     *		A0 - struct Hook *
+     *		A1 - struct LVDrawMsg *
+     *		A2 - struct Node *
+     * The callback hook *must* check the lvdm_MethodID field of the
+     * message and only do processing if it is known. If any other
+     * value is passed, the callback hook must return LVCB_UNKNOWN.
+	 */
+
+#define LVA_ShowSelected	(LVA_Dummy+23)
+	/* (I) BOOL - Enable highlighting selected items (default is TRUE).
+	 * Note that this is different from the GadTools ListView default,
+	 * which is not displaying the selected item.
+	 */
+
+#define LVA_Clipped			(LVA_Dummy+24)
+	/* (I) BOOL - When this attribute is set, the ListView gadget Installs
+	 * a ClipRegion in its Layer whenever it redraws its items.
+	 * (defaults is FALSE).
+	 */
+
+#define LVA_DoMultiSelect	(LVA_Dummy+25)
+	/* (I) BOOL - Allows picking multiple items from the list
+	 * (default is FALSE).
+	 * When MultiSelect mode is active and a List structure is used,
+	 * the ListView gadget keeps track of which items are selected
+	 * by setting/clearing the ln_Type field of the Node structure.
+	 * When items are passed with an array, you must also provide
+	 * a second array for storing selection information (LVA_SelectArray).
+	 * When item number n is selected, then the n-th BOOL of this array
+	 * is set with its selection order.
+	 */
+
+#define LVA_ItemHeight		(LVA_Dummy+27)
+	/* (I) ULONG - Exact height of an item. Defaults to be the Y size
+	 * of the font used to draw the text labels. The listview will ask
+	 * the height to its Image items if not explicitly given.
+	 */
+
+#define LVA_MaxPen			(LVA_Dummy+28)
+	/* (I) LONG - The maximum pen number used by rendering in a custom
+	 * rendering callback hook. This is used to optimize the
+	 * rendering and scrolling of the listview display (default is
+	 * the maximum pen number used by all of TEXTPEN, BACKGROUNDPEN,
+	 * FILLPEN, TEXTFILLPEN and BLOCKPEN).
+	 */
+
+#define LVA_TextFont		(LVA_Dummy+29)
+	/* (I) struct TextFont * - Font to be used for rendering texts.
+	 * Defaults to the default screen font.
+	 */
+
+#define LVA_DoubleClick		(LVA_Dummy+30)
+	/* (N) ULONG - The item specified by ti_Data has been double clicked.
+	 */
+
+#define LVA_MaxSelect		(LVA_Dummy+31)
+	/* (IS) ULONG - Maximum number of selected items to allow in multiselect
+	 * mode. If you later set this tag with a more restrictive condition, the
+	 * listview will NOT deselect any of the currently selected items to
+	 * satisfy your request. Default is unlimited (~0).
+	 */
+
+#define LVA_PixelTop		(LVA_Dummy+32)
+	/* (ISGNU) ULONG - Offset from top of list in pixel units. Useful for
+	 * scrollers.
+	 */
+
+#define LVA_PixelHeight		(LVA_Dummy+33)
+	/* (N) LONG - Total height of list in pixel units. Useful for scrollers.
+	 */
+
+#define LVA_PixelVVisible	(LVA_Dummy+34)
+	/* (N) LONG - Number of vertical visible pixels. Useful for scrollers.
+	 */
+
+#define LVA_PixelLeft		(LVA_Dummy+35)
+	/* (ISNU) LONG - Offset from left of list in pixel units. Useful for scrollers.
+	 */
+
+#define LVA_PixelWidth		(LVA_Dummy+36)
+	/* (ISNG) LONG - Total width of list in pixel units. Useful for scrollers.
+	 */
+
+#define LVA_PixelHVisible	(LVA_Dummy+37)
+	/* (N) Number of horizontal visible pixels. Useful for scrollers.
+	 */
+
+#define LVA_Title			(LVA_Dummy+38)
+	/* (IS) Listview title item.  This item will be drawn on the top line of
+	 * the list and will not scroll.  ti_Data points to an item in the same
+	 * format of the items list (e.g.: If it is LVA_StringArray, then it will
+	 * be a Node * with ln_Name pointing to a text string. The item will be
+	 * passed to the custom item drawing hook. (TODO)
+	 */
+
+#define LVA_Columns			(LVA_Dummy+39)
+	/* (I) (LONG) Number of columns to be displayed. Default is 1. If set
+	 * to ~0, the listview will precheck all items to calculate this number
+	 * automatically. (TODO)
+	 */
+
+#define LVA_ColumnSpacing	(LVA_Dummy+40)
+	/* (I) ULONG - Spacing between columns in pixel units.
+	 * Default is 4 pixels. (TODO)
+	 */
+
+#define LVA_ColumnWidths	(LVA_Dummy+41)
+	/* (IGS) ULONG * - Points to an array of ULONGs containing the width of
+	 * each column expressed in pixel units.  A value of -1 causes the
+	 * ListView to automatically calculate the width of the column, by
+	 * asking the width to all items. (TODO)
+	 */
+
+#define LVA_ColumnSeparator	(LVA_Dummy+42)
+	/* (I) UBYTE - When the listview is in multicolumn mode, the
+	 * internal text label routines will scan the string for this
+	 * character, as a separator for the column labels. This defaults
+	 * to '\t', so a label for a three column list view would look
+	 * like this: "One\tTwo\tThree". (TODO)
+	 */
+
+#define LVA_ResizeColumns	(LVA_Dummy+43)
+	/* (IS) BOOL - Allows the user to resize the columns by dragging
+	 * on the listview title line. (TODO)
+	 */
+
+#define LVA_SelectTick		(LVA_Dummy+44)
+	/* (I) When user selects an item, show a checkmark on the left
+	 * instead of rendering the item in selected state. (TODO)
+	 */
+
+#define LVA_ScrollInertia	(LVA_Dummy+45)
+	/* (IS) ULONG - Sets the scrolling inertia of the listview.
+	 * Defaults to 4 for LVA_Clipped mode, 0 for a non-clipped listview.
+	 * (TODO)
+	 */
+
+#define LVA_ScrollRatio		(LVA_Dummy+46)
+	/* (IS) ULONG - If you ask the listview to scroll more than
+	 * LVA_Visible / LVA_ScrollRatio lines, all the listview contents
+	 * will be redrawn instead.  The minimum value of 1 will trigger a
+	 * full redraw only when ALL the listview visible part is scrolled away.
+	 * The default value is 2, which is a good compromise for items which
+	 * can redraw themselves relatively quickly, such as simple text
+	 * labels or bitmap images.
+	 */
+
+/* Public flags */
+#define LVB_READONLY		0	/* Do not allow item selection				*/
+#define LVB_CLIPPED			1	/* Clip item drawing inside their boxes		*/
+#define LVB_SHOWSELECTED	2	/* Hilights the selected item				*/
+#define LVB_DOMULTISELECT	3	/* Allows user to pick more than one item	*/
+#define LVB_SMOOTHSCROLLING	4	/* Scoll pixel by pixel						*/
+#define LVB_RESIZECOLUMNS	5	/* Allow user to resize the columns			*/
+
+/* Internal flags - DO NOT USE */
+#define LVB_CLOSEFONT		27	/* Close the font when disposing the object	*/
+#define LVB_LIST			28	/* Using an exec List						*/
+#define LVB_DONTDRAW		29	/* Do not perform any drawing operations	*/
+#define LVB_SCROLLING		30	/* User scrolling with middle mouse button	*/
+#define LVB_DRAGGING		31	/* User dragging selection with LMB			*/
+
+
+#define LVF_READONLY		(1<<LVB_READONLY)
+#define LVF_CLIPPED			(1<<LVB_CLIPPED)
+#define LVF_SHOWSELECTED	(1<<LVB_SHOWSELECTED)
+#define LVF_DOMULTISELECT	(1<<LVB_DOMULTISELECT)
+#define LVF_SMOOTHSCROLLING	(1<<LVB_SMOOTHSCROLLING)
+#define LVF_RESIZECOLUMNS	(1<<LVB_RESIZECOLUMNS)
+
+#define LVF_CLOSEFONT		(1<<LVB_CLOSEFONT)
+#define LVF_LIST			(1<<LVB_LIST)
+#define LVF_DONTDRAW		(1<<LVB_DONTDRAW)
+#define LVF_SCROLLING		(1<<LVB_SCROLLING)
+#define LVF_DRAGGING		(1<<LVB_DRAGGING)
+
+
+
+/* Changed attributes:
+ *
+ * GA_ToggleSelect
+ *	(I) BOOL - When TRUE, the listview gadget will allow deselecting items
+ *	by clicking on them.
+ *
+ * GA_SelectRender
+ *	(I) struct Image * - Specifies an imageclass object to be used as
+ *	cursor/selection.  The image will be drawn in IDS_SELECTED state
+ *	for the selected item and IDS_NORMAL for all all other highlighted
+ *	items.
+ *
+ * GA_Immediate
+ *	(I) BOOL - Sends interim notifications when the selected item changes.
+ *
+ * GA_TextAttr
+ *	(I) struct TextAttr * - Font to be used for rendering texts.
+ *	Defaults to the default screen font. See also LVA_TextFont.
+ *
+ * GA_ReadOnly
+ *	(I) BOOL - Prevent selection of items (default is FALSE).
+ *
+ * LAYOUTA_Spacing
+ *	(I) UWORD - Extra space to place between lines of listview
+ *	(defaults to 0).
+ *
+ */
+
+
+
+/* Do not define these if <libraries/gadtools.h> will be included too */
+
+#ifdef LV_GADTOOLS_STUFF
+
+/* The different types of messages that a listview callback hook can see */
+#define LV_DRAW		0x202L		/* draw yourself, with state	*/
+
+/* Possible return values from a callback hook */
+#define LVCB_OK			0		/* callback understands this message type		*/
+#define LVCB_UNKNOWN	1		/* callback does not understand this message	*/
+
+/* states for LVDrawMsg.lvdm_State */
+#define LVR_NORMAL				0	/* the usual				*/
+#define LVR_SELECTED			1	/* for selected gadgets		*/
+#define LVR_NORMALDISABLED		2	/* for disabled gadgets		*/
+#define LVR_SELECTEDDISABLED	8	/* disabled and selected	*/
+
+#endif /* LV_GADTOOLS_STUFF */
+
+#define LVR_TITLE				16	/* ListView title item		*/
+
+
+/* More callback hook methods */
+
+#define LV_GETNEXT		0x203L	/* gimme next item in list		*/
+#define LV_GETPREV		0x204L	/* gimme previous item in list	*/
+#define LV_GETITEM		0x205L	/* gimme item handle by number	*/
+
+/* These two methods can be used to optimize listview rendering
+ * operations.  You can safely assume that the rastport attributes
+ * you set inside LV_DRAWBEGIN will remain unchanged for all
+ * subsequent calls to LV_DRAW, until an LV_DRAWEND is issued.
+ * They do also provide a way to lock/unlock the list of items
+ * if the access to its item needs to be arbitrated by a semaphore.
+ */
+#define LV_DRAWBEGIN	0x206L	/* prepare to draw items		*/
+#define LV_DRAWEND		0x207L	/* items drawing completed		*/
+
+
+
+/* More messages */
+
+struct lvDrawItem
+{
+	ULONG				lvdi_MethodID;	/* LV_DRAW						*/
+	ULONG				lvdi_Current;	/* Current item number			*/
+	APTR				lvdi_Items;		/* Pointer to List, array, etc.	*/
+	struct RastPort		*lvdi_RastPort;	/* where to render to			*/
+	struct DrawInfo		*lvdi_DrawInfo;	/* useful to have around		*/
+	struct Rectangle	lvdi_Bounds;	/* limits of where to render	*/
+	ULONG				lvdi_State;		/* how to render				*/
+	ULONG				lvdi_Flags;		/* Current LVF_#? flags			*/
+};
+
+struct lvGetItem
+{
+	ULONG	lvgi_MethodID;	/* LV_GETITEM					*/
+	ULONG	lvgi_Number;	/* Number of item to get		*/
+	APTR	lvgi_Items;		/* Pointer to List, array, etc.	*/
+};
+
+#define lvGetNext	lvGetItem
+#define lvGetPrev	lvGetItem
+#define lvDrawBegin	lvGetItem	/* lvgi_Number has no useful meaning	*/
+#define lvDrawEnd	lvGetItem	/* lvgi_Number has no useful meaning	*/
+
+#endif /* !LISTVIEWCLASS_H */
diff --git a/ListViewHooks.c b/ListViewHooks.c
new file mode 100644
index 0000000..9d65c93
--- /dev/null
+++ b/ListViewHooks.c
@@ -0,0 +1,293 @@
+/*
+**	ListViewHooks.c
+**
+**	Copyright (C) 1996,97 Bernardo Innocenti
+**
+**	Use 4 chars wide TABs to read this file
+**
+**	Internal drawing and browsing hooks the listview class
+*/
+
+#define USE_BUILTIN_MATH
+#define INTUI_V36_NAMES_ONLY
+#define __USE_SYSBASE
+#define  CLIB_ALIB_PROTOS_H		/* Avoid dupe defs of boopsi funcs */
+
+#include <exec/types.h>
+#include <intuition/intuition.h>
+#include <graphics/gfxbase.h>
+#include <graphics/gfxmacros.h>
+
+#include <proto/intuition.h>
+#include <proto/graphics.h>
+
+#ifdef __STORM__
+	#pragma header
+#endif
+
+#include "CompilerSpecific.h"
+#include "Debug.h"
+
+#define LV_GADTOOLS_STUFF
+#include "ListViewClass.h"
+
+
+
+/* Definitions for builtin List hook */
+
+APTR HOOKCALL ListGetItem (
+	REG(a0, struct Hook			*hook),
+	REG(a1, struct Node			*node),
+	REG(a2, struct lvGetItem	*lvgi));
+APTR HOOKCALL ListGetNext (
+	REG(a0, struct Hook			*hook),
+	REG(a1, struct Node			*node),
+	REG(a2, struct lvGetNext	*lvgn));
+APTR HOOKCALL ListGetPrev (
+	REG(a0, struct Hook			*hook),
+	REG(a1, struct Node			*node),
+	REG(a2, struct lvGetPrev	*lvgp));
+ULONG HOOKCALL ListStringDrawItem (
+	REG(a0, struct Hook			*hook),
+	REG(a1, struct Node			*node),
+	REG(a2, struct lvDrawItem	*lvdi));
+ULONG HOOKCALL ListImageDrawItem (
+	REG(a0, struct Hook			*hook),
+	REG(a1, struct Node			*node),
+	REG(a2, struct lvDrawItem	*lvdi));
+
+
+/* Definitions for builtin Array hook */
+
+APTR HOOKCALL ArrayGetItem (
+	REG(a0, struct Hook			*hook),
+	REG(a1, STRPTR				*item),
+	REG(a2, struct lvGetItem	*lvgi));
+ULONG HOOKCALL StringDrawItem (
+	REG(a0, struct Hook			*hook),
+	REG(a1, STRPTR				 str),
+	REG(a2, struct lvDrawItem	*lvdi));
+ULONG HOOKCALL ImageDrawItem (
+	REG(a0, struct Hook			*hook),
+	REG(a1, struct Image		*img),
+	REG(a2, struct lvDrawItem	*lvdi));
+
+
+
+APTR HOOKCALL ListGetItem (
+	REG(a0, struct Hook			*hook),
+	REG(a1, struct Node			*node),
+	REG(a2, struct lvGetItem	*lvg))
+{
+	ULONG i;
+
+	ASSERT_VALIDNO0(lvg)
+
+	node = ((struct List *)(lvg->lvgi_Items))->lh_Head;
+
+	/* Warning: no sanity check is made against
+	 * list being shorter than expected!
+	 */
+	for (i = 0; i < lvg->lvgi_Number; i++)
+	{
+		ASSERT_VALIDNO0(node)
+		node = node->ln_Succ;
+	}
+
+	return (APTR)node;
+}
+
+
+
+APTR HOOKCALL ListGetNext (
+	REG(a0, struct Hook			*hook),
+	REG(a1, struct Node			*node),
+	REG(a2, struct lvGetItem	*lvg))
+{
+	ASSERT_VALIDNO0(node)
+	ASSERT_VALIDNO0(lvg)
+
+	return (APTR)(node->ln_Succ->ln_Succ ? node->ln_Succ : NULL);
+}
+
+
+
+APTR HOOKCALL ListGetPrev (
+	REG(a0, struct Hook			*hook),
+	REG(a1, struct Node			*node),
+	REG(a2, struct lvGetItem	*lvg))
+{
+	ASSERT_VALIDNO0(node)
+	ASSERT_VALIDNO0(lvg)
+
+	return (APTR)(node->ln_Pred->ln_Pred ? node->ln_Pred : NULL);
+}
+
+
+
+ULONG HOOKCALL ListStringDrawItem (
+	REG(a0, struct Hook			*hook),
+	REG(a1, struct Node			*node),
+	REG(a2, struct lvDrawItem	*lvdi))
+{
+	ASSERT_VALIDNO0(node)
+	ASSERT_VALIDNO0(lvdi)
+
+	return StringDrawItem (hook, node->ln_Name, lvdi);
+}
+
+
+
+ULONG HOOKCALL ListImageDrawItem (
+	REG(a0, struct Hook			*hook),
+	REG(a1, struct Node			*node),
+	REG(a2, struct lvDrawItem	*lvdi))
+{
+	ASSERT_VALIDNO0(node)
+	ASSERT_VALIDNO0(lvdi)
+
+	return ImageDrawItem (hook, (struct Image *)node->ln_Name, lvdi);
+}
+
+
+
+APTR HOOKCALL ArrayGetItem (
+	REG(a0, struct Hook			*hook),
+	REG(a1, STRPTR				*item),
+	REG(a2, struct lvGetItem	*lvg))
+{
+	ASSERT_VALIDNO0(lvg)
+	ASSERT_VALIDNO0(lvg->lvgi_Items)
+
+	return (APTR)(((STRPTR *)lvg->lvgi_Items)[lvg->lvgi_Number]);
+}
+
+
+
+ULONG HOOKCALL StringDrawItem (
+	REG(a0, struct Hook			*hook),
+	REG(a1, STRPTR				 str),
+	REG(a2, struct lvDrawItem	*lvdi))
+{
+	struct RastPort *rp;
+	ULONG len;
+
+	ASSERT_VALIDNO0(lvdi)
+	rp = lvdi->lvdi_RastPort;
+	ASSERT_VALIDNO0(rp)
+	ASSERT_VALID(str)
+
+	if (!str)
+		/* Move to the leftmost pixel of the rectangle
+		 * to have the following RectFill() clear all the line
+		 */
+		Move (rp, lvdi->lvdi_Bounds.MinX, 0);
+	else
+	{
+		struct TextExtent textent;
+
+		if (lvdi->lvdi_State == LVR_NORMAL)
+		{
+#ifndef OS30_ONLY
+			if (GfxBase->LibNode.lib_Version < 39)
+			{
+				SetAPen (rp, lvdi->lvdi_DrawInfo->dri_Pens[TEXTPEN]);
+				SetBPen (rp, lvdi->lvdi_DrawInfo->dri_Pens[BACKGROUNDPEN]);
+				SetDrMd (rp, JAM2);
+			}
+			else
+#endif /* !OS30_ONLY */
+			SetABPenDrMd (rp, lvdi->lvdi_DrawInfo->dri_Pens[TEXTPEN],
+				lvdi->lvdi_DrawInfo->dri_Pens[BACKGROUNDPEN],
+				JAM2);
+		}
+		else
+		{
+#ifndef OS30_ONLY
+			if (GfxBase->LibNode.lib_Version < 39)
+			{
+				SetAPen (rp, lvdi->lvdi_DrawInfo->dri_Pens[FILLTEXTPEN]);
+				SetBPen (rp, lvdi->lvdi_DrawInfo->dri_Pens[FILLPEN]);
+				SetDrMd (rp, JAM2);
+			}
+			else
+#endif /* !OS30_ONLY */
+				SetABPenDrMd (rp, lvdi->lvdi_DrawInfo->dri_Pens[FILLTEXTPEN],
+					lvdi->lvdi_DrawInfo->dri_Pens[FILLPEN],
+					JAM2);
+		}
+
+		Move (rp, lvdi->lvdi_Bounds.MinX, lvdi->lvdi_Bounds.MinY + rp->Font->tf_Baseline);
+
+		len = strlen (str);
+
+		if (!(lvdi->lvdi_Flags & LVF_CLIPPED))
+		{
+			/* Calculate how much text will fit in the listview width */
+			len = TextFit (rp, str, len, &textent, NULL, 1,
+				lvdi->lvdi_Bounds.MaxX - lvdi->lvdi_Bounds.MinX + 1,
+				lvdi->lvdi_Bounds.MaxY - lvdi->lvdi_Bounds.MinY + 1);
+		}
+
+		Text (rp, str, len);
+
+		/* Text() will move the pen X position to
+		 * lvdi->lvdi_Bounds.MinX + textent.te_Width.
+		 */
+	}
+
+	/* Now clear the rest of the row. rp->cp_x is updated by Text() to the
+	 * next character to print.
+	 */
+	SetAPen (rp, lvdi->lvdi_DrawInfo->dri_Pens[(lvdi->lvdi_State == LVR_NORMAL) ?
+		BACKGROUNDPEN : FILLPEN]);
+	RectFill (rp, rp->cp_x,
+		lvdi->lvdi_Bounds.MinY,
+		lvdi->lvdi_Bounds.MaxX,
+		lvdi->lvdi_Bounds.MaxY);
+
+
+	return LVCB_OK;
+}
+
+
+
+ULONG HOOKCALL ImageDrawItem (
+	REG(a0, struct Hook			*hook),
+	REG(a1, struct Image		*img),
+	REG(a2, struct lvDrawItem	*lvdi))
+{
+	struct RastPort *rp;
+	UWORD left;
+
+	ASSERT_VALID(img)
+	ASSERT_VALIDNO0(lvdi)
+	rp = lvdi->lvdi_RastPort;
+	ASSERT_VALIDNO0(rp)
+
+	if (!img)
+		/* Move to the leftmost pixel of the item rectangle
+		 * to have the following RectFill() clear all the line
+		 */
+		left = lvdi->lvdi_Bounds.MinX;
+	else
+	{
+		DrawImageState (rp, img,
+			lvdi->lvdi_Bounds.MinX, lvdi->lvdi_Bounds.MinY,
+			lvdi->lvdi_State, lvdi->lvdi_DrawInfo);
+
+		left = lvdi->lvdi_Bounds.MinX + img->Width;
+	}
+
+	/* Now clear the rest of the row. rp->cp_x is updated by Text() to the
+	 * next character to print.
+	 */
+	SetAPen (rp, lvdi->lvdi_DrawInfo->dri_Pens[(lvdi->lvdi_State == LVR_NORMAL) ?
+		BACKGROUNDPEN : FILLPEN]);
+	RectFill (rp, left,
+		lvdi->lvdi_Bounds.MinY,
+		lvdi->lvdi_Bounds.MaxX,
+		lvdi->lvdi_Bounds.MaxY);
+
+	return LVCB_OK;
+}
diff --git a/SMakefile b/SMakefile
new file mode 100644
index 0000000..f985286
--- /dev/null
+++ b/SMakefile
@@ -0,0 +1,136 @@
+##
+##	$VER: LVDemo_Makefile 2.2 (14.9.97)
+##
+##	Copyright (C) 1996,97 by Bernardo Innocenti
+##
+##
+
+###########################################################
+# Name of the main executable
+###########################################################
+#
+PROJ = LVDemo
+
+
+###########################################################
+# Package configuration
+###########################################################
+#
+
+# set to OS30_ONLY to leave out support for old V37
+# set to ANY_OS to make an executable for V37 with V39 support
+#
+#OSVER = ANY_OS
+OSVER = OS30_ONLY
+
+# Cpu to compile for (eg: "68020").
+#
+#CPU = 68000
+CPU = 68020
+
+###########################################################
+# Object files in this project
+###########################################################
+#
+OBJS = startup_sc.o LVDemo.o ListViewHooks.o \
+ ListViewClass.o ListBoxClass.o ScrollButtonClass.o
+
+
+###########################################################
+# Make the project
+###########################################################
+#
+all: $(PROJ)
+
+
+###########################################################
+# Remove all targets and intermediate files
+###########################################################
+#
+clean:
+	-Delete $(PROJ) $(OBJS) $(PROJ).gst
+
+
+###########################################################
+# Compiler, linker and assembler flags
+###########################################################
+#
+# Note: Using the "STRINGSCONST" compiler option requires
+#       patched versions of the OS headers to work correctly
+#
+
+# Compiler flags for both release and debug versions
+#
+COMMON_CFLAGS = PARAMETERS=REGISTERS STRINGMERGE NOSTACKCHECK NOCHECKABORT \
+	NOICONS NOVERSION ERRORREXX NOLINK DATA=NEAR CODE=NEAR \
+	STRSECT=CODE STRINGSCONST GST $(PROJ).gst DEF=$(OSVER) CPU=$(CPU)
+
+# Compiler optimization flags
+#
+OPT_CFLAGS = OPTIMIZE OPTTIME OPTSCHEDULER OPTINLINELOCAL \
+	OPTRDEPTH=4 OPTDEPTH=4 OPTCOMP=8
+
+# Debug flags: don't optimize and include all symbols in debug hunks
+#
+DEBUG_CFLAGS = NOOPTIMIZE DEBUG=FULLFLUSH ONERROR=CONTINUE DEF DEBUG CODE=FAR
+
+# Use the utility.library for 32bit multiplication and division.
+#
+UTILLIB_LFLAGS = DEFINE __CXM33=__UCXM33 DEFINE __CXD33=__UCXD33 \
+	DEFINE __CXM22=__UCXM22 DEFINE __CXD22=__UCXD22
+
+
+# RELEASE version should be compiled with these flags
+#
+#CFLAGS = $(COMMON_CFLAGS) $(OPT_CFLAGS)
+#LFLAGS = NODEBUG SMALLCODE SMALLDATA NOALVS NOICONS $(UTILLIB_LFLAGS)
+#LIBS = LIB LIB:sc.lib
+
+
+# DEBUG version should be compiled with these flags
+#
+CFLAGS = $(COMMON_CFLAGS) $(DEBUG_CFLAGS)
+LFLAGS = ADDSYM SMALLCODE SMALLDATA BATCH NOALVS NOICONS $(UTILLIB_LFLAGS)
+LIBS = LIB LIB:debug.lib LIB:sc.lib LIB:small.lib
+
+
+###########################################################
+# Make Global Symbol Table to speed up compiling
+###########################################################
+#
+# We must define some symbols here because defining them
+# inside GST.c won't work as expected.
+#
+# NOTE:	The GST file does not depend on ListViewClass.h because
+#	otherwise all objects would be remade whenever I slightly edit
+#	the header file.
+#
+
+$(PROJ).gst: GST.c
+	$(CC) FROM GST.c MAKEGST $(PROJ).gst NOOBJNAME $(CFLAGS) \
+	 DEF=INTUI_V36_NAMES_ONLY DEF=__USE_SYSBASE \
+	 DEF=CLIB_ALIB_PROTOS_H DEF=LV_GADTOOLS_STUFF
+
+###########################################################
+# Make the executable
+###########################################################
+#
+# NOTE: Using implicit make rule to compile C files:
+#	.c.o:
+#		$(CC) $(CFLAGS) $(*).c
+#
+# NOTE: Using implicit make rule to assemble startup_sc.s
+#
+
+$(PROJ): $(PROJ).gst $(OBJS)
+	$(LD) FROM $(OBJS) TO $(PROJ) $(LIBS) $(LFLAGS)
+
+
+###########################################################
+# Dependencies
+###########################################################
+#
+ListViewClass.o:		ListViewClass.c
+ListBoxClass.o:			ListBoxClass.c
+ScrollButtonClass.o:	ScrollButtonClass.c
+LVDemo.o:				LVDemo.c
diff --git a/ScrollButtonClass.c b/ScrollButtonClass.c
new file mode 100644
index 0000000..ebc0a77
--- /dev/null
+++ b/ScrollButtonClass.c
@@ -0,0 +1,147 @@
+/*
+**	ScrollButtonClass.c
+**
+**	Copyright (C) 1998 Bernardo Innocenti
+**
+**	Use 4 chars wide TABs to read this file
+**
+**	Subclass of the buttongclass specialized for scroll buttons
+*/
+
+#define USE_BUILTIN_MATH
+#define INTUI_V36_NAMES_ONLY
+#define __USE_SYSBASE
+#define  CLIB_ALIB_PROTOS_H		/* Avoid dupe defs of boopsi funcs */
+
+
+#include <intuition/gadgetclass.h>
+
+#include <proto/intuition.h>
+
+
+#ifdef __STORM__
+	#pragma header
+#endif
+
+#include "CompilerSpecific.h"
+#include "Debug.h"
+#include "BoopsiStubs.h"
+
+#include "ScrollButtonClass.h"
+
+
+
+
+/* Per object instance data */
+struct ScrollButtonData
+{
+	/* The number of ticks we still have to wait
+	 * before sending any notification.
+	 */
+	ULONG TickCounter;
+};
+
+
+
+/* Dispatcher function prototype (just to keep esigent compilers quiet) */
+
+static ULONG HOOKCALL ScrollButtonDispatcher (
+	REG(a0, Class *cl),
+	REG(a2, struct Gadget *g),
+	REG(a1, struct gpInput *gpi));
+
+static ULONG HOOKCALL ScrollButtonDispatcher (
+	REG(a0, Class *cl),
+	REG(a2, struct Gadget *g),
+	REG(a1, struct gpInput *gpi))
+
+/* ScrollButton Class Dispatcher entrypoint.
+ * Handle boopsi messages.
+ */
+{
+	struct ScrollButtonData *bd = (struct ScrollButtonData *) INST_DATA(cl, g);
+
+	switch (gpi->MethodID)
+	{
+		case GM_GOACTIVE:
+			/* May define an attribute to make delay configurable */
+			bd->TickCounter = 3;
+
+			/* Notify our target that we have initially hit. */
+			NotifyAttrs ((Object *)g, gpi->gpi_GInfo, 0,
+				GA_ID,	g->GadgetID,
+				TAG_DONE);
+
+			/* Send more input */
+			return GMR_MEACTIVE;
+
+		case GM_HANDLEINPUT:
+		{
+			struct RastPort *rp;
+			ULONG retval = GMR_MEACTIVE;
+			UWORD selected = 0;
+
+			/* This also works with classic (non-boopsi) images. */
+			if (PointInImage ((gpi->gpi_Mouse.X << 16) + (gpi->gpi_Mouse.Y), g->GadgetRender))
+			{
+				/* We are hit */
+				selected = GFLG_SELECTED;
+			}
+
+			if (gpi->gpi_IEvent->ie_Class == IECLASS_RAWMOUSE && gpi->gpi_IEvent->ie_Code == SELECTUP)
+			{
+				/* Gadgetup, time to go */
+				retval = GMR_NOREUSE;
+				/* Unselect the gadget on our way out... */
+				selected = 0;
+			}
+			else if (gpi->gpi_IEvent->ie_Class == IECLASS_TIMER)
+			{
+				/* We got a tick.  Decrement counter, and if 0, send notify. */
+
+				if (bd->TickCounter)
+					bd->TickCounter--;
+				else if (selected)
+					/* Notify our target that we are still being hit */
+					NotifyAttrs ((Object *)g, gpi->gpi_GInfo, 0,
+						GA_ID,	g->GadgetID,
+						TAG_DONE);
+			}
+
+			if ((g->Flags & GFLG_SELECTED) != selected)
+			{
+				/* Update changes in gadget render */
+				g->Flags ^= GFLG_SELECTED;
+				if (rp = ObtainGIRPort (gpi->gpi_GInfo))
+				{
+					DoMethod ((Object *) g, GM_RENDER, gpi->gpi_GInfo, rp, GREDRAW_UPDATE);
+					ReleaseGIRPort (rp);
+				}
+			}
+			return retval;
+		}
+
+		default:
+			/* Super class handles everything else */
+			return (DoSuperMethodA (cl, (Object *)g, (Msg) gpi));
+	}
+}
+
+
+
+Class *MakeScrollButtonClass (void)
+{
+	Class *class;
+
+	if (class = MakeClass (NULL, BUTTONGCLASS, NULL, sizeof(struct ScrollButtonData), 0))
+		class->cl_Dispatcher.h_Entry = (ULONG (*)()) ScrollButtonDispatcher;
+
+	return class;
+}
+
+
+
+BOOL FreeScrollButtonClass (Class *cl)
+{
+	return (FreeClass (cl));
+}
diff --git a/ScrollButtonClass.h b/ScrollButtonClass.h
new file mode 100644
index 0000000..560fa52
--- /dev/null
+++ b/ScrollButtonClass.h
@@ -0,0 +1,48 @@
+#ifndef SCROLLBUTTONCLASS_H
+#define SCROLLBUTTONCLASS_H
+/*
+**	ScrollButtonClass
+**
+**	This code is inspierd from ScrollerWindow 0.3
+**	Copyright © 1994 Christoph Feck, TowerSystems.
+**
+**	Subclass of buttongclass.  The ROM class has two problems, which make
+**	it not quite usable for scrollarrows.  The first problem is the missing
+**	delay.  Once the next INTUITICK gets sent by input.device, the ROM
+**	class already sends a notification.  The other problem is that it also
+**	notifies us, when the button finally gets released (which is necessary
+**	for command buttons).
+**
+**	We define a new class with the GM_GOACTIVE and GM_HANDLEINPUT method
+**	overloaded to work around these problems.
+*/
+
+
+#define SCROLLBUTTONCLASS	"scrollbuttonclass"
+#define SCROLLBUTTONVERS	1
+
+
+/* Functions to initialize and destroy the class */
+Class	*MakeScrollButtonClass	(void);
+BOOL	 FreeScrollButtonClass	(Class *cl);
+
+
+
+/*****************/
+/* Class Methods */
+/*****************/
+
+/* This class does not define any new methods */
+
+/********************/
+/* Class Attributes */
+/********************/
+
+/* #define SBA_Dummy (TAG_USER | ('S'<<16) | ('B'<<8)) */
+
+/* This class does not define any new attributes */
+
+
+
+
+#endif /* !SCROLLBUTTONCLASS_H */
diff --git a/VectorGlyphIClass.h b/VectorGlyphIClass.h
new file mode 100644
index 0000000..3998396
--- /dev/null
+++ b/VectorGlyphIClass.h
@@ -0,0 +1,26 @@
+/*
+**	VectorGlyphIClass.h
+**
+**	Copyright (C) 1995,96,97,98 by Bernardo Innocenti
+**
+**	"vectorglyphiclass" class, a vector image class built
+**	on top of the "imageclass", providing some useful
+**	glyphs for buttons.
+*/
+
+#define VECTORGLYPHCLASS "vectorglyphiclass"
+
+/* Values for the SYSIA_Which attribute */
+#define VG_PLAY			0
+#define VG_STOP			1
+#define VG_REW			2
+#define VG_FWD			3
+#define VG_PICK			4
+#define VG_UPARROW		5
+#define VG_DOWNARROW	6
+#define VG_LEFTARROW	7
+#define VG_RIGHTARROW	8
+
+
+/* Number of glyphs offered by the vectorglyph.image class */
+#define VG_IMGCOUNT 9
diff --git a/startup_gcc.s b/startup_gcc.s
new file mode 100644
index 0000000..ca4a05d
--- /dev/null
+++ b/startup_gcc.s
@@ -0,0 +1,2 @@
+.text
+	jmp	__main
diff --git a/startup_sc.s b/startup_sc.s
new file mode 100644
index 0000000..a37c6b0
--- /dev/null
+++ b/startup_sc.s
@@ -0,0 +1,9 @@
+
+	XREF @_main
+
+	SECTION CODE,code
+
+_start:
+	jmp	@_main
+
+	END
diff --git a/startup_storm.s b/startup_storm.s
new file mode 100644
index 0000000..912a205
--- /dev/null
+++ b/startup_storm.s
@@ -0,0 +1,9 @@
+
+	XREF __main
+
+	SECTION CODE,code
+
+_start:
+	bra	__main
+
+	END