From: Bernie Innocenti Date: Fri, 11 Mar 2011 06:02:27 +0000 (-0500) Subject: Initial commit X-Git-Url: https://codewiz.org/gitweb?a=commitdiff_plain;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 + */ +#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 in 68K register +** +** min(a,b) +** Return the minimum between and +** +** max(a,b) +** Return the maximum between and +** +** abs(a) +** Return the absolute value of +** +** _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 +#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 + #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 +#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 + + #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 . + * 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 evalutates to a +** non-zero value, output a debug message otherwise. +** +** ASSERT_VALID(x) +** Checks if the expression 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 points to a valid +** memory location, and outputs a debug message +** otherwise. A NULL pointer is considered INVALID. +** +** DB(x) +** Compile the expression when making a debug +** executable, leave it out otherwise. +*/ + +#ifdef DEBUG + + /* Needed for TypeOfMem() */ + #ifndef PROTO_EXEC_H + #include + #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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#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 . +** 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 +#include +#include + +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#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 */ + +#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 ." +}; + +#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 +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#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 +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#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 */ + 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 to . No sanity checks are performed + * to ensure that all items between and 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 + */ + #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< 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 +#include +#include +#include + +#include +#include + +#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 + +#include + + +#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