Removing debug output Fixing conversion manager build issues make it
[dasher.git] / Src / Gtk2 / DasherControl.cpp
blobc9434a862e0a299ca806893b1c5719b9e4f93217
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_bHaveTimer = false;
30 // CDasherControl class definitions
31 CDasherControl::CDasherControl(GtkVBox *pVBox, GtkDasherControl *pDasherControl) {
32 m_pDasherControl = pDasherControl;
33 m_pVBox = GTK_WIDGET(pVBox);
35 Realize();
37 m_iComboCount = 0;
39 // Create input device objects
40 // (We create the SocketInput object now even if socket input is not enabled, because
41 // we are not allowed to create it in response to a parameter update event later, because
42 // that would mean registering a new event listener during the processing of an event.
44 RegisterFactory(new CWrapperFactory(m_pEventHandler, m_pSettingsStore, new CDasherMouseInput(m_pEventHandler, m_pSettingsStore)));
45 RegisterFactory(new CWrapperFactory(m_pEventHandler, m_pSettingsStore, new CSocketInput(m_pEventHandler, m_pSettingsStore)));
47 m_pKeyboardHelper = new CKeyboardHelper(this);
48 m_pKeyboardHelper->Grab(GetBoolParameter(BP_GLOBAL_KEYBOARD));
50 CreateInput();
52 // m_pSocketInput = (CSocketInput *)GetModule(1);
53 // m_pSocketInput->Ref();
55 m_pMouseInput = (CDasherMouseInput *)GetModule(0);
56 m_pMouseInput->Ref();
58 // Create a pango cache
60 m_pPangoCache = 0;
62 // TODO: Use system defaults?
63 if(GetStringParameter(SP_DASHER_FONT) == "")
64 SetStringParameter(SP_DASHER_FONT, "Sans 10");
67 m_pPangoCache = new CPangoCache(GetStringParameter(SP_DASHER_FONT));
69 // Don't create the screen until we've been realised.
71 m_pScreen = NULL;
75 void CDasherControl::SetupUI() {
76 m_pCanvas = gtk_drawing_area_new();
77 GTK_WIDGET_SET_FLAGS(m_pCanvas, GTK_CAN_FOCUS);
78 gtk_widget_set_double_buffered(m_pCanvas, false);
80 GtkWidget *pFrame = gtk_frame_new(NULL);
81 gtk_frame_set_shadow_type(GTK_FRAME(pFrame), GTK_SHADOW_IN);
83 // m_pSpeedFrame = gtk_frame_new("Speed:");
85 // m_pSpeedHScale = gtk_hscale_new_with_range(0.1, 8.0, 0.1);
86 // gtk_scale_set_digits( GTK_SCALE(m_pSpeedHScale), 1 );
88 // gtk_container_add(GTK_CONTAINER(m_pSpeedFrame), m_pSpeedHScale);
89 gtk_container_add(GTK_CONTAINER(pFrame), m_pCanvas);
92 // m_pStatusBar = gtk_hbox_new(false, 2);
94 // m_pSpin = gtk_spin_button_new_with_range(0.1, 8.0, 0.1);
95 // m_pCombo = gtk_combo_box_new_text();
97 // gtk_widget_set_size_request(m_pCombo, 256, -1);
99 // m_pStatusLabel = gtk_label_new("Characters/min: --");
100 // gtk_label_set_justify(GTK_LABEL(m_pStatusLabel), GTK_JUSTIFY_RIGHT);
102 // gtk_box_pack_start(GTK_BOX(m_pStatusBar), gtk_label_new("Speed:"), 0, 0, 0);
103 // // gtk_box_pack_start(GTK_BOX(m_pStatusBar), m_pSpin, 0, 0, 0);
104 // // gtk_box_pack_start(GTK_BOX(m_pStatusBar), m_pCombo, 0, 0, 0);
105 // // gtk_box_pack_start(GTK_BOX(m_pStatusBar), m_pStatusLabel, TRUE, TRUE, 0);
107 gtk_box_pack_start(GTK_BOX(m_pVBox), pFrame, TRUE, TRUE, 0);
108 // // gtk_box_pack_start(GTK_BOX(m_pVBox), m_pSpeedFrame, FALSE, FALSE, 0);
109 // gtk_box_pack_start(GTK_BOX(m_pVBox), m_pStatusBar, FALSE, FALSE, 0);
111 gtk_widget_show_all(GTK_WIDGET(m_pVBox));
113 // if(!GetBoolParameter(BP_SHOW_SLIDER))
114 // gtk_widget_hide(m_pStatusBar);
116 // Connect callbacks - note that we need to implement the callbacks
117 // as "C" style functions and pass this as user data so they can
118 // call the object
120 g_signal_connect(m_pCanvas, "button_press_event", G_CALLBACK(button_press_event), this);
121 g_signal_connect(m_pCanvas, "button_release_event", G_CALLBACK(button_press_event), this);
122 g_signal_connect_after(m_pCanvas, "realize", G_CALLBACK(realize_canvas), this);
123 g_signal_connect(m_pCanvas, "configure_event", G_CALLBACK(canvas_configure_event), this);
124 g_signal_connect(m_pCanvas, "destroy", G_CALLBACK(canvas_destroy_event), this);
126 // We'll use the same call back for keyboard events from the canvas
127 // and slider - maybe this isn't the right thing to do long term
129 g_signal_connect(m_pCanvas, "key-release-event", G_CALLBACK(key_release_event), this);
130 g_signal_connect(m_pCanvas, "key_press_event", G_CALLBACK(key_press_event), this);
132 g_signal_connect(m_pCanvas, "focus_in_event", G_CALLBACK(canvas_focus_event), this);
133 g_signal_connect(m_pCanvas, "expose_event", G_CALLBACK(canvas_expose_event), this);
137 void CDasherControl::SetupPaths() {
138 char *home_dir;
139 char *user_data_dir;
140 char *system_data_dir;
142 home_dir = getenv("HOME");
143 user_data_dir = new char[strlen(home_dir) + 10];
144 sprintf(user_data_dir, "%s/.dasher/", home_dir);
146 mkdir(user_data_dir, S_IRUSR | S_IWUSR | S_IXUSR | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH);
148 // PROGDATA is provided by the makefile
149 system_data_dir = PROGDATA "/";
151 SetStringParameter(SP_SYSTEM_LOC, system_data_dir);
152 SetStringParameter(SP_USER_LOC, user_data_dir);
155 void CDasherControl::CreateSettingsStore() {
156 m_pSettingsStore = new CGnomeSettingsStore(m_pEventHandler);
159 void CDasherControl::ScanAlphabetFiles(std::vector<std::string> &vFileList) {
160 GDir *directory;
161 G_CONST_RETURN gchar *filename;
162 GPatternSpec *alphabetglob;
163 alphabetglob = g_pattern_spec_new("alphabet*xml");
165 directory = g_dir_open(GetStringParameter(SP_SYSTEM_LOC).c_str(), 0, NULL);
167 if(directory) {
168 while((filename = g_dir_read_name(directory))) {
169 if(alphabet_filter(filename, alphabetglob)) {
170 vFileList.push_back(filename);
173 g_dir_close(directory);
176 directory = g_dir_open(GetStringParameter(SP_USER_LOC).c_str(), 0, NULL);
178 if(directory) {
179 while((filename = g_dir_read_name(directory))) {
180 if(alphabet_filter(filename, alphabetglob)) {
181 vFileList.push_back(filename);
184 g_dir_close(directory);
186 // FIXME - need to delete glob?
189 void CDasherControl::ScanColourFiles(std::vector<std::string> &vFileList) {
190 GDir *directory;
191 G_CONST_RETURN gchar *filename;
193 GPatternSpec *colourglob;
194 colourglob = g_pattern_spec_new("colour*xml");
196 directory = g_dir_open(GetStringParameter(SP_SYSTEM_LOC).c_str(), 0, NULL);
198 if(directory) {
199 while((filename = g_dir_read_name(directory))) {
200 if(colour_filter(filename, colourglob)) {
201 vFileList.push_back(filename);
204 g_dir_close(directory);
207 directory = g_dir_open(GetStringParameter(SP_USER_LOC).c_str(), 0, NULL);
209 if(directory) {
210 while((filename = g_dir_read_name(directory))) {
211 if(colour_filter(filename, colourglob)) {
212 vFileList.push_back(filename);
215 g_dir_close(directory);
218 // FIXME - need to delete glob?
221 CDasherControl::~CDasherControl() {
222 WriteTrainFileFull();
224 // Delete the input devices
226 if(m_pMouseInput != NULL) {
227 m_pMouseInput->Unref();
228 m_pMouseInput = NULL;
231 if(m_pPangoCache != NULL) {
232 delete m_pPangoCache;
233 m_pPangoCache = NULL;
236 if(m_pKeyboardHelper) {
237 delete m_pKeyboardHelper;
238 m_pKeyboardHelper = 0;
242 bool CDasherControl::FocusEvent(GtkWidget *pWidget, GdkEventFocus *pEvent) {
243 if((pEvent->type == GDK_FOCUS_CHANGE) && (pEvent->in)) {
244 GdkEventFocus *focusEvent = (GdkEventFocus *) g_malloc(sizeof(GdkEventFocus));
245 gboolean *returnType;
247 focusEvent->type = GDK_FOCUS_CHANGE;
248 focusEvent->window = (GdkWindow *) m_pDasherControl;
249 focusEvent->send_event = FALSE;
250 focusEvent->in = TRUE;
252 g_signal_emit_by_name(GTK_OBJECT(m_pDasherControl), "focus_in_event", GTK_WIDGET(m_pDasherControl), focusEvent, NULL, &returnType);
254 return true;
257 void CDasherControl::SetFocus() {
258 gtk_widget_grab_focus(m_pCanvas);
261 GArray *CDasherControl::GetAllowedValues(int iParameter) {
263 // FIXME - this should really be implemented in DasherInterface in
264 // place of GetAlphabets and GetColours
266 GArray *pRetVal(g_array_new(false, false, sizeof(gchar *)));
268 std::vector < std::string > vList;
269 GetPermittedValues(iParameter, vList);
271 for(std::vector < std::string >::iterator it(vList.begin()); it != vList.end(); ++it) {
272 // For internal glib reasons we need to make a variable and then
273 // pass - we can't use the iterator directly
274 const char *pTemp(it->c_str());
275 char *pTempNew = new char[strlen(pTemp) + 1];
276 strcpy(pTempNew, pTemp);
277 g_array_append_val(pRetVal, pTempNew);
280 return pRetVal;
283 void CDasherControl::RealizeCanvas(GtkWidget *pWidget) {
284 // Start the timer loops as everything is set up
285 // Aim for 20 frames per second
286 if(!g_bHaveTimer) {
287 g_timeout_add_full(G_PRIORITY_DEFAULT_IDLE, 50, timer_callback, this, NULL);
288 g_timeout_add_full(G_PRIORITY_DEFAULT_IDLE, 5000, long_timer_callback, this, NULL);
289 g_bHaveTimer = true;
293 int CDasherControl::CanvasConfigureEvent() {
295 if(m_pScreen != NULL)
296 delete m_pScreen;
298 m_pScreen = new CCanvas(m_pCanvas, m_pPangoCache);
299 ChangeScreen(m_pScreen);
301 return 0;
304 void CDasherControl::ExternalEventHandler(Dasher::CEvent *pEvent) {
305 // Pass events outside
306 if(pEvent->m_iEventType == 1) {
307 Dasher::CParameterNotificationEvent * pEvt(static_cast < Dasher::CParameterNotificationEvent * >(pEvent));
308 HandleParameterNotification(pEvt->m_iParameter);
310 // TODO: Horrible - just keep events here
311 else if((pEvent->m_iEventType >= 2) && (pEvent->m_iEventType <= 9)) {
312 HandleEvent(pEvent);
317 void CDasherControl::WriteTrainFile(const std::string &strNewText) {
318 if(strNewText.length() == 0)
319 return;
321 std::string strFilename(GetStringParameter(SP_USER_LOC) + GetStringParameter(SP_TRAIN_FILE));
323 int fd=open(strFilename.c_str(),O_CREAT|O_WRONLY|O_APPEND,S_IRUSR|S_IWUSR);
324 write(fd,strNewText.c_str(),strNewText.length());
325 close(fd);
328 void CDasherControl::ExternalKeyDown(int iKeyVal) {
329 if(m_pKeyboardHelper) {
330 int iButtonID(m_pKeyboardHelper->ConvertKeycode(iKeyVal));
332 if(iButtonID != -1)
333 KeyDown(get_time(), iButtonID);
337 void CDasherControl::ExternalKeyUp(int iKeyVal) {
338 if(m_pKeyboardHelper) {
339 int iButtonID(m_pKeyboardHelper->ConvertKeycode(iKeyVal));
341 if(iButtonID != -1)
342 KeyUp(get_time(), iButtonID);
346 void CDasherControl::HandleParameterNotification(int iParameter) {
348 switch(iParameter) {
349 case SP_DASHER_FONT:
350 if(m_pPangoCache) {
351 m_pPangoCache->ChangeFont(GetStringParameter(SP_DASHER_FONT));
352 ScheduleRedraw();
354 break;
355 case BP_GLOBAL_KEYBOARD:
356 if(m_pKeyboardHelper)
357 m_pKeyboardHelper->Grab(GetBoolParameter(BP_GLOBAL_KEYBOARD));
358 break;
361 // Emit a dasher_changed signal to notify the application about changes.
362 g_signal_emit_by_name(GTK_OBJECT(m_pDasherControl), "dasher_changed", iParameter);
366 void CDasherControl::HandleEvent(CEvent *pEvent) {
367 if(pEvent->m_iEventType == 2) {
368 CEditEvent *pEditEvent(static_cast < CEditEvent * >(pEvent));
370 if(pEditEvent->m_iEditType == 1) {
371 // Insert event
372 g_signal_emit_by_name(GTK_OBJECT(m_pDasherControl), "dasher_edit_insert", pEditEvent->m_sText.c_str());
374 else if(pEditEvent->m_iEditType == 2) {
375 // Delete event
376 g_signal_emit_by_name(GTK_OBJECT(m_pDasherControl), "dasher_edit_delete", pEditEvent->m_sText.c_str());
378 else if(pEditEvent->m_iEditType == 10) {
379 g_signal_emit_by_name(GTK_OBJECT(m_pDasherControl), "dasher_edit_convert");
381 else if(pEditEvent->m_iEditType == 11) {
382 g_signal_emit_by_name(GTK_OBJECT(m_pDasherControl), "dasher_edit_protect");
385 else if(pEvent->m_iEventType == 3) {
386 CEditContextEvent *pEditContextEvent(static_cast < CEditContextEvent * >(pEvent));
387 g_signal_emit_by_name(GTK_OBJECT(m_pDasherControl), "dasher_context_request", pEditContextEvent->m_iMaxLength);
389 else if(pEvent->m_iEventType == 4) {
390 g_signal_emit_by_name(GTK_OBJECT(m_pDasherControl), "dasher_start");
392 else if(pEvent->m_iEventType == 5) {
393 g_signal_emit_by_name(GTK_OBJECT(m_pDasherControl), "dasher_stop");
395 else if(pEvent->m_iEventType == 6) {
396 CControlEvent *pControlEvent(static_cast < CControlEvent * >(pEvent));
397 g_signal_emit_by_name(GTK_OBJECT(m_pDasherControl), "dasher_control", pControlEvent->m_iID);
399 else if(pEvent->m_iEventType == 7) {
400 CLockEvent *pLockEvent(static_cast<CLockEvent *>(pEvent));
401 DasherLockInfo sInfo;
402 sInfo.szMessage = pLockEvent->m_strMessage.c_str();
403 sInfo.bLock = pLockEvent->m_bLock;
404 sInfo.iPercent = pLockEvent->m_iPercent;
406 g_signal_emit_by_name(GTK_OBJECT(m_pDasherControl), "dasher_lock_info", &sInfo);
408 else if(pEvent->m_iEventType == 8) {
409 CMessageEvent *pMessageEvent(static_cast<CMessageEvent *>(pEvent));
410 DasherMessageInfo sInfo;
411 sInfo.szMessage = pMessageEvent->m_strMessage.c_str();
412 sInfo.iID = pMessageEvent->m_iID;
413 sInfo.iType = pMessageEvent->m_iType;
415 g_signal_emit_by_name(GTK_OBJECT(m_pDasherControl), "dasher_message", &sInfo);
417 else if(pEvent->m_iEventType == 9) {
418 CCommandEvent *pCommandEvent(static_cast<CCommandEvent *>(pEvent));
419 g_signal_emit_by_name(GTK_OBJECT(m_pDasherControl), "dasher_command", pCommandEvent->m_strCommand.c_str());
423 int CDasherControl::TimerEvent() {
425 // return 1;
427 int x, y;
429 gdk_window_get_pointer(m_pCanvas->window, &x, &y, NULL);
430 m_pMouseInput->SetCoordinates(x, y);
432 NewFrame(get_time(), false);
434 // Update our UserLog object about the current mouse position
435 CUserLogBase* pUserLog = GetUserLogPtr();
436 if (pUserLog != NULL) {
437 // We want current canvas and window coordinates so normalization
438 // is done properly with respect to the canvas.
439 GdkRectangle sWindowRect;
440 GdkRectangle sCanvasRect;
442 gdk_window_get_frame_extents(m_pCanvas->window, &sWindowRect);
444 pUserLog->AddWindowSize(sWindowRect.y,
445 sWindowRect.x,
446 sWindowRect.y + sWindowRect.height,
447 sWindowRect.x + sWindowRect.width);
449 if (m_pScreen != NULL) {
450 if (m_pScreen->GetCanvasSize(&sCanvasRect))
451 pUserLog->AddCanvasSize(sCanvasRect.y,
452 sCanvasRect.x,
453 sCanvasRect.y + sCanvasRect.height,
454 sCanvasRect.x + sCanvasRect.width);
457 int iMouseX = 0;
458 int iMouseY = 0;
459 gdk_window_get_pointer(NULL, &iMouseX, &iMouseY, NULL);
461 pUserLog->AddMouseLocationNormalized(iMouseX, iMouseY, true, GetNats());
464 return 1;
466 // See CVS for code which used to be here
469 int CDasherControl::LongTimerEvent() {
470 // std::cout << "Framerate: " << GetFramerate() << std::endl;
471 // std::cout << "Render count: " << GetRenderCount() << std::endl;
472 return 1;
475 gboolean CDasherControl::ExposeEvent() {
476 NewFrame(get_time(), true);
477 return 0;
480 gboolean CDasherControl::ButtonPressEvent(GdkEventButton *event) {
482 // Take the focus if we click on the canvas
484 GdkEventFocus *focusEvent = (GdkEventFocus *) g_malloc(sizeof(GdkEventFocus));
485 gboolean *returnType;
487 focusEvent->type = GDK_FOCUS_CHANGE;
488 focusEvent->window = (GdkWindow *) m_pCanvas;
489 focusEvent->send_event = FALSE;
490 focusEvent->in = TRUE;
492 gtk_widget_grab_focus(GTK_WIDGET(m_pCanvas));
493 g_signal_emit_by_name(GTK_OBJECT(m_pCanvas), "focus_in_event", GTK_WIDGET(m_pCanvas), focusEvent, NULL, &returnType);
495 if(event->type == GDK_BUTTON_PRESS)
496 HandleClickDown(get_time(), (int)event->x, (int)event->y);
497 else if(event->type == GDK_BUTTON_RELEASE)
498 HandleClickUp(get_time(), (int)event->x, (int)event->y);
500 return false;
503 gint CDasherControl::KeyReleaseEvent(GdkEventKey *event) {
506 if((event->keyval == GDK_Shift_L) || (event->keyval == GDK_Shift_R)) {
507 if(event->state & GDK_CONTROL_MASK)
508 SetLongParameter(LP_BOOSTFACTOR, 25);
509 else
510 SetLongParameter(LP_BOOSTFACTOR, 100);
512 else if((event->keyval == GDK_Control_L) || (event->keyval == GDK_Control_R)) {
513 if(event->state & GDK_SHIFT_MASK)
514 SetLongParameter(LP_BOOSTFACTOR, 175);
515 else
516 SetLongParameter(LP_BOOSTFACTOR, 100);
518 else {
519 if(m_pKeyboardHelper) {
520 int iKeyVal(m_pKeyboardHelper->ConvertKeycode(event->keyval));
522 if(iKeyVal != -1)
523 KeyUp(get_time(), iKeyVal);
527 return 0;
530 gint CDasherControl::KeyPressEvent(GdkEventKey *event) {
531 if((event->keyval == GDK_Shift_L) || (event->keyval == GDK_Shift_R))
532 SetLongParameter(LP_BOOSTFACTOR, 175);
533 else if((event->keyval == GDK_Control_L) || (event->keyval == GDK_Control_R))
534 SetLongParameter(LP_BOOSTFACTOR, 25);
535 else {
536 if(m_pKeyboardHelper) {
537 int iKeyVal(m_pKeyboardHelper->ConvertKeycode(event->keyval));
539 if(iKeyVal != -1)
540 KeyDown(get_time(), iKeyVal);
543 return 0;
546 void CDasherControl::CanvasDestroyEvent() {
547 // Delete the screen
549 if(m_pScreen != NULL) {
550 delete m_pScreen;
551 m_pScreen = NULL;
555 // Tell the logging object that a new user trial is starting.
556 void CDasherControl::UserLogNewTrial()
558 CUserLogBase* pUserLog = GetUserLogPtr();
559 if (pUserLog != NULL) {
560 pUserLog->NewTrial();
564 int CDasherControl::GetFileSize(const std::string &strFileName) {
565 struct stat sStatInfo;
567 if(!stat(strFileName.c_str(), &sStatInfo))
568 return sStatInfo.st_size;
569 else
570 return 0;
573 // FIXME - these two methods seem a bit pointless!
575 int CDasherControl::alphabet_filter(const gchar *filename, GPatternSpec *alphabetglob) {
576 return int (g_pattern_match_string(alphabetglob, filename));
579 int CDasherControl::colour_filter(const gchar *filename, GPatternSpec *colourglob) {
580 return int (g_pattern_match_string(colourglob, filename));
583 // "C" style callbacks - these are here just because it's not possible
584 // (or at least not easy) to connect a callback directly to a C++
585 // method, so we pass a pointer to th object in the user_data field
586 // and use a wrapper function. Please do not put any functional code
587 // here.
589 extern "C" void realize_canvas(GtkWidget *widget, gpointer user_data) {
590 static_cast < CDasherControl * >(user_data)->RealizeCanvas(widget);
594 extern "C" gboolean button_press_event(GtkWidget *widget, GdkEventButton *event, gpointer data) {
595 return static_cast < CDasherControl * >(data)->ButtonPressEvent(event);
598 extern "C" gint key_press_event(GtkWidget *widget, GdkEventKey *event, gpointer data) {
599 return static_cast < CDasherControl * >(data)->KeyPressEvent(event);
602 extern "C" gint canvas_configure_event(GtkWidget *widget, GdkEventConfigure *event, gpointer data) {
603 return static_cast < CDasherControl * >(data)->CanvasConfigureEvent();
606 extern "C" void canvas_destroy_event(GtkWidget *pWidget, gpointer pUserData) {
607 static_cast<CDasherControl*>(pUserData)->CanvasDestroyEvent();
610 extern "C" gint key_release_event(GtkWidget *pWidget, GdkEventKey *event, gpointer pUserData) {
611 return static_cast<CDasherControl*>(pUserData)->KeyReleaseEvent(event);
614 extern "C" gboolean canvas_focus_event(GtkWidget *widget, GdkEventFocus *event, gpointer data) {
615 return static_cast < CDasherControl * >(data)->FocusEvent(widget, event);
618 extern "C" gint canvas_expose_event(GtkWidget *widget, GdkEventExpose *event, gpointer data) {
619 return ((CDasherControl*)data)->ExposeEvent();