domingo, 25 de mayo de 2008

El nuevo EventReceiver

Terminamos hoy la parte del gestor de eventos exponiendo al detalle el nuevo EventeReceiver.


Para empezar, aquí está la declaración de la clase, que iría en su respectivo archivo de cabecera:
#include <irrlicht/irrlicht.h>
#include <map>

using namespace irr;
using namespace core;
using namespace scene;
using namespace video;
using namespace io;
using namespace gui;

#define AppCB(var) void (*var)(void*, const SEvent&)

#define parGUIE std::pair< EGUI_EVENT_TYPE, int >
#define mapGUI std::map< parGUIE, AppCB() >
#define parKEYE std::pair< EKEY_CODE, bool >
#define mapKEY std::map< parKEYE, AppCB() >
#define mapMOUSE std::map< EMOUSE_INPUT_EVENT, AppCB() >
#define mapLOG std::map< ELOG_LEVEL, AppCB() >
//#define mapUSER AppCB()

class EventReceiver : public IEventReceiver
{
public:
EventReceiver(void* parent);
virtual ~EventReceiver();
virtual bool OnEvent(const SEvent& event);
bool mAddGuiEvent(EGUI_EVENT_TYPE eventType, int id, AppCB(callback));
bool mAddKeyUpEvent(EKEY_CODE code, AppCB(callback));
bool mAddKeyDownEvent(EKEY_CODE code, AppCB(callback));
bool mAddMouseEvent(EMOUSE_INPUT_EVENT input_event, AppCB(callback));
bool mAddLogEvent(ELOG_LEVEL level, AppCB(callback));

protected:
mapGUI f_guiEvents;
mapKEY f_keyEvents;
mapMOUSE f_mouseEvents;
mapLOG f_logEvents;
void* f_parent;
};


Bien, al principio vemos los includes necesarios y las definiciones, que ya explicamos, de las tablas hash y el puntero a función. Mas abajo, dentro de la declaración de la clase, que hereda de IEventReceiver, hemos añadido, además del onEvent, métodos para facilitar la adición de nuevos eventos, de modo que EventReceiver pueda gestionarlos adecuadamente. Cada uno de estos métodos recibe distintos en función del tipo de evento. Tal es el caso de mAddGuiEvent, que recibe el tipo de evento GUI y el id del control que lo provocó. Lo que sí que tienen en común, es el puntero a función o callback a invocar una vez producido el evento.

En la parte protegida podemos observar las tablas hash que contendrán los eventos y un puntero genérico (void*) al padre de la instancia de la clase. Este último campo lo veremos más abajo.

Veamos ahora la implementación. Primero, el contructor y destructor, nada de especial. Como las tablas hash no han sido creadas de manera dinámica, no es necesario ponelas en ninguno de los dos metodos.
#include "EventReceiver.h"

EventReceiver::EventReceiver(void* parent)
{
f_parent = parent;
}

EventReceiver::~EventReceiver()
{
f_parent = NULL;
}



Ahora veamos los métodos que nos permiten añadir facilmente callbacks a los eventos. Tampoco tiene ningún secreto, dado que tan sólo agregan el callback a la tabla hash pertinente con la clave recibida.
bool
EventReceiver::mAddGuiEvent(EGUI_EVENT_TYPE eventType, int id, AppCB(callback))
{
parGUIE key(eventType,id);
f_guiEvents[key] = callback;

return true;
}

bool
EventReceiver::mAddKeyUpEvent(EKEY_CODE code, AppCB(callback))
{
parKEYE key(code,false);
f_keyEvents[key] = callback;
return true;
}

bool
EventReceiver::mAddKeyDownEvent(EKEY_CODE code, AppCB(callback))
{
parKEYE key(code,true);
f_keyEvents[key] = callback;
return true;
}

bool
EventReceiver::mAddMouseEvent(EMOUSE_INPUT_EVENT input_event, AppCB(callback))
{
f_mouseEvents[input_event] = callback;

return true;
}

bool
EventReceiver::mAddLogEvent(ELOG_LEVEL level, AppCB(callback))
{
f_logEvents[level] = callback;

return true;
}



