1 #include "../Common/Common.h"
2 #include "../../config.h"
5 #include "DasherControl.h"
7 #include "../DasherCore/Event.h"
8 #include "../DasherCore/WrapperFactory.h"
14 #include <gdk/gdkkeysyms.h>
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
);
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
));
52 // m_pSocketInput = (CSocketInput *)GetModule(1);
53 // m_pSocketInput->Ref();
55 m_pMouseInput
= (CDasherMouseInput
*)GetModule(0);
58 // Create a pango cache
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.
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
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() {
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
) {
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
);
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
);
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
) {
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
);
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
);
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
);
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
);
283 void CDasherControl::RealizeCanvas(GtkWidget
*pWidget
) {
284 // Start the timer loops as everything is set up
285 // Aim for 20 frames per second
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
);
293 int CDasherControl::CanvasConfigureEvent() {
295 if(m_pScreen
!= NULL
)
298 m_pScreen
= new CCanvas(m_pCanvas
, m_pPangoCache
);
299 ChangeScreen(m_pScreen
);
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)) {
317 void CDasherControl::WriteTrainFile(const std::string
&strNewText
) {
318 if(strNewText
.length() == 0)
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());
328 void CDasherControl::ExternalKeyDown(int iKeyVal
) {
329 if(m_pKeyboardHelper
) {
330 int iButtonID(m_pKeyboardHelper
->ConvertKeycode(iKeyVal
));
333 KeyDown(get_time(), iButtonID
);
337 void CDasherControl::ExternalKeyUp(int iKeyVal
) {
338 if(m_pKeyboardHelper
) {
339 int iButtonID(m_pKeyboardHelper
->ConvertKeycode(iKeyVal
));
342 KeyUp(get_time(), iButtonID
);
346 void CDasherControl::HandleParameterNotification(int iParameter
) {
351 m_pPangoCache
->ChangeFont(GetStringParameter(SP_DASHER_FONT
));
355 case BP_GLOBAL_KEYBOARD
:
356 if(m_pKeyboardHelper
)
357 m_pKeyboardHelper
->Grab(GetBoolParameter(BP_GLOBAL_KEYBOARD
));
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) {
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) {
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() {
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
,
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
,
453 sCanvasRect
.y
+ sCanvasRect
.height
,
454 sCanvasRect
.x
+ sCanvasRect
.width
);
459 gdk_window_get_pointer(NULL
, &iMouseX
, &iMouseY
, NULL
);
461 pUserLog
->AddMouseLocationNormalized(iMouseX
, iMouseY
, true, GetNats());
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
;
475 gboolean
CDasherControl::ExposeEvent() {
476 NewFrame(get_time(), true);
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
);
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);
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);
516 SetLongParameter(LP_BOOSTFACTOR
, 100);
519 if(m_pKeyboardHelper
) {
520 int iKeyVal(m_pKeyboardHelper
->ConvertKeycode(event
->keyval
));
523 KeyUp(get_time(), iKeyVal
);
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);
536 if(m_pKeyboardHelper
) {
537 int iKeyVal(m_pKeyboardHelper
->ConvertKeycode(event
->keyval
));
540 KeyDown(get_time(), iKeyVal
);
546 void CDasherControl::CanvasDestroyEvent() {
549 if(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
;
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
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();