Quite a bit of stuff
[dasher.git] / Src / Gtk2 / DasherControl.cpp
blobbdc8f11a657902dd07a2cd95984da07f4ef96732
1 #include "../Common/Common.h"
2 #include "../../config.h"
4 #include <iostream>
5 #include "DasherControl.h"
6 #include "Timer.h"
7 #include "../DasherCore/Event.h"
8 #include "../DasherCore/WrapperFactory.h"
10 #include <fcntl.h>
12 #include <gtk/gtk.h>
13 #include <gdk/gdk.h>
14 #include <gdk/gdkkeysyms.h>
15 #include <sys/stat.h>
16 using namespace std;
18 // 'Private' methods (only used in this file)
19 extern "C" gint key_release_event(GtkWidget *widget, GdkEventKey *event, gpointer user_data);
20 extern "C" gboolean button_press_event(GtkWidget *widget, GdkEventButton *event, gpointer data);
21 extern "C" void realize_canvas(GtkWidget *widget, gpointer user_data);
22 extern "C" gint canvas_configure_event(GtkWidget *widget, GdkEventConfigure *event, gpointer data);
23 extern "C" gint key_press_event(GtkWidget *widget, GdkEventKey *event, gpointer data);
24 extern "C" void canvas_destroy_event(GtkWidget *pWidget, gpointer pUserData);
25 extern "C" gboolean canvas_focus_event(GtkWidget *widget, GdkEventFocus *event, gpointer data);
26 extern "C" gint canvas_expose_event(GtkWidget *widget, GdkEventExpose *event, gpointer data);
28 static bool g_iTimeoutID = 0;
30 // CDasherControl class definitions
31 CDasherControl::CDasherControl(GtkVBox *pVBox, GtkDasherControl *pDasherControl) {
32 m_pPangoCache = NULL;
33 m_pScreen = NULL;
35 m_pDasherControl = pDasherControl;
36 m_pVBox = GTK_WIDGET(pVBox);
38 Realize();
40 // m_pKeyboardHelper = new CKeyboardHelper(this);
41 // m_pKeyboardHelper->Grab(GetBoolParameter(BP_GLOBAL_KEYBOARD));
44 void CDasherControl::CreateLocalFactories() {
45 RegisterFactory(new CWrapperFactory(m_pEventHandler, m_pSettingsStore, new CDasherMouseInput(m_pEventHandler, m_pSettingsStore)));
46 RegisterFactory(new CWrapperFactory(m_pEventHandler, m_pSettingsStore, new CSocketInput(m_pEventHandler, m_pSettingsStore)));
47 RegisterFactory(new CWrapperFactory(m_pEventHandler, m_pSettingsStore, new CDasher1DMouseInput(m_pEventHandler, m_pSettingsStore)));
49 #ifdef JOYSTICK
50 RegisterFactory(new CWrapperFactory(m_pEventHandler, m_pSettingsStore, new CDasherJoystickInput(m_pEventHandler, m_pSettingsStore, this)));
51 RegisterFactory(new CWrapperFactory(m_pEventHandler, m_pSettingsStore, new CDasherJoystickInputDiscrete(m_pEventHandler, m_pSettingsStore, this)));
52 #endif
54 // Create locally cached copies of the mouse input objects, as we
55 // need to pass coordinates to them from the timer callback
57 m_pMouseInput = (CDasherMouseInput *)GetModule(0);
58 m_pMouseInput->Ref();
60 m_p1DMouseInput = (CDasher1DMouseInput *)GetModule(2);
61 m_p1DMouseInput->Ref();
65 void CDasherControl::SetupUI() {
66 m_pCanvas = gtk_drawing_area_new();
67 GTK_WIDGET_SET_FLAGS(m_pCanvas, GTK_CAN_FOCUS);
68 gtk_widget_set_double_buffered(m_pCanvas, false);
70 GtkWidget *pFrame = gtk_frame_new(NULL);
71 gtk_frame_set_shadow_type(GTK_FRAME(pFrame), GTK_SHADOW_IN);
72 gtk_container_add(GTK_CONTAINER(pFrame), m_pCanvas);
74 gtk_box_pack_start(GTK_BOX(m_pVBox), pFrame, TRUE, TRUE, 0);
75 gtk_widget_show_all(GTK_WIDGET(m_pVBox));
77 // Connect callbacks - note that we need to implement the callbacks
78 // as "C" style functions and pass this as user data so they can
79 // call the object
81 g_signal_connect(m_pCanvas, "button_press_event", G_CALLBACK(button_press_event), this);
82 g_signal_connect(m_pCanvas, "button_release_event", G_CALLBACK(button_press_event), this);
83 g_signal_connect_after(m_pCanvas, "realize", G_CALLBACK(realize_canvas), this);
84 g_signal_connect(m_pCanvas, "configure_event", G_CALLBACK(canvas_configure_event), this);
85 g_signal_connect(m_pCanvas, "destroy", G_CALLBACK(canvas_destroy_event), this);
87 g_signal_connect(m_pCanvas, "key-release-event", G_CALLBACK(key_release_event), this);
88 g_signal_connect(m_pCanvas, "key_press_event", G_CALLBACK(key_press_event), this);
90 g_signal_connect(m_pCanvas, "focus_in_event", G_CALLBACK(canvas_focus_event), this);
91 g_signal_connect(m_pCanvas, "expose_event", G_CALLBACK(canvas_expose_event), this);
93 // Create the Pango cache
95 // TODO: Use system defaults?
96 if(GetStringParameter(SP_DASHER_FONT) == "")
97 SetStringParameter(SP_DASHER_FONT, "Sans 10");
99 m_pPangoCache = new CPangoCache(GetStringParameter(SP_DASHER_FONT));
104 void CDasherControl::SetupPaths() {
105 char *home_dir;
106 char *user_data_dir;
107 char *system_data_dir;
109 home_dir = getenv("HOME");
110 user_data_dir = new char[strlen(home_dir) + 10];
111 sprintf(user_data_dir, "%s/.dasher/", home_dir);
113 mkdir(user_data_dir, S_IRUSR | S_IWUSR | S_IXUSR | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH);
115 // PROGDATA is provided by the makefile
116 system_data_dir = PROGDATA "/";
118 SetStringParameter(SP_SYSTEM_LOC, system_data_dir);
119 SetStringParameter(SP_USER_LOC, user_data_dir);
122 void CDasherControl::CreateSettingsStore() {
123 m_pSettingsStore = new CGnomeSettingsStore(m_pEventHandler);
126 void CDasherControl::ScanAlphabetFiles(std::vector<std::string> &vFileList) {
127 GDir *directory;
128 G_CONST_RETURN gchar *filename;
129 GPatternSpec *alphabetglob;
130 alphabetglob = g_pattern_spec_new("alphabet*xml");
132 directory = g_dir_open(GetStringParameter(SP_SYSTEM_LOC).c_str(), 0, NULL);
134 if(directory) {
135 while((filename = g_dir_read_name(directory))) {
136 if(g_pattern_match_string(alphabetglob, filename))
137 vFileList.push_back(filename);
139 g_dir_close(directory);
142 directory = g_dir_open(GetStringParameter(SP_USER_LOC).c_str(), 0, NULL);
144 if(directory) {
145 while((filename = g_dir_read_name(directory))) {
146 if(g_pattern_match_string(alphabetglob, filename))
147 vFileList.push_back(filename);
149 g_dir_close(directory);
152 g_pattern_spec_free(alphabetglob);
155 void CDasherControl::ScanColourFiles(std::vector<std::string> &vFileList) {
156 GDir *directory;
157 G_CONST_RETURN gchar *filename;
159 GPatternSpec *colourglob;
160 colourglob = g_pattern_spec_new("colour*xml");
162 directory = g_dir_open(GetStringParameter(SP_SYSTEM_LOC).c_str(), 0, NULL);
164 if(directory) {
165 while((filename = g_dir_read_name(directory))) {
166 if(g_pattern_match_string(colourglob, filename))
167 vFileList.push_back(filename);
169 g_dir_close(directory);
172 directory = g_dir_open(GetStringParameter(SP_USER_LOC).c_str(), 0, NULL);
174 if(directory) {
175 while((filename = g_dir_read_name(directory))) {
176 if(g_pattern_match_string(colourglob, filename))
177 vFileList.push_back(filename);
179 g_dir_close(directory);
182 g_pattern_spec_free(colourglob);
185 CDasherControl::~CDasherControl() {
186 if(m_pMouseInput) {
187 m_pMouseInput->Unref();
188 m_pMouseInput = NULL;
191 if(m_p1DMouseInput) {
192 m_p1DMouseInput->Unref();
193 m_p1DMouseInput = NULL;
196 if(m_pPangoCache) {
197 delete m_pPangoCache;
198 m_pPangoCache = NULL;
201 // if(m_pKeyboardHelper) {
202 // delete m_pKeyboardHelper;
203 // m_pKeyboardHelper = 0;
204 // }
207 bool CDasherControl::FocusEvent(GtkWidget *pWidget, GdkEventFocus *pEvent) {
208 if((pEvent->type == GDK_FOCUS_CHANGE) && (pEvent->in)) {
209 GdkEventFocus *focusEvent = (GdkEventFocus *) g_malloc(sizeof(GdkEventFocus));
210 gboolean *returnType;
212 focusEvent->type = GDK_FOCUS_CHANGE;
213 focusEvent->window = (GdkWindow *) m_pDasherControl;
214 focusEvent->send_event = FALSE;
215 focusEvent->in = TRUE;
217 g_signal_emit_by_name(GTK_OBJECT(m_pDasherControl), "focus_in_event", GTK_WIDGET(m_pDasherControl), focusEvent, NULL, &returnType);
219 return true;
222 void CDasherControl::SetFocus() {
223 gtk_widget_grab_focus(m_pCanvas);
226 GArray *CDasherControl::GetAllowedValues(int iParameter) {
227 // Glib version of the STL based core function
229 GArray *pRetVal(g_array_new(false, false, sizeof(gchar *)));
231 std::vector < std::string > vList;
232 GetPermittedValues(iParameter, vList);
234 for(std::vector < std::string >::iterator it(vList.begin()); it != vList.end(); ++it) {
235 // For internal glib reasons we need to make a variable and then
236 // pass - we can't use the iterator directly
237 const char *pTemp(it->c_str());
238 char *pTempNew = new char[strlen(pTemp) + 1];
239 strcpy(pTempNew, pTemp);
240 g_array_append_val(pRetVal, pTempNew);
243 return pRetVal;
246 void CDasherControl::RealizeCanvas(GtkWidget *pWidget) {
247 // TODO: Pointless function - call directly from C callback.
248 OnUIRealised();
251 void CDasherControl::StartTimer() {
252 // Start the timer loops as everything is set up
253 // Aim for 20 frames per second
255 if(g_iTimeoutID == 0) {
256 g_iTimeoutID = g_timeout_add_full(G_PRIORITY_DEFAULT_IDLE, 50, timer_callback, this, NULL);
257 // TODO: Reimplement this (or at least reimplement some kind of status reporting)
258 //g_timeout_add_full(G_PRIORITY_DEFAULT_IDLE, 5000, long_timer_callback, this, NULL);
262 void CDasherControl::ShutdownTimer() {
263 // TODO: Figure out how to implement this - at the moment it's done
264 // through a return value from the timer callback, but it would be
265 // nicer to prevent any further calls as soon as the shutdown signal
266 // has been receieved.
269 int CDasherControl::CanvasConfigureEvent() {
271 if(m_pScreen != NULL)
272 delete m_pScreen;
274 m_pScreen = new CCanvas(m_pCanvas, m_pPangoCache);
275 ChangeScreen(m_pScreen);
277 return 0;
280 void CDasherControl::ExternalEventHandler(Dasher::CEvent *pEvent) {
281 // Convert events coming from the core to Glib signals.
283 if(pEvent->m_iEventType == EV_PARAM_NOTIFY) {
284 Dasher::CParameterNotificationEvent * pEvt(static_cast < Dasher::CParameterNotificationEvent * >(pEvent));
285 HandleParameterNotification(pEvt->m_iParameter);
286 g_signal_emit_by_name(GTK_OBJECT(m_pDasherControl), "dasher_changed", pEvt->m_iParameter);
288 else if(pEvent->m_iEventType == EV_EDIT) {
289 CEditEvent *pEditEvent(static_cast < CEditEvent * >(pEvent));
291 if(pEditEvent->m_iEditType == 1) {
292 // Insert event
293 g_signal_emit_by_name(GTK_OBJECT(m_pDasherControl), "dasher_edit_insert", pEditEvent->m_sText.c_str(), pEditEvent->m_iOffset);
295 else if(pEditEvent->m_iEditType == 2) {
296 // Delete event
297 g_signal_emit_by_name(GTK_OBJECT(m_pDasherControl), "dasher_edit_delete", pEditEvent->m_sText.c_str(), pEditEvent->m_iOffset);
299 else if(pEditEvent->m_iEditType == 10) {
300 g_signal_emit_by_name(GTK_OBJECT(m_pDasherControl), "dasher_edit_convert");
302 else if(pEditEvent->m_iEditType == 11) {
303 g_signal_emit_by_name(GTK_OBJECT(m_pDasherControl), "dasher_edit_protect");
306 else if(pEvent->m_iEventType == EV_EDIT_CONTEXT) {
307 CEditContextEvent *pEditContextEvent(static_cast < CEditContextEvent * >(pEvent));
308 g_signal_emit_by_name(GTK_OBJECT(m_pDasherControl), "dasher_context_request", pEditContextEvent->m_iOffset, pEditContextEvent->m_iLength);
310 else if(pEvent->m_iEventType == EV_START) {
311 g_signal_emit_by_name(GTK_OBJECT(m_pDasherControl), "dasher_start");
313 else if(pEvent->m_iEventType == EV_STOP) {
314 g_signal_emit_by_name(GTK_OBJECT(m_pDasherControl), "dasher_stop");
316 else if(pEvent->m_iEventType == EV_CONTROL) {
317 CControlEvent *pControlEvent(static_cast < CControlEvent * >(pEvent));
318 g_signal_emit_by_name(GTK_OBJECT(m_pDasherControl), "dasher_control", pControlEvent->m_iID);
320 else if(pEvent->m_iEventType == EV_LOCK) {
321 CLockEvent *pLockEvent(static_cast<CLockEvent *>(pEvent));
322 DasherLockInfo sInfo;
323 sInfo.szMessage = pLockEvent->m_strMessage.c_str();
324 sInfo.bLock = pLockEvent->m_bLock;
325 sInfo.iPercent = pLockEvent->m_iPercent;
327 g_signal_emit_by_name(GTK_OBJECT(m_pDasherControl), "dasher_lock_info", &sInfo);
329 else if(pEvent->m_iEventType == EV_MESSAGE) {
330 CMessageEvent *pMessageEvent(static_cast<CMessageEvent *>(pEvent));
331 DasherMessageInfo sInfo;
332 sInfo.szMessage = pMessageEvent->m_strMessage.c_str();
333 sInfo.iID = pMessageEvent->m_iID;
334 sInfo.iType = pMessageEvent->m_iType;
336 g_signal_emit_by_name(GTK_OBJECT(m_pDasherControl), "dasher_message", &sInfo);
338 else if(pEvent->m_iEventType == EV_COMMAND) {
339 CCommandEvent *pCommandEvent(static_cast<CCommandEvent *>(pEvent));
340 g_signal_emit_by_name(GTK_OBJECT(m_pDasherControl), "dasher_command", pCommandEvent->m_strCommand.c_str());
344 void CDasherControl::WriteTrainFile(const std::string &strNewText) {
345 if(strNewText.length() == 0)
346 return;
348 std::string strFilename(GetStringParameter(SP_USER_LOC) + GetStringParameter(SP_TRAIN_FILE));
350 int fd=open(strFilename.c_str(),O_CREAT|O_WRONLY|O_APPEND,S_IRUSR|S_IWUSR);
351 write(fd,strNewText.c_str(),strNewText.length());
352 close(fd);
355 // TODO: Sort these methods out
356 void CDasherControl::ExternalKeyDown(int iKeyVal) {
357 // if(m_pKeyboardHelper) {
358 // int iButtonID(m_pKeyboardHelper->ConvertKeycode(iKeyVal));
360 // if(iButtonID != -1)
361 // KeyDown(get_time(), iButtonID);
362 // }
363 KeyDown(get_time(), iKeyVal);
366 void CDasherControl::ExternalKeyUp(int iKeyVal) {
367 // if(m_pKeyboardHelper) {
368 // int iButtonID(m_pKeyboardHelper->ConvertKeycode(iKeyVal));
370 // if(iButtonID != -1)
371 // KeyUp(get_time(), iButtonID);
372 // }
373 KeyUp(get_time(), iKeyVal);
376 void CDasherControl::HandleParameterNotification(int iParameter) {
377 switch(iParameter) {
378 case SP_DASHER_FONT:
379 if(m_pPangoCache) {
380 m_pPangoCache->ChangeFont(GetStringParameter(SP_DASHER_FONT));
381 ScheduleRedraw();
383 break;
384 case BP_GLOBAL_KEYBOARD:
385 // TODO: reimplement
386 // if(m_pKeyboardHelper)
387 // m_pKeyboardHelper->Grab(GetBoolParameter(BP_GLOBAL_KEYBOARD));
388 break;
392 int CDasherControl::TimerEvent() {
393 int x, y;
395 gdk_window_get_pointer(m_pCanvas->window, &x, &y, NULL);
396 m_pMouseInput->SetCoordinates(x, y);
398 gdk_window_get_pointer(gdk_get_default_root_window(), &x, &y, NULL);
400 int iRootWidth;
401 int iRootHeight;
403 gdk_drawable_get_size(gdk_get_default_root_window(), &iRootWidth, &iRootHeight);
405 if(GetLongParameter(LP_YSCALE) < 10)
406 SetLongParameter(LP_YSCALE, 10);
408 y = (y - iRootHeight / 2);
410 m_p1DMouseInput->SetCoordinates(y, GetLongParameter(LP_YSCALE));
412 NewFrame(get_time(), false);
414 // Update our UserLog object about the current mouse position
415 CUserLogBase* pUserLog = GetUserLogPtr();
416 if (pUserLog != NULL) {
417 // We want current canvas and window coordinates so normalization
418 // is done properly with respect to the canvas.
419 GdkRectangle sWindowRect;
420 GdkRectangle sCanvasRect;
422 gdk_window_get_frame_extents(m_pCanvas->window, &sWindowRect);
424 pUserLog->AddWindowSize(sWindowRect.y,
425 sWindowRect.x,
426 sWindowRect.y + sWindowRect.height,
427 sWindowRect.x + sWindowRect.width);
429 if (m_pScreen != NULL) {
430 if (m_pScreen->GetCanvasSize(&sCanvasRect))
431 pUserLog->AddCanvasSize(sCanvasRect.y,
432 sCanvasRect.x,
433 sCanvasRect.y + sCanvasRect.height,
434 sCanvasRect.x + sCanvasRect.width);
437 int iMouseX = 0;
438 int iMouseY = 0;
439 gdk_window_get_pointer(NULL, &iMouseX, &iMouseY, NULL);
441 pUserLog->AddMouseLocationNormalized(iMouseX, iMouseY, true, GetNats());
444 return 1;
446 // See CVS for code which used to be here
449 int CDasherControl::LongTimerEvent() {
450 // std::cout << "Framerate: " << GetFramerate() << std::endl;
451 // std::cout << "Render count: " << GetRenderCount() << std::endl;
452 return 1;
455 gboolean CDasherControl::ExposeEvent() {
456 NewFrame(get_time(), true);
457 return 0;
460 gboolean CDasherControl::ButtonPressEvent(GdkEventButton *event) {
462 // Take the focus if we click on the canvas
464 // GdkEventFocus *focusEvent = (GdkEventFocus *) g_malloc(sizeof(GdkEventFocus));
465 // gboolean *returnType;
467 // focusEvent->type = GDK_FOCUS_CHANGE;
468 // focusEvent->window = (GdkWindow *) m_pCanvas;
469 // focusEvent->send_event = FALSE;
470 // focusEvent->in = TRUE;
472 // gtk_widget_grab_focus(GTK_WIDGET(m_pCanvas));
473 // g_signal_emit_by_name(GTK_OBJECT(m_pCanvas), "focus_in_event", GTK_WIDGET(m_pCanvas), focusEvent, NULL, &returnType);
475 // No - don't take the focus - give it to the text area instead
477 if(event->type == GDK_BUTTON_PRESS)
478 HandleClickDown(get_time(), (int)event->x, (int)event->y);
479 else if(event->type == GDK_BUTTON_RELEASE)
480 HandleClickUp(get_time(), (int)event->x, (int)event->y);
482 return false;
485 gint CDasherControl::KeyReleaseEvent(GdkEventKey *event) {
486 // TODO: This is seriously flawed - the semantics of of X11 Keyboard
487 // events mean the there's no guarantee that key up/down events will
488 // be received in pairs.
490 if((event->keyval == GDK_Shift_L) || (event->keyval == GDK_Shift_R)) {
491 // if(event->state & GDK_CONTROL_MASK)
492 // SetLongParameter(LP_BOOSTFACTOR, 25);
493 // else
494 // SetLongParameter(LP_BOOSTFACTOR, 100);
496 else if((event->keyval == GDK_Control_L) || (event->keyval == GDK_Control_R)) {
497 // if(event->state & GDK_SHIFT_MASK)
498 // SetLongParameter(LP_BOOSTFACTOR, 175);
499 // else
500 // SetLongParameter(LP_BOOSTFACTOR, 100);
502 else {
503 // if(m_pKeyboardHelper) {
504 // int iKeyVal(m_pKeyboardHelper->ConvertKeycode(event->keyval));
506 // if(iKeyVal != -1)
507 // KeyUp(get_time(), iKeyVal);
508 // }
511 return 0;
514 gint CDasherControl::KeyPressEvent(GdkEventKey *event) {
515 // if((event->keyval == GDK_Shift_L) || (event->keyval == GDK_Shift_R))
516 // SetLongParameter(LP_BOOSTFACTOR, 175);
517 // else if((event->keyval == GDK_Control_L) || (event->keyval == GDK_Control_R))
518 // SetLongParameter(LP_BOOSTFACTOR, 25);
519 // else {
520 // if(m_pKeyboardHelper) {
521 // int iKeyVal(m_pKeyboardHelper->ConvertKeycode(event->keyval));
523 // if(iKeyVal != -1)
524 // KeyDown(get_time(), iKeyVal);
525 // }
526 // }
527 return 0;
530 void CDasherControl::CanvasDestroyEvent() {
531 // Delete the screen
533 if(m_pScreen != NULL) {
534 delete m_pScreen;
535 m_pScreen = NULL;
539 // Tell the logging object that a new user trial is starting.
540 void CDasherControl::UserLogNewTrial()
542 CUserLogBase* pUserLog = GetUserLogPtr();
543 if (pUserLog != NULL) {
544 pUserLog->NewTrial();
548 int CDasherControl::GetFileSize(const std::string &strFileName) {
549 struct stat sStatInfo;
551 if(!stat(strFileName.c_str(), &sStatInfo))
552 return sStatInfo.st_size;
553 else
554 return 0;
557 // "C" style callbacks - these are here just because it's not possible
558 // (or at least not easy) to connect a callback directly to a C++
559 // method, so we pass a pointer to th object in the user_data field
560 // and use a wrapper function. Please do not put any functional code
561 // here.
563 extern "C" void realize_canvas(GtkWidget *widget, gpointer user_data) {
564 static_cast < CDasherControl * >(user_data)->RealizeCanvas(widget);
568 extern "C" gboolean button_press_event(GtkWidget *widget, GdkEventButton *event, gpointer data) {
569 return static_cast < CDasherControl * >(data)->ButtonPressEvent(event);
572 extern "C" gint key_press_event(GtkWidget *widget, GdkEventKey *event, gpointer data) {
573 return static_cast < CDasherControl * >(data)->KeyPressEvent(event);
576 extern "C" gint canvas_configure_event(GtkWidget *widget, GdkEventConfigure *event, gpointer data) {
577 return static_cast < CDasherControl * >(data)->CanvasConfigureEvent();
580 extern "C" void canvas_destroy_event(GtkWidget *pWidget, gpointer pUserData) {
581 static_cast<CDasherControl*>(pUserData)->CanvasDestroyEvent();
584 extern "C" gint key_release_event(GtkWidget *pWidget, GdkEventKey *event, gpointer pUserData) {
585 return static_cast<CDasherControl*>(pUserData)->KeyReleaseEvent(event);
588 extern "C" gboolean canvas_focus_event(GtkWidget *widget, GdkEventFocus *event, gpointer data) {
589 return static_cast < CDasherControl * >(data)->FocusEvent(widget, event);
592 extern "C" gint canvas_expose_event(GtkWidget *widget, GdkEventExpose *event, gpointer data) {
593 return ((CDasherControl*)data)->ExposeEvent();