Y ahora sí, damas y caballeros, aquí viene el metodo onEvent, el cual tiene un switch que, según el evento principal, formará la clave y preguntará a la tabla hash correspondiente si tiene dicha clave, en cuyo caso recuperará el callback y lo invocará. Es aqui cuando hace uso del attributo f_parent. Recordad que el puntero a función debe apuntar a un metodo de clase (estático) y necesitábamos pasarle el objeto a manipular.
bool
EventReceiver::OnEvent(const SEvent& event)
{
switch(event.EventType)
{
case EET_GUI_EVENT:
{
s32 id = event.GUIEvent.Caller->getID();
EGUI_EVENT_TYPE eventType = event.GUIEvent.EventType;
parGUIE key(eventType,id);
if(f_guiEvents.find(key) != f_guiEvents.end())
{
AppCB(callback) = f_guiEvents[key];
callback(f_parent,event);
return true;
}
break;
}
case EET_KEY_INPUT_EVENT:
{
EKEY_CODE code = event.KeyInput.Key;
bool pressed = event.KeyInput.PressedDown;
parKEYE key(code,pressed);
if(f_keyEvents.find(key) != f_keyEvents.end())
{
AppCB(callback) = f_keyEvents[key];
callback(f_parent,event);
return true;
}
break;
}
case EET_MOUSE_INPUT_EVENT:
{
EMOUSE_INPUT_EVENT key = event.MouseInput.Event;
if(f_mouseEvents.find(key) != f_mouseEvents.end())
{
AppCB(callback) = f_mouseEvents[key];
callback(f_parent,event);
return true;
}
break;
}
case EET_LOG_TEXT_EVENT:
{
ELOG_LEVEL key = event.LogEvent.Level;
if(f_logEvents.find(key) != f_logEvents.end())
{
AppCB(callback) = f_logEvents[key];
callback(f_parent,event);
return true;
}
break;
}
case EET_USER_EVENT:
default:
{
break;
}
};
return false;
}



Para terminar, un ejemplo de uso del nuevo EventReceiver. Aquí vemos un cacho de código en el que se inicializa una instancia de tipo EventReceiver y se le agregan callbacks.
    f_eventReceiver = new EventReceiver(this);
f_device->setEventReceiver(f_eventReceiver);

//Inicializacion de controles
f_menu = f_env->addMenu(0, f_genIDs->getNewID());
f_buttonQuit = f_env->addButton(rect<s32>(10,210,100,240), 0, f_genIDs->getNewID(), L"Quit");
f_eventReceiver->mAddGuiEvent(EGET_BUTTON_CLICKED, f_buttonQuit->getID(), MainApplication::mButtonQuit_Click);
f_buttonNewWindow = f_env->addButton(rect<s32>(10,250,100,290), 0, f_genIDs->getNewID(), L"New Window");
f_eventReceiver->mAddGuiEvent(EGET_BUTTON_CLICKED, f_buttonNewWindow->getID(), MainApplication::mButtonNewWindow_Click);
f_buttonFileOpen = f_env->addButton(rect<s32>(10,300,100,340), 0, f_genIDs->getNewID(), L"File Open");
f_eventReceiver->mAddGuiEvent(EGET_BUTTON_CLICKED, f_buttonFileOpen->getID(), MainApplication::mButtonFileOpen_Click);



Y a continuación vemos la implementación de los callbacks añadidos en el codigo anterior. Veis como al principio de cada evento se hace una conversión (cast) al tipo MainApplication, que es el que hace de padre (el valor del atributo f_parent) de f_eventReceiver.
void
MainApplication::mButtonQuit_Click(void *o, const SEvent & event)
{
MainApplication* me = (MainApplication*)o;
me->f_device->closeDevice();
// me->f_ImageLogo->remove();
}

void
MainApplication::mButtonNewWindow_Click(void *o, const SEvent & event)
{
MainApplication* me = (MainApplication*)o;
me->f_listBoxLogger->addItem(L"Window created");

IGUIWindow* window = me->f_env->addWindow(
rect<s32>(100, 100, 300, 200),
false, // modal?
L"Test window");
me->f_env->addStaticText(L"SubVentanica",
rect<s32>(35,35,140,50),
true, // border?,
false, // wordwrap?
window);
}

void
MainApplication::mButtonFileOpen_Click(void *o, const SEvent & event)
{
MainApplication* me = (MainApplication*)o;
me->f_listBoxLogger->addItem(L"File open");
me->f_env->addFileOpenDialog(L"Please choose a file.");
}


Bien, mediante los punteros a función hemos conseguido hacer mas mantenible el objeto EventReceiver necesario para gestionar los eventos con Irrlicht. En la proxima entrega de esta serie, empezaremos a pincelar la estructura de las ventanas en Irrlicht.

No hay comentarios: