跳转到内容

Aros/Developer/Docs/Libraries/Intuition/BOOPSI

来自 Wikibooks,开放世界中的开放书籍
Aros 维基百科的导航栏
Aros 用户
Aros 用户文档
Aros 用户常见问题解答
Aros 用户应用程序
Aros 用户 DOS Shell
Aros/User/AmigaLegacy
Aros 开发文档
Aros 开发人员文档
从 AmigaOS/SDL 移植软件
面向 Zune 初学者
Zune .MUI 类
面向 SDL 初学者
Aros 开发人员构建系统
特定平台
Aros x86 完整系统 HCL
Aros x86 音频/视频支持
Aros x86 网络支持
Aros Intel AMD x86 安装
Aros 存储支持 IDE SATA 等
Aros Poseidon USB 支持
x86-64 支持
摩托罗拉 68k Amiga 支持
Linux 和 FreeBSD 支持
Windows Mingw 和 MacOSX 支持
Android 支持
Arm Raspberry Pi 支持
PPC Power Architecture
其他
Aros 公共许可证

BOOPSI(Intuition 的基本面向对象编程系统)是 AmigaOS 的面向对象编程系统。它使用面向对象的子系统扩展了 AmigaOS 窗口环境 (Intuition),允许对象类的层次结构,其中每个类定义一个单独的 GUI 小部件或界面事件。

BOOPSI 使开发人员更容易创建自己的小部件系统并创建标准化的图形用户界面。Magic User Interface (MUI) 和 ReAction 是基于 BOOPSI 构建的完整小部件工具包的示例。这两个工具包已成为 Amiga 软件程序员生成和维护图形用户界面的流行选择。

面向对象的设计带来了诸如对象与其他对象的直接耦合之类的优势。例如,程序员可以链接一个数值输入字段和一个滑块控件,如果用户调整滑块控件,则输入字段中的数值会自动更改。

BOOPSI 是随 AmigaOS (TM) 2.0 正式引入的,并在后续版本中得到进一步扩展。在稍加了解 BOOPSI 的情况下,数据类型会变得更容易。

oop.library 是另一个 AROS 面向对象软件基础,用于构建 HIDD。(图形 HIDD、PCI HIDD、鼠标 HIDD 等)。过去,Staf(监督 ABI 更改)表示,他可能会在那里改变一些方面(他不希望为当前的 ABIv1 工作冻结它),因此最好从他那里获得有关这些担忧的意见,如果将任何编译器/框架支持与任何东西统一起来是否有意义。

Intuition 的基本面向对象编程系统 (BOOPSI) 是一个简单的 OS 支持模型,用于为您的程序生成系统范围的和语言无关的类。BOOPSI 本身是通过 intuition.library 提供的,尽管它可以用于 GUI 以外的事情并没有什么理由。该系统通过提供一种方法来使面向对象风格的类可用,然后提供一组函数来创建对象并在这些对象上执行操作来工作。

查看 Open Boopsi 项目

创建小工具对象,并调用 AddGadget()。设置位置和大小数据,以便将其放置在需要的位置。计算窗口调整大小时的新位置和大小等。Intuition 确实有一个名为 GM_LAYOUT 的新小工具方法,用于告知 Boopsi 小工具其窗口的尺寸已更改。GM_LAYOUT 方法使用 gpLayout 结构。因此,您可以添加、删除和修改已添加到小工具对象的 BOOPSI 对象。通过使用 LAYOUT_AddChild、LAYOUT_AddImage、LAYOUT_RemoveChild 和 LAYOUT_ModifyChild 标签。

Why Zune is recommended as the default gui option for AROS.

BOOPSI 中的类非常易于创建、维护和使用。它们只是一个充当类每个“方法”的调度器的函数。本质上,一个关于请求方法的“消息”是使用 DoMethod() 函数发送给对象的。DoMethod() 函数将查看该对象并确定它所属的类,然后将消息传递到该类的“方法”调度器。消息本身是一个简单的结构,形式如下

typedef struct {
	ULONG MethodID;
	/* Method specific information follows */
} *Msg;

例如,OM_SET 消息如下所示

struct opSet {
	ULONG MethodID;
	struct TagItem *ops_AttrList;
	struct GadgetInfo *ops_GInfo;
};

调度器

[编辑 | 编辑源代码]

调度器实际上只是一个单一函数,它使用指向其类结构、调用该方法的对象和发送给该对象的消息的指针来调用。然后,调度器查看消息内部以查看在对象上调用了哪个方法,然后采取适当的行动。以下是调度器的一个示例

ULONG dispatcher( Class *cl, Object *o, Msg msg )
{
	ULONG retval = NULL;
	
	/* figure out what method was called */
	switch( msg->MethodID )
	{
		/* these are just some of the standard methods defined by rootclass */
		case OM_NEW:
			break;
		case OM_SET:
			break;
		case OM_GET:
			break;
			
		/* other methods that the class can handle are placed here... */
		
		/* the method called isn't understood by this class, call our parent
		   class with the message */
		default:
			DoSuperMethodA( cl, o, msg );
			break;
	}
	return( retval );
}

BOOPSI 中的对象只是一个分配的内存块,它被对象所属的类及其所有祖先拥有的部分分割。每个类都知道其数据在哪里,方法是使用其类结构中的一个字段,该字段由系统设置,因此为了访问其数据,该类会在该字段中查找,然后将其值添加到对象指针中。幸运的是,提供了一个宏来为您执行此功能。

struct localObjData {
	ULONG value1;
	ULONG value2;
};

void dispatcher( Class *cl, Object *o, Msg msg )
{
	struct localObjData *lod;
	ULONG retval;

	switch( msg->MethodID )
	{
		case OM_NEW:
			break;
		...
		case SM_SOMEMETHOD:
			lod = INST_DATA(cl, o); /* get the pointer to our instance data */
			lod->value2 = lod->value1;  /* do something with it */
			lod->value1 = SOME_NUMBER;
			break;
		...
		default:
			DoSuperMethodA(cl, o, msg);
			break;
	}
	return( retval );
}

!!!!!!!!!!!!!!!警告!!!!!!!!!!!!!!! 如果您打算制作类,请务必确保您的实例数据指针 lod 已设置,否则您将遇到很多麻烦。我无法告诉你我忘记设置它多少次,最终花费了很长时间才弄清楚问题出在那里。

类由系统跟踪,因此要向系统添加新类,您必须首先使用 MakeClass() 函数创建一个系统函数可以理解和使用的类结构。MakeClass() 函数只接受一些信息,例如类的名称、父类的标识符和实例大小。调度器不会传递到函数中,因为它是稍后添加到类结构中的。然后,如果您想使该类公开可用,则必须使用 AddClass() 函数。

if( cl = MakeClass( "someclassnamehere",
		"parentclassnamehere",
		0,  /* this is used to point to a private Class structure */
		sizeof( struct localObjData ), /* the size of the class */
		0 ) )
{
	cl->cl_Dispatcher.h_Entry = dispatcher;
	AddClass( cl );
}

当不再需要该类时,它将通过 RemoveClass() 和 FreeClass() 函数删除。所有这些工作都在一个简单框架类中完成,该类作为共享库实现,当加载时会自动将自己添加到系统中。

BOOPSI 中的对象只是分配的内存块,由对象所属的类控制。要创建新对象,请在 intuition.library 中使用 NewObject() 调用,并传递对象要成为其实例的类的标识符以及任何初始属性设置。

struct Gadget *gad;

gad = NewObject( NULL, "buttongclass",
	GA_Left, 0,
	GA_Top, 0,
	GA_Width, 10,
	GA_Height, 10,
	...
	TAG_DONE );

使用 NewObject() 创建对象后,您可以通过 DoMethod() 调用其其中一个方法在它上执行许多函数。对于一些系统支持的方法,例如 OM_SET 和 OM_GET,会为您提供函数,因此您不需要使用 DoMethod()。

ULONG data;

SetAttrs( someobject, GA_Left, 10, GA_Top, 10, TAG_DONE );

SetGadgetAttrs( somegadget, window, requester, GA_Left, 10, ..., TAG_DONE );

/* get some attribute from some object and put it in the data ULONG */
GetAttr( &data, someobject, SA_SomeAttribute );

DoMethod( someobject, SM_SOMEMETHOD, ... );

SetGadgetAttrs() 是为小工具类的任何子类提供的,以便可以创建这些类所需的某些特殊信息。虽然在设置小工具的属性时并不总是需要它,但任何可能改变小工具图形的属性都应该使用 SetGadgetAttrs() 设置。

DoMethod() 函数实际上只是普通链接库的一部分,通常操作起来很简单,尽管它可能会导致问题。基于堆栈的 DoMethod() 被设计为,您传递给它的每个参数都将被解释为 ULONG,如果它不是 ULONG,则会将其转换为 ULONG。因此,如果您需要传递更小的数据,请确保将其适当地打包,或者直接使用标签数组 DoMethod()。例如,图像类使用的 IM_DRAW 方法接受一个 X 和 Y 参数,它们都是字

struct impDraw
{
	ULONG MethodID;
	struct RastPort *imp_RPort;
	struct
	{
		WORD X;
		WORD Y;
	} imp_Offset;
	ULONG imp_State;
	struct DrawInfo *imp_DrInfo;
	
	struct
	{
		WORD Width;
		WORD Height;
	} imp_Dimensions;
};

/* BAD BAD BAD BAD BAD BAD */

DoMethod( imageobject, IM_DRAW, rp, x, y, state, dri );

/* end BAD */

/* GOOD GOOD GOOD */

struct impDraw msg;

msg.MethodID = IM_DRAW;
msg.imp_RPort = rp;
...
DoMethodA( imageobject, &msg );

/* also GOOD */

struct Offset {
	WORD X;
	WORD Y;
};

/* ... */

struct Offset off;

off.X = x;
off.Y = y;

DoMethod( imageobject, IM_DRAW, rp, off, state, dri );
                                    ^^^
                  /* this is the key since it will put the two words on the stack
                     as if it were just a ULONG */

/* end GOOD */

请注意,尽管 imp_Draw 结构大于我们在堆栈上传递的结构,但这并不重要,因为只有 IM_DRAWFRAME 方法会关注额外的字段,因此不需要进行额外的工作。

BOOPSI 中的对象具有一个特殊属性,即每个创建的对象在负偏移处都包含一个结构。该结构由根类放置到位,并包含一个指向对象类的指针和一个 MinNode 结构,该结构可用于将对象保存在列表中。

为了使用每个对象中的 MinNode 结构,根类提供了 OM_ADDTAIL、OM_REMOVE、OM_ADDMEMBER 和 OM_REMMEMBER 方法。OM_ADDTAIL 和 OM_REMOVE 方法由根类实现以执行其预期功能,因此您可以在程序或类中使用它们。但是,OM_*MEMBER 方法没有由根类实现,而是为了为子类提供实现模型。成员函数的一个示例可能是将项目添加到您分配的某个列表视图对象中。

DoMethod( listview, OM_ADDMEMBER, sometextitem );

由于对象具有根类数据位于负偏移处的属性,因此可以轻松地创建基类并公开其结构,因此无需使用 SetAttrs() 和 GetAttr() 函数。这在 gadgetclass 基类中得到了有效利用,因此作为小部件的 BOOPSI 对象可以轻松地用作 Intuition 中的旧式小部件。

struct Gadget *gad;

gad = NewObject( NULL, "strgclass",
		GA_Left, 0,
		...
		TAG_DONE );

AddGadget( win, gad, -1 );
RefreshGadgets( gad, win, 0 );
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ /* Dont forget this!!!  Its the number one problem ppl have */

小部件

[编辑 | 编辑源代码]

作为 icclass 或 gadgetclass 实例的对象也能够通过其属性相互通信。目标对象由 ICA_TARGET 属性设置,然后每当类的某个属性发生改变时,它都会向目标对象发送 OM_UPDATE 消息,然后目标对象可以以某种方式对其进行操作。目标不一定是对象,因为您可以将 ICA_TARGET 值设置为 ICTARGET_IDCMP,以便从对象发送的任何 OM_UPDATE 消息都将通过 IDCMP 端口传递给应用程序。不幸的是,当目标是对象时,可能会混淆属性标识符应该意味着什么。为了解决这个问题,使用了一个名为 ICA_MAP 的特殊属性,将属性 ID 映射到另一个属性,目标对象能够理解该属性。例如,propgclass 使用 PGA_Top 属性作为其当前值,而 strgclass 使用 STRINGA_LongVal 属性作为其当前值。这两个属性具有不同的标识符,因此如果它们彼此作为目标,它们将无法理解属性应该意味着什么。

/* example of communication between a prop gadget, string gadget, and an app */

struct TagItem prop2intmap[] =
{
	{PGA_Top, STRINGA_LongVal},
	{TAG_DONE,}
};

struct TagItem int2propmap[] =
{
	{STRINGA_LongVal, PGA_Top},
	{TAG_DONE,}
};

#define PROPID 1
#define STRID 2

void main()
{
	/* Open libs and window */
	
	prop = NewObject( NULL, "propgclass",
			GA_Left, 0,
			...
			GA_ID, PROPID,
			ICA_MAP, prop2intmap,
			PGA_Top, 0,
			...
			TAG_DONE );
	if( prop && (integer = NewObject( NULL, "strgclass",
						GA_ID, STRID,
						...
						ICA_TARGET, prop,
						ICA_MAP, int2propmap,
						GA_Previous, prop,
						STRINGA_LongVal, 0,
						...
						TAG_DONE )) )
	{
		SetAttrs( prop, ICA_TARGET, integer, TAG_DONE );
		AddGList( window, prop, -1, -1, 0 );
		RefreshGList( prop, window, 0, -1 );
		...
		RemoveGList( window, prop, -1 );
		DisposeObject( integer );
	}
	DisposeObject( prop );
}

以下是用 IDCMP 端口进行通信的相同示例。

struct TagItem prop2intmap[] =
{
	{PGA_Top, STRINGA_LongVal},
	{TAG_DONE,}
};

struct TagItem int2propmap[] =
{
	{STRINGA_LongVal, PGA_Top},
	{TAG_DONE,}
};

#define PROPID 1
#define STRID 2

void main()
{
	/* Open libs and window */
	
	model = NewObject( NULL, "modelclass",
						ICA_TARGET, ICTARGET_IDCMP,
						ICA_MAP, int2propmap
						TAG_DONE );
						
	prop = NewObject( NULL, "propgclass",
						GA_Left, 0,
						...
						GA_ID, PROPID,
						ICA_TARGET, model
						PGA_Top, 0,
						...
						TAG_DONE );
						
	if( int2prop = NewObject( NULL, "icclass",
						ICA_TARGET, prop,
						ICA_MAP, int2propmap,
						TAG_DONE ) )
	{
		DoMethod( model, OM_ADDMEMBER, int2prop );
		if( model && prop && int2prop && (integer = NewObject( NULL, "strgclass",
						GA_ID, STRID,
						...
						ICA_TARGET, model,
						GA_Previous, prop,
						STRINGA_LongVal, 0,
						...
						TAG_DONE )) )
		{
	
			if( prop2int = NewObject( NULL, "icclass",
						ICA_TARGET, integer,
						ICA_MAP, prop2intmap,
						TAG_DONE ) )
			{
		
				DoMethod( model, OM_ADDMEMBER, prop2int );

				AddGList( window, prop, -1, -1, 0 );
				RefreshGList( prop, window, 0, -1 );
				...
				RemoveGList( window, prop, -1 );
			}
			DisposeObject( integer );		
		}
	}
	DisposeObject( prop );
	/* we only have to delete the model and not the icclasses since it
	   will dispose of its internal list when we dispose of it */
	DisposeObject( model );
}

最后,当您完全完成一个对象时,需要通过 DisposeObject() 函数将其释放。它使用起来很简单,但您需要注意不要释放两次同一个对象。这可能发生的唯一时间是,如果您将对象传递给另一个对象,并且该对象已经为您释放了它。另一种可能性是,如果您在窗口中添加了自己的系统小部件(关闭、深度等),那么一旦您关闭了窗口,系统将自动为您释放窗口中的任何系统小部件。您还应该记住,DisposeObject() 函数足够智能,知道不要尝试释放空指针。此属性允许您创建许多相互独立的对象,而无需将它们封装在 if 语句中。

/* use */
gad = NewObject(...
gad2 = NewObject(...
if( gad && gad2 )
{
}
DisposeObject( gad );
DisposeObject( gad2 );

/* instead of */

if( gad = NewObject(...) )
{
	if( gad2 = NewObject(...) )
	{
		DisposeObject( gad2 );
	}
	DisposeObject( gad );
}

/* if you can :) */

请注意,如果您这样做,则必须注意不要在任何其他对象中使用对象指针。例如,GA_Previous 属性接受指向小部件的指针,然后修改其 NextGadget 字段以指向正在创建的对象。如果传递到该属性的小部件不存在,则会破坏内存。

创建自己的类

[编辑 | 编辑源代码]

创建自己的类时,建议您使用 Skeleton 类,然后从那里继续,因为它已经为您完成了许多繁重的工作,因此您可以直接编写类的核心代码。

按钮类

[编辑 | 编辑源代码]

最好的做法是带您逐步了解按钮类的代码... 编写过程中发现的任何错误都留给读者作为练习 :)

Header File

#ifndef BBBUTTONCLASS_H
#define BBBUTTONCLASS_H

#define BGA_Dummy (TAG_USER + 0x60000)
#define BGA_Push (BGA_Dummy + 1)
#define BGA_Image (BGA_Dummy + 2)

//Convenient macro for creating buttons
#define ButtonObject NewObject( NULL, "bbbuttongadget"

#endif
Casting Macros
I like to use casting macros a lot so here are some: 
#define GA(o) ((struct Gadget *)o)
#define IA(o) ((struct Image *)o)
#define IM(o) ((struct Image *)o)
#define SET(o) ((struct opSet *)o)
#define GET(o) ((struct opGet *)o)
#define GPR(o) ((struct gpRender *)o)
#define GPI(o) ((struct gpInput *)o)
#define GPL(o) ((struct gpLayout *)o)

它们只是提供了一种简单快捷的方式,可以将 BOOPSI 样式的消息从 Msg 转换为该消息的适当结构

几乎每个类都需要一些与之关联的标志来编码选项或状态。

#define BB_TEXT 0 //is the label text?
#define BB_PUSH 1 //should the button hold its state when (de)selected

#define BF_TEXT (1L << BB_TEXT)
#define BF_PUSH (1L << BB_PUSH)

与其将所有代码都打包到调度器中,不如将其分解成单独的函数,这是一个好主意。

//always good for debugging, since you can't use printf() from a library or even a class i think...
extern int kprintf( const char *str, ... );

//the dispatcher itself
ULONG
ASM dispatchClass( REG(a0) Class * cl, REG(a2) Object * o, REG(a1) Msg msg );

//Used to set our attributes
ULONG
ASM setClassAttrs( REG(a0) Class * cl, REG(a2) Object * o, REG(a1) struct opSet * msg );

//Used to get some attribute
ULONG
ASM getClassAttr( REG(a0) Class *cl, REG(a2) Object *o, REG(a1) struct opGet * msg);

//a custom TextLength() that doesn't need a RastPort
LONG myTextLength( struct TextFont *font, char *str, int len );

//The ubiquitous Notify() function found in all my gadget classes
//It takes care of sending the OM_UPDATE messages to the target object
void ASM Notify( REG(a0) Class *cl, REG(a2) Object *o, REG(a1) struct opSet *msg, REG(d1) ULONG flags, REG(d2) sel, REG(a3) struct GadgetInfo *ginfo );
Globals
Class *cl = 0;  // our class structure pointer
struct Library *ClasservBase;  //pointer to the classerv.library base, see the my classes page

struct DrawInfo defdri;  //a default drawinfo in case one isn't passed in
//we use it to find a height and width of buttons with text labels so we really need it

WORD defpens[NUMDRIPENS];  //default pen array for the drawinfo

//in case we're disabled we should ghost
USHORT ghostdata[] =
{
	0x2222, 0x8888
};

//our instance data, try to make it small
struct localObjData {
	struct DrawInfo *dri;
	BYTE flags;
};

//ahhh, easy open and closing of libs
struct OpenLibTemplate olt[] =
{
	{"intuition.library",36,&IntuitionBase},
	{"graphics.library",0,&GfxBase},
	{"utility.library",0,&UtilityBase},
	{"classerv.library",0,&ClasservBase},
	{0}
};

//same for classes although we don't need any here
struct OpenClassTemplate oct[] =
{
	{0}
};

我们需要为小部件和超类选择一个名称。就命名约定而言,目前还没有统一的标准。一旦 classerv.library 充分发挥其潜力,就应该有一个标准。

#define MYCLASSID "bbbuttongadget"
#define SUPERCLASSID "gadgetclass"
/* OpenLibrary/CloseLibrary
I do all of my classes so that they are in libraries, you don't have to though, you can make private classes and public classes in your own code. I just like being able to have them shareable and easily replaced. */
int ASM SAVEDS __UserLibInit( REG(a6) struct MyLibrary *libbase )
{
	//Open libraries and classes
	if( OpenLibraries( olt ) )
	{
		if( OpenClasses( oct ) )
		{
			//setup our default drawinfo
			defpens[BACKGROUNDPEN] = 0;
			defpens[SHADOWPEN] = 1;
			defpens[SHINEPEN] = 2;
			defpens[TEXTPEN] = 1;
			defdri.dri_Pens = defpens;
			defdri.dri_NumPens = NUMDRIPENS;
			defdri.dri_Font = GfxBase->DefaultFont;
			//setup our class
			if( cl = MakeClass( MYCLASSID,
 	                     SUPERCLASSID, NULL,
   	                   sizeof(struct localObjData), 0))
			{
				/* Fill in the callback hook */
				cl->cl_Dispatcher.h_Entry = (ULONG (*) ())dispatchClass;
				/* Keep track of the libbase here since we'll need it later */
				cl->cl_UserData = (ULONG)libbase;
				/* Make the class public */
				AddClass( cl );
				return( FALSE );
			}
			/* something is hosed, close the classes and libs */
		}
		CloseClasses( oct );
	}
	CloseLibraries( olt );
	return( TRUE );
}

void ASM SAVEDS __UserLibCleanup( REG(a6) struct MyLibrary *libbase )
{
	if( cl )
	{
		/* Remove and free our class structure */
		RemoveClass( cl );
		FreeClass(cl);
	}
	/* Close libs and classes, see how easy it is :) */
	CloseClasses( oct );
	CloseLibraries( olt );
}

调度器... 糟糕,困难的部分来了... 请注意没有 __saveds,它在这里不能使用...

ULONG ASM dispatchClass( REG(a0) Class *cl, REG(a2) Object *o, REG(a1) Msg msg)
{
	ULONG retval = FALSE;
	Object *newobj;
	struct localObjData *lod;
/* C can be a pain when coding shared libraries. Just because this function is in a shared library doesn't mean that a6 will be set to our library base so we must first put our lib base, which we saved in cl_UserData, into a6 before we can get a4 and access to all our global data (library bases, etc...). */
	putreg( REG_A6, cl->cl_UserData );
	geta4();

	switch (msg->MethodID)
	{
		case OM_NEW:       /* First, pass up to superclass */
#ifdef DEBUG
			kprintf( "class.class/OM_NEW:\n" );
#endif
			if(newobj = (Object *)DSM(cl, o, msg))
			{

首先,您使用 DoSuperMethodA()(简称 DSM())将消息传递给上层,这最终会命中根类,创建我们的对象,然后在我们父类将其实例数据初始化为默认值后,消息会传回给我们。然后,在下面,我们获得实例数据指针并设置任何默认值。始终确保设置实例指针,并且绝不要从 OM_NEW 方法中复制和粘贴实例宏,因为它使用 lod = INST_DATA( cl, newobj ),而所有其他方法都应该使用 lod = INST_DATA(cl, o),请注意应该是 o 而不是 newobj。注意:您应该始终将消息传递给父类,将来有些人可能希望实现错误处理,以便在 taglists 中传递的任何对象在 OM_NEW 失败时都会被释放。

				/* Initial local instance data */
				lod = INST_DATA( cl, newobj );
				lod->dri = &defdri;
				//Use set function to interpret the tags passed in
				setClassAttrs( cl, newobj, (struct opSet *)msg );
				retval = (ULONG)newobj;
			}
			break;
		case OM_SET:
#ifdef DEBUG
			kprintf( "class.class/OM_SET:\n" );
#endif
			retval = DSM( cl, o, msg );
			retval += setClassAttrs( cl, o, SET(msg) );
			break;
		case OM_GET:
#ifdef DEBUG
			kprintf( "class.class/OM_GET:\n" );
#endif
			retval = getClassAttr( cl, o, GET(msg) );
			break;

GM_RENDER 出现了,每当 Intuition 请求时,它就会执行小部件的所有绘制操作。由于这是第一个与小部件相关的函数,因此我们现在将讨论 GadgetInfo 结构。

struct GadgetInfo
{
	struct Screen *gi_Screen;
	struct Window *gi_Window;
	struct Requester *gi_Requester;
	
	struct RastPort *gi_RastPort;
	struct Layer *gi_Layer;
	
	struct IBox gi_Domain;
	
	struct {
		UBYTE DetailPen;
		UBYTE BlockPen;
	} gi_Pens;
	
	struct DrawInfo *gi_DrInfo;
	
	ULONG gi_Reserved[4];
};

此结构为您提供了有关小部件在系统中的位置以及任何绘制所需信息的丰富信息。它在所有小部件方法消息和根类定义的 OM_SET 消息中都可用。但请注意,所有小部件方法的指针都位于 MethodID 之后,但 OM_SET 消息的指针位于不同的位置,这之前曾给我带来过问题。

		case GM_RENDER:

			{
				struct RastPort *rp = GPR(msg)->gpr_RPort;
				struct DrawInfo *dri;
				//Support for the GREL_ flags, these should be made into macros someday.
				WORD left = (GA(o)->Flags & GFLG_RELRIGHT) ? GPR(msg)->gpr_GInfo->gi_Domain.Width + GA(o)->LeftEdge - 1 : GA(o)->LeftEdge;
				WORD top = (GA(o)->Flags & GFLG_RELBOTTOM) ? GPR(msg)->gpr_GInfo->gi_Domain.Height + GA(o)->TopEdge - 1 : GA(o)->TopEdge;
				WORD width = (GA(o)->Flags & GFLG_RELWIDTH) ? GPR(msg)->gpr_GInfo->gi_Domain.Width + GA(o)->Width : GA(o)->Width;
				WORD height = (GA(o)->Flags & GFLG_RELHEIGHT) ? GPR(msg)->gpr_GInfo->gi_Domain.Height + GA(o)->Height : GA(o)->Height;

				lod = INST_DATA( cl, o );

				dri = lod->dri;  //cache a pointer to the drawinfo
				//here we check if there is a border image (most likely a frameiclass object) which we should draw else we just setup a background color
				if( GA(o)->GadgetRender )
				{
					DrawImageState( rp, GA(o)->GadgetRender, left, top, (GA(o)->Flags & GFLG_SELECTED) ? IDS_SELECTED : IDS_NORMAL, GPR(msg)->gpr_GInfo->gi_DrInfo );
					SetDrMd( rp, JAM1 );
				}
				else
				{
					SetAPen( rp, (GA(o)->Flags & GFLG_SELECTED) ? dri->dri_Pens[FILLPEN] : dri->dri_Pens[BACKGROUNDPEN] );
					RectFill( rp, left, top, left + width - 1, top + height - 1 );
				}
				if( GA(o)->GadgetText )
				{
					WORD lwidth, lheight;
					WORD xoffset, yoffset;

					//check if we're drawing some text or an image, and figure out the width and height of which one
					if( (lod->flags & BF_TEXT) )
					{
						lwidth = myTextLength( dri->dri_Font, (char *)GA(o)->GadgetText, strlen( (char *)GA(o)->GadgetText ) );
						lheight = dri->dri_Font->tf_YSize;
					}
					else
					{
						lwidth = IA(GA(o)->GadgetText)->Width;
						lheight = IA(GA(o)->GadgetText)->Height;
					}
					//center the text or image inside the gadget
					xoffset = left + ((width - lwidth) / 2);
					yoffset = top + ((height - lheight) / 2);
					//draw whatever it is
					if( (lod->flags & BF_TEXT) )
					{
						SetFont( rp, dri->dri_Font );
						SetAPen( rp, (GA(o)->Flags & GFLG_SELECTED) ? dri->dri_Pens[HIGHLIGHTTEXTPEN] : dri->dri_Pens[TEXTPEN] );
						Move( rp, xoffset, yoffset + dri->dri_Font->tf_Baseline );
						Text( rp, (STRPTR)GA(o)->GadgetText, strlen( (char *)GA(o)->GadgetText ) );
					}
					else
					{
						DrawImageState( rp, GA(o)->GadgetRender, xoffset, yoffset, (GA(o)->Flags & GFLG_SELECTED) ? IDS_SELECTED : IDS_NORMAL, GPR(msg)->gpr_GInfo->gi_DrInfo );
					}
				}
				//check if we're disabled and draw a ghosting pattern if we are
				if( GA(o)->Flags & GFLG_DISABLED	)
				{
					SetAPen( rp, GPI(msg)->gpi_GInfo->gi_DrInfo->dri_Pens[SHADOWPEN] );
					SetAfPt( rp, ghostdata, 1 );
					RectFill( rp, left, top, left + width - 1, top + height - 1 );
				}
			}
			break;
/* GM_LAYOUT is an OS 3.x only function but if it is here it won't hurt. Here we use it to automatically resize the framing image, although you can use it for a number of other things. Remember though that you shouldn't do any drawing here since Intuition will call GM_RENDER later, this is just setup so you can do recomputing. */
		case GM_LAYOUT:
			{
				WORD width = (GA(o)->Flags & GFLG_RELWIDTH) ? GPR(msg)->gpr_GInfo->gi_Domain.Width + GA(o)->Width : GA(o)->Width;
				WORD height = (GA(o)->Flags & GFLG_RELHEIGHT) ? GPR(msg)->gpr_GInfo->gi_Domain.Height + GA(o)->Height : GA(o)->Height;

				lod = INST_DATA( cl, o );

				if( GA(o)->GadgetRender )
				{
					SetAttrs( GA(o)->GadgetRender,
								IA_Width, width,
								IA_Height, height,
								TAG_DONE );
				}
			}
			break;
/* GM_HITTEST is here so that you can tell Intuition whether the gadget was hit or not and then respond accordingly. Here we just respond that we were in fact hit and we should be activated since it is a square gadget. Although this method can be used so that it check something else to see if it was hit or not (ie. call IM_HITTEST on a BOOPSI image object) */
		case GM_HITTEST:
			retval = GMR_GADGETHIT;
			break;
/* GM_GOACTIVE is here so that you can set things up and do any long time operations before the gadget starts to receive all of the input.device messages. This function has a special return value which tells intuition what it should do with the input event which caused us to go active. They are GMR_NOREUSE which tells Intuition to just kill the event, GMR_REUSE which means it should use the event again (ie. if there was a right mouse button event we would want the menu to show up instead of just trashing it, this can only happen in GM_HANDLEINPUT though), GMR_NEXTACTIVE which is just like pressing tab when a string gadget is selected, GMR_PREVACTIVE which is just like pressing shift tab when a string gadget is selected, and GMR_MEACTIVE which tells Intuition that we are active and want to stay that way. Note that below should've checked to see if an input event activated us or if it was ActivateGadget(), this should be fixed. */
		case GM_GOACTIVE:
			{
				struct RastPort *rp;

				lod = INST_DATA( cl, o );

				//check if we're a push in button and act appropriately
				if( lod->flags & BF_PUSH )
					GA(o)->Flags ^= GFLG_SELECTED;
				else
					GA(o)->Flags |= GFLG_SELECTED;
				//Always, always, always use ObtainGIRPort to get the RastPort,
				//unless of course its a GM_RENDER msg
				if( rp = ObtainGIRPort( GPI(msg)->gpi_GInfo ) )
				{
					DoMethod( o, GM_RENDER, GPI(msg)->gpi_GInfo, rp, GREDRAW_REDRAW );
					ReleaseGIRPort( rp );
				}
				//If its a push in button there is no need to go into GM_HANDLEINPUT
				//so we just return GMR_NOREUSE.
				if( lod->flags & BF_PUSH )
					retval = GMR_NOREUSE;
				else
					retval = GMR_MEACTIVE;
			}
			break;
/* GM_HANDLEINPUT is where the gadget does all the processing of the input events. Here its pretty simple since just need to catch timer events and send messages on those and check to make sure the mouse is actually over the gadget */
		case GM_HANDLEINPUT:
			{
				struct RastPort *rp;
				WORD x = GPI(msg)->gpi_Mouse.X;  //these are relative to the upper left corner of the gadget
				WORD y = GPI(msg)->gpi_Mouse.Y;
				struct InputEvent *ie = GPI(msg)->gpi_IEvent;
				BOOL sel = FALSE;
				WORD left = (GA(o)->Flags & GFLG_RELRIGHT) ? GPR(msg)->gpr_GInfo->gi_Domain.Width + GA(o)->LeftEdge - 1 : GA(o)->LeftEdge;
				WORD top = (GA(o)->Flags & GFLG_RELBOTTOM) ? GPR(msg)->gpr_GInfo->gi_Domain.Height + GA(o)->TopEdge - 1 : GA(o)->TopEdge;
				WORD width = (GA(o)->Flags & GFLG_RELWIDTH) ? GPR(msg)->gpr_GInfo->gi_Domain.Width + GA(o)->Width : GA(o)->Width;
				WORD height = (GA(o)->Flags & GFLG_RELHEIGHT) ? GPR(msg)->gpr_GInfo->gi_Domain.Height + GA(o)->Height : GA(o)->Height;

				lod = INST_DATA( cl, o );

				//check to see if the mouse is over the gadget
				if( (x >= 0) && (x < (width)) &&
					(y >= 0) && (y < (height)) )
				{
					sel = TRUE;
				}
				//on timer events we should send messages to our ICA_TARGET
				//Notice they are OPUF_INTERIM since the user isn't done with us yet
				if( ie->ie_Class == IECLASS_TIMER )
				{
					Notify( cl, o, msg, OPUF_INTERIM, sel, GPI(msg)->gpi_GInfo );
				}
				//this takes care of mouse events
				if( ie->ie_Class == IECLASS_RAWMOUSE )
				{
					switch( ie->ie_Code )
					{
						case SELECTUP:
							//user let up the select button we need to deactivate
							retval = GMR_NOREUSE;
							//if we are selected we need to tell intuition to send a IDCMP_RELVERIFY
							if( GA(o)->Flags & GFLG_SELECTED )
								retval |= GMR_VERIFY;
							//send a final notify, (ie. no OPUF_INTERIM flag)
							Notify( cl, o, msg, 0, sel, GPI(msg)->gpi_GInfo );
							//Set the code field of the IntuiMessage to our GadgetID
							(*GPI(msg)->gpi_Termination) = GA(o)->GadgetID;
							break;
						case MENUDOWN:
							//this is where GMR_REUSE come into play mainly
							retval = GMR_REUSE;
							break;
						default:
							//check if we need to change the graphics of the gadget if
							//the mouse is/isn't over the gadget
							if( (!sel && (GA(o)->Flags & GFLG_SELECTED)) ||
								(sel && !(GA(o)->Flags & GFLG_SELECTED)) )
							{
								GA(o)->Flags ^= GFLG_SELECTED;
								if( rp = ObtainGIRPort( GPI(msg)->gpi_GInfo ) )
								{
									DoMethod( o, GM_RENDER, GPI(msg)->gpi_GInfo, rp, GREDRAW_REDRAW );
									ReleaseGIRPort( rp );
								}
							}
							break;
					}
				}
			}
			break;
/* GM_GOINACTIVE is where we cleanup after GM_GOACTIVE and GM_HANDLEINPUT. Do the final render here since the gadget could've been inactivated by a select up or a mouse down, so we would've had to write this code for both GM_HANDLEINPUT. */
		case GM_GOINACTIVE:
			{
				struct RastPort *rp;

				lod = INST_DATA( cl, o );

				if( !(lod->flags & BF_PUSH) )
				{
					GA(o)->Flags &= ~GFLG_SELECTED;
					if( rp = ObtainGIRPort( GPI(msg)->gpi_GInfo ) )
					{
						DoMethod( o, GM_RENDER, GPI(msg)->gpi_GInfo, rp, GREDRAW_REDRAW );
						ReleaseGIRPort( rp );
					}
				}
			}
			break;

                /* Finally, if we don't know what the method is we just pass it up to the parent classes */
		default:
			retval = DSM(cl, o, msg);
			break;
	}
	return(retval);
}

SetAttrs()... 这里没有更好的选择。Skeleton 类中提供的 SetClassAttr() 函数旨在简化处理所有愚蠢标签的过程。同样请注意,函数中没有 __saveds,尽管我认为它应该可以用,因为它始终被调度器调用,但谁知道呢。

ULONG
ASM setClassAttrs( REG(a0) Class * cl, REG(a2) Object * o, REG(a1) struct opSet * msg )
{
	struct localObjData *lod = INST_DATA(cl, o);
	struct TagItem *tags = msg->ops_AttrList;
	struct TagItem *tstate;
	struct TagItem *tag;
	ULONG           tidata;
	BOOL change = FALSE;
	BOOL sizechange = FALSE;

	putreg( REG_A6, cl->cl_UserData );
	geta4();

	/* process rest */
	tstate = tags;
	while (tag = NextTagItem(&tstate))
	{
		tidata = tag->ti_Data;
		switch (tag->ti_Tag)
		{
			//This is a special attribute to tell make the gadget just
			//show the image instead of trying to try the frame
			case BGA_Image:
				GA(o)->GadgetRender = tidata;
				GA(o)->Width = IM(tidata)->Width;
				GA(o)->Height = IM(tidata)->Height;
				break;
			//A text label is wanted, set flags and set change flag
			case GA_Text:
				lod->flags |= BF_TEXT;
				change = TRUE;
				break;
			//An image for the label is wanted, clear flag and set change flag
			case GA_LabelImage:
				lod->flags &= ~BF_TEXT;
				change = TRUE;
				break;
			case GA_DrawInfo:
				lod->dri = (struct DrawInfo *)tidata;
				break;
			//programmatic size change, do a redraw
			case GA_Width:
			case GA_Height:
				sizechange = TRUE;
				break;
			//flag thingies, should prolly use one of the utility.library functions.
			case BGA_Push:
				if( tidata )
					lod->flags |= BF_PUSH;
				else
					lod->flags &= ~BF_PUSH;
				break;
			//dunno what it is
			default:
				break;
		}
	}
	//Check for size change, change the frame's rectangle, should prolly do a GM_RENDER here, oops
	if( sizechange )
	{
		if( GA(o)->GadgetRender )
		{
			SetAttrs( GA(o)->GadgetRender,
						IA_Width, GA(o)->Width,
						IA_Height, GA(o)->Height,
						TAG_DONE );
		}
	}
	//Theres some new text gotta adjust the size.  There should be some flags here
	//so that the programmer can stop the resize from occurring
	if( change && GA(o)->GadgetText )
	{
		struct IBox cont, frame;
		WORD width, height;

		//check for text/image label and find the size
		if( (lod->flags & BF_TEXT) )
		{
			struct DrawInfo *dri = lod->dri;

			width = myTextLength( dri->dri_Font, (char *)GA(o)->GadgetText, strlen( (char *)GA(o)->GadgetText ) );
			height = dri->dri_Font->tf_YSize;
		}
		else
		{
			width = IA(GA(o)->GadgetText)->Width;
			height = IA(GA(o)->GadgetText)->Height;
		}
		//try and do an IM_FRAMEBOX to figure out how big the frame wants to be for this size graphic
		cont.Width = width;
		cont.Height = height;
		if( GA(o)->GadgetRender )
		{
			frame.Left = 0;
			frame.Top = 0;
			frame.Width = width;
			frame.Height = height;
			DoMethod( GA(o)->GadgetRender, IM_FRAMEBOX, &frame, &cont, lod->dri, 0 );
		}
		//GREL_ flags should be check above
		if( !(GA(o)->Flags & GFLG_RELWIDTH) )
			GA(o)->Width = cont.Width;
		if( !(GA(o)->Flags & GFLG_RELHEIGHT) )
			GA(o)->Height = cont.Height;
		//Adjust the frame size, dunno if we wanna redraw here or not since the
		//programmer might want to manually do it so a redraw would be ugly
		if( GA(o)->GadgetRender )
		{
			SetAttrs( GA(o)->GadgetRender,
								IA_Width, cont.Width,
								IA_Height, cont.Height,
								TAG_DONE );
		}
	}
	return (1L);
}

GetClassAtr... 只是纯粹的脂肪。目前这对于我们来说毫无用处,不过没有理由删除它。

ULONG
ASM getClassAttr( REG(a0) Class *cl, REG(a2) Object *o, REG(a1) struct opGet * msg )
{
	struct localObjData *lod = INST_DATA(cl, o);

	putreg( REG_A6, cl->cl_UserData );
	geta4();

	switch (msg->opg_AttrID)
	{
		default:
			return ((ULONG) DSM(cl, o, (Msg)msg));
	}
	return (1L);
}

myTextLength... 糟糕... 这可能很糟糕,但我讨厌不得不创建一个 RastPort 只是为了获取文本长度。

LONG myTextLength( struct TextFont *font, char *str, int len )
{
	int lpc;
	LONG width = 0;
	int currch;

	if( font->tf_Flags & FPF_PROPORTIONAL )
	{
		for( lpc = 0; lpc < len; lpc++ )
		{
			currch = str[lpc] - font->tf_LoChar;
			width += ((WORD *)font->tf_CharSpace)[currch] + ((WORD *)font->tf_CharKern[currch]);
		}
	}
	else
	{
		width = font->tf_XSize * len;
	}
	return( width );
}

通知... 很容易做但概念上很难 OM_NOTIFY/OM_UPDATE 连接是我永远无法理解的东西。无论如何,我们需要向我们的目标发送一个 OM_UPDATE,并将我们的 GA_ID 作为属性。该属性的 ti_Data 反映了鼠标是否在按钮上。

void ASM Notify( REG(a0) Class *cl, REG(a2) Object *o, REG(a1) struct opSet *msg, REG(d1) ULONG flags, REG(d2) sel, REG(a3) struct GadgetInfo *ginfo )
{
	struct TagItem tt[2];
	struct localObjData *lod = INST_DATA(cl, o);

	putreg( REG_A6, cl->cl_UserData );
	geta4();

	tt[0].ti_Tag = GA_ID;
	tt[0].ti_Data = sel ? GA(o)->GadgetID : -GA(o)->GadgetID;

	tt[1].ti_Tag = TAG_DONE;

	DoSuperMethod( cl, o, OM_NOTIFY, tt, ginfo, flags );
}
//Set up proportional gadget and arrows using standard Intuition.
/* Obtain Images for the up and down arrows (use BOOPSI sysiclass)). */

upImage = NewObject(NULL, SYSICLASS,
SYSIA_Which, UPIMAGE,
SYSIA_DrawInfo, di,
SYSIA_Size, SYSISIZE_MEDRES,
TAG_END);

downImage = NewObject(NULL, SYSICLASS,
SYSIA_Which, DOWNIMAGE,
SYSIA_DrawInfo, di,
SYSIA_Size, SYSISIZE_MEDRES,
TAG_END);

...

/* Up and down gadget definitions */
upArrow.NextGadget = &vPropGad;
upArrow.LeftEdge = (-sim.win[WIN_SRC]->BorderRight + 1);
upArrow.TopEdge = (-upImage->Height - downImage->Height - sizeGHeight);
upArrow.Width = upImage->Width;
upArrow.Height = upImage->Height;
upArrow.Flags = GFLG_RELBOTTOM | GFLG_RELRIGHT | GFLG_EXTENDED | GFLG_GADGIMAGE;
upArrow.Activation = GACT_IMMEDIATE | GACT_RIGHTBORDER | GACT_RELVERIFY | GACT_BOOLEXTEND;
upArrow.GadgetType = GTYP_BOOLGADGET;
upArrow.GadgetRender = upImage;
upArrow.SelectRender = NULL;
upArrow.GadgetText = NULL;
upArrow.MutualExclude = 0; //Obsolete anyway.
upArrow.SpecialInfo = NULL;
upArrow.GadgetID = GAD_SRC_UP;
upArrow.UserData = NULL;
upArrow.MoreFlags = 0;

/* Bounds variables not initialised because GMORE_BOUNDS is not set */
downArrow.NextGadget = &upArrow;
downArrow.LeftEdge = (-sim.win[WIN_SRC]->BorderRight + 1);
downArrow.TopEdge = (-downImage->Height - sizeGHeight);
downArrow.Width = upImage->Width;
downArrow.Height = upImage->Height;
downArrow.Flags = GFLG_RELBOTTOM | GFLG_RELRIGHT | GFLG_EXTENDED | GFLG_GADGIMAGE;
downArrow.Activation = GACT_IMMEDIATE | GACT_RIGHTBORDER |GACT_RELVERIFY | GACT_BOOLEXTEND;
downArrow.GadgetType = GTYP_BOOLGADGET;
downArrow.GadgetRender = downImage;
downArrow.SelectRender = NULL;
downArrow.GadgetText = NULL;
downArrow.MutualExclude = 0; //Obsolete anyway.
downArrow.SpecialInfo = NULL;
downArrow.GadgetID = GAD_SRC_DOWN;
downArrow.UserData = NULL;
downArrow.MoreFlags = 0;

/* Bounds variables not initialised because GMORE_BOUNDS is not set */
(void)AddGList(sim.win[WIN_SRC], (struct Gadget *)&downArrow, 0, -1, NULL);
RefreshGadgets((struct Gadget *)&downArrow, sim.win[WIN_SRC], NULL);

struct Image *NewImageObject (ULONG which)
* Creates a sysiclass object. */
{
return ((struct Image *)NewObject (NULL, SYSICLASS,
	SYSIA_DrawInfo,	DrawInfo,
	SYSIA_Which,	which,
	SYSIA_Size, Scr->Flags & SCREENHIRES ? SYSISIZE_MEDRES : SYSISIZE_LOWRES, TAG_DONE)); 
/* NB: SYSISIZE_HIRES not yet supported. */

创建对象

[编辑 | 编辑源代码]

创建对象相对容易,只需要找出你想创建对象的类名,或者传入私有类的类指针。

struct Gadget *gad;
Class *privclass;

/* for public classes */
gad = NewObject( NULL, "strgclass", ... );

/* for private classes */
gad = NewObject( privclass, 0, ... );

/* defines are a handy shortcut instead of writing them out all the time */
#define ButtonObject NewObject( NULL, "buttongclass"
There are a few gotchas though. Some attributes can't be specified or can only be given during creation so you need to be careful about what you pass in. For example, the frbuttonclass won't use the GA_Width/GA_Height attributes during creation so you need to use a separate SetAttrs() call in order to adjust it properly.

struct Image *frame;
struct Gadget *gad;

frame = NewObject( NULL, "frameiclass", TAG_DONE );
if( frame )
{
	gad = NewObject( NULL, "frbuttonclass",
			GA_Left, 10,
			GA_Top, 10,
			GA_Width, 200, /* these won't do anything */
			GA_Height, 30,
			GA_Image, frame,
			GA_Text, "Hello World",
			TAG_DONE );
	if( gad )
	{
		/* you have to use a SetAttrs() in order for it to work */
		SetAttrs( gad, GA_Width, 200, GA_Height, 30, TAG_DONE );
		AddGadget( win, gad, -1 );
		RefreshGadgets( gad, win, 0 );
	}
}

创建多个对象也可能很麻烦,因为你应该检查每次创建以确保它成功,然后才能使用它。对每次创建都这样做非常繁琐,所以最好一次创建几个,然后在一个 "if" 语句中检查它们。

/* use */

gad = NewObject( ... );
gad2 = NewObject( ... );
if( gad && gad2 )
{
}
/* DisposeObject() is smart enough to not dispose of a NULL so we can do this safely */
DisposeObject( gad );
DisposeObject( gad2 );

/* instead of */

if( gad = NewObject( ... ) )
{
	if( gad2 = NewObject( ... ) )
	{
		/* ... */
		DisposeObject( gad2 );
	}
	DisposeObject( gad );
}

BOOPSI 小工具就像普通的小工具一样,它们通过 IDCMP 端口使用相同的消息进行通信。它们还有一个额外的消息类,称为 IDCMP_IDMCPUPDATE,它允许小工具向任务发送属性列表。要设置它,你需要将小工具的 ICA_TARGET 属性设置为 ICTARGET_IDMCP,并且可以选择将一个属性映射到 ICSPECIAL_CODE,这样该属性的值就会被插入到 IntuiMessage 的 Code 字段中。小工具还会将包含小工具 ID 的 GA_ID 属性放入标签列表中,这样你就可以知道哪个小工具发送了消息。

struct TagItem prop2idcmp[] = {
	{PGA_Top, ICSPECIAL_CODE}, /* we are mapping the PGA_Top to the 
	                         IntuiMessages Code field */
	{TAG_DONE}
};

void main()
{
	struct Window *win;
	
	if( win = OpenWindowTags( NULL,
				...,
				/* use IDCMP_IDCMPUPDATE to get taglists from gadgets */
				WA_IDCMP, IDCMP_IDCMPUPDATE|(otherflags),
				...,
				TAG_DONE ) )
	{
		struct Gadget *prop;
		
		prop = NewObject( NULL, "propgclass",
					...,
					GA_ID, 1, /* set the GadgetID so we can distinguish it from the others */
					ICA_MAP, prop2idcmp, /* pass in the map taglist */
					ICA_TARGET, ICTARGET_IDCMP, /* tell the gadget to send
					                           an IDCMP_IDCMPUPDATE message
					                           when a notify attribute changes */
					TAG_DONE );
		if( prop )
		{
			ULONG waitsigs, portsig;
			BOOL done = FALSE;
			struct IntuiMessage *imsg;
			ULONG gadgetid;
			
			AddGadget( win, prop, -1 );
			RefreshGadgets( prop, win, 0 );
			portsig = 1L << win->UserPort->mp_SigBit;
			while( !done )
			{
				waitsigs = Wait( portsig | SIGBREAKF_CTRL_C );
				if( waitsigs & portsig )
				{
					while( imsg = GetMsg( win->UserPort ) )
					{
						switch( imsg->Class )
						{
							case IDCMP_IDCMPUPDATE:
								/* the taglist will have a GA_ID with it's gadget ID
								   We use GetTagData() and set the default to 0 so
								   we know if something went wrong and we got a message
								   without a GA_ID */
								gadgetid = GetTagData( GA_ID, 0, imsg->IAddress );
								switch( gadgetid )
								{
									case 1:
										/* its the prop gad, do something */
										printf( "prop top %ld\n", imsg->Code );
										break;
									default:
										break;
								}
								break;
						}
						ReplyMsg( imsg );
					}
				}
				if( waitsigs & SIGBREAKF_CTRL_C )
					done = TRUE;
			}
			RemoveGadget( win, prop );
		}
		DisposeObject( prop );
		CloseWindow( win );
	}
}

由 Tim Stack ([email protected]) 维护 最后更新于 1997 年 12 月 27 日 09:51

ObjC 和 ObjFW

[编辑 | 编辑源代码]

BOOPSI 和 ObjC 似乎都使用类似的对象模型。ObjC 允许直接访问瞬态变量,而 BOOPSI 似乎需要一个宏才能做到这一点。但是,即使 ObjC 允许直接访问瞬态变量,这也很少使用,而是使用访问器(只有在类内部才直接访问实例变量,几乎从不使用 @public 实例变量,因为这会破坏鸭子类型)。

分派似乎也很相似,两者都有一个分派表,用于每次调用。但是,有一个显著的差异:选择器(这是 ObjC 中分派表中的一个条目,它指定要发送给对象的消息)在 BOOPSI 中似乎是每个类的连续整数,而 ObjC 中的整数(它们可以是连续的,但不必是,通常它们不是)是全局唯一的。ObjC 中的选择器对于每个类都是全局的,如果两个类具有相同名称的方法,它们共享同一个选择器。选择器(例如,你通过 @selector 获取它们)是一个结构体,包含方法的名称和(如果编译器知道)类型编码,然后运行时注册该方法并将结构体的内容替换为指向分派表中条目的指针。

因此,这意味着 BOOPSI 中的分派表很小,因为它们只包含该类实现的方法,而在 ObjC 中,它们很大,因为它们需要为存在的每个选择器保留一个插槽。ObjC 中的解决方案是使用稀疏数组(在我的运行时中是 3 级,GNU 使用 4 级,这使得它在没有真正收益的情况下变得明显更慢(2 倍!),因为 2^24 应该足够了)。

因此,为了将它们统一起来,有两个问题

  • 我们如何统一选择器?我们是否应该更改 BOOPSI 以使用稀疏数组?否则,如果 BOOPSI 对象应该能够接受 ObjC 消息,BOOPSI 会遇到麻烦。
  • 我们可以更改 BOOPSI 对象以在索引 0 处指向 ObjC 类吗?这将允许运行时调用它们。

如果你真的对桥接两者感兴趣,我相信可以做到。但这可能需要对 BOOPSI 进行更改。另请参见 Apple 做的 CoreFoundation/Foundation 桥接。

参考文献

[编辑 | 编辑源代码]

NewObject NewObjectA DisposeObject

SetAttrs SetGadgetAttrs GetAttr

MakeClass AddClass RemoveClass FreeClass

AddGadget RefreshGadgets FreeGadget

ObtainGIRPort() ReleaseGIRPort()

DoMathod DoMethodA DoSuperMethod DoSuperMethodA

CoerceMethodA CoerceMethod

SetSuperAttrs

ULONG DoGadgetMethodA(struct Gadget *object, struct Window *win, struct Requester *req, Msg msg);

华夏公益教科书