Last month I illustrated the class heirarchy and discussed each class. This month I will introduce the IApplication class and also discuss the much needed classlib.
while (!done) {
sig = Wait(sig1 | sig2 | sig3)
if (sig & sig1) ...
else if (sig & sig2) ...
else if (sig & sig3) ...
else ....
}
Well, with OOI you will never have to code this again! All you'll have to
do is register an event with IApplication and it will
automatically handle waiting for signals.
To register an event, you just need to instantiate an object of one of these three (3) classes (or an object derived from one):
// Derive a class from IMessagePort
class MyPort : public IMessagePort
{
public:
...
Message * EventHandler(Message *m);
};
// Derive a class from IUserWindow
class MyWindow : public IUserWindow
{
public:
DECLARE_RESPONSE_TABLE;
protected:
void HandleButton1();
void HandleButton2();
void HandleRawkey(int code, int qualifier);
...
};
// Derive a class from IApplication
class MyApp : public IApplication
{
public:
SetupWindow();
protected:
MyPort *port;
};
// The main function
main()
{
MyApp app();
app.Run;
}
// Create the window and port and add the events
void MyApp::SetupWindow()
{
MainWindow = new MyWindow();
AddEvent(*MainWindow);
port = new MyPort;
AddEvent(*port);
...
}
// Create the response table for class MyWindow
DEFINE_RESPONSE_TABLE1(MyWindow, IUserWindow)
ID_GADGETUP(BUTTON_1, HandleButton1)
ID_GADGETUP(BUTTON_2, HandleButton2)
ID_RAWKEY(HandleRawkey)
END_RESPONSE_TABLE
// Create the functions for handling events
void MyWindow::HandleButton1()
{
...
}
void MyWindow::HandleButton2()
{
...
}
void MyWindow::HandleRawkey(int code, int qualifier)
{
....
}
First, we define a class called MyPort, which is derived from
the pre-defined class IMessagePort. This class's main job
is to override IMessagePort's EventHandler() function.
IApplication calls this function when a message arrives at the port.
The MyWindow class is a bit different. If any of you have ever used Borland's OWL for the PC this should look very familiar. Instead of having to define a single EventHandler function, classes derived from IUserWindow need to supply a member function for each event it wishes to handle. In the example, the MyWindow class wants to handle IDCMP_GADGETDOWN messages from BUTTON_1 and BUTTON_2, as well as IDCMP_RAWKEY events.
The tricks to setting all this up are the macros DEFINE_ and DECLARE_RESPONSE_TABLE (the concept here was borrowed form OWL, but my implementation is much different :). Together, these two macros will declare and define two member functions. I haven't actually written these macros yet, but they will look something like this:
#define DECLARE_RESPONSE_TABLE(cls) \
IntuiMessage * EventHandler(IntuiMessage *i);\
IntuiMessage * EventStub(IntuiMessage *i)
#define DEFINE_RESPONSE_TABLE1(cls,base) \
IntuiMessage * cls::EventHandler(IntuiMessage *i) { \
if (NULL != EventStub(i)) return base::EventHandler(i); \
else return NULL; \
} \
IntuiMessage *cls::EventStub(IntuiMessage *i) { \
if (NULL == i) return NULL;
#define ID_GADGETUP(b,f) \
else if (i->Class == IDCMP_GADGETUP && \
((Gadget *)(i->IAddress))->GadgetID == b) {\
f(); \
::ReplyMessage((Message *)i); \
return NULL; \
}
#define ID_RAWKEY(f) \
else if (i->Class == IDCMP_RAWKEY) {\
f(i->Code, i->Qualifier); \
::ReplyMessage((Message *)i); \
return NULL; \
}
#define END_RESPONSE_TABLE \
else return i;\
}
So, when all is said and done, the macros above would pre-process into:
IntuiMessage * MyWindow::EventHandler(IntuiMessage *i) {
if (NULL != EventStub(i)) return IUserWindow::EventHandler(i);
else return NULL;
}
IntuiMessage *MyWindow::EventStub(IntuiMessage *i) {
if (NULL == i) return NULL;
else if (i->Class == IDCMP_GADGETUP &&
((Gadget *)(i->IAddress))->GadgetID == BUTTON_1) {
HandleButton1();
::ReplyMessage((Message *)i);
return NULL;
}
else if (i->Class == IDCMP_GADGETUP &&
((Gadget *)(i->IAddress))->GadgetID == BUTTON_2) {
HandleButton2();
::ReplyMessage((Message *)i);
return NULL;
}
else if (i->Class == IDCMP_RAWKEY) {\
HandleRawkey(i->Code, i->Qualifier); \
::ReplyMessage((Message *)i); \
return NULL;
}
else return i;
}
Pretty groovy, huh?
Whenever you add an event, IApplication adds it to one of three internal linked lists (depending on what type of event you are adding). Also, it ORs the bitmask into a global bitmask that is can use for the exec.library Wait() function:
void IApplication::AddEvent(IMessagePort& port)
{
PortList.Add(&port);
WaitMask |= port.GetSignalMask();
}
Of course, there will also be two other AddEvent() functions:
one for IUserWindow and one for ISignal.
When your main() function executes app.Run(), the IApplication class eventually enters it's EventLoop() function:
void IApplication::EventLoop()
{
BOOL done = FALSE;
IntuiMessage *i;
Message *m;
ULONG sig;
while (!done) {
sig = Wait(WaitMask);
// Walk through window list
WindowList.Rewind();
while (IUserWindow *win = WindowList++) {
if (win->GetSignalMask() & sig) {
i = (IntuiMessage *)GetMessage(win->GetUserPort());
if (NULL != win->EventHandler(i))
ReplyMessage((Message *)i);
sig = 0; // Mark as handled
break;
}
}
// Walk through port list
if (0 != sig) {
PortList.Rewind();
while (IMessagePort *p = PortList++) {
if (p->GetSignalMask() & sig) {
m = GetMessage(p->GetPort());
if (NULL != p->EventHandler(m))
ReplyMessage(m);
sig = 0; // Mark as handled
break;
}
}
}
// Walk through signal list
if (0 != sig) {
SignalList.Rewind();
while (ISignal *s = SignalList++) {
if (s->GetSignalMask() & sig) {
s->EventHandler();
}
}
}
}
}