highgui: fix GTK issues with external UI thread
[platform/upstream/opencv.git] / modules / highgui / src / window_gtk.cpp
1 /*M///////////////////////////////////////////////////////////////////////////////////////
2 //
3 //  IMPORTANT: READ BEFORE DOWNLOADING, COPYING, INSTALLING OR USING.
4 //
5 //  By downloading, copying, installing or using the software you agree to this license.
6 //  If you do not agree to this license, do not download, install,
7 //  copy or use the software.
8 //
9 //
10 //                        Intel License Agreement
11 //                For Open Source Computer Vision Library
12 //
13 // Copyright (C) 2000, Intel Corporation, all rights reserved.
14 // Third party copyrights are property of their respective owners.
15 //
16 // Redistribution and use in source and binary forms, with or without modification,
17 // are permitted provided that the following conditions are met:
18 //
19 //   * Redistribution's of source code must retain the above copyright notice,
20 //     this list of conditions and the following disclaimer.
21 //
22 //   * Redistribution's in binary form must reproduce the above copyright notice,
23 //     this list of conditions and the following disclaimer in the documentation
24 //     and/or other materials provided with the distribution.
25 //
26 //   * The name of Intel Corporation may not be used to endorse or promote products
27 //     derived from this software without specific prior written permission.
28 //
29 // This software is provided by the copyright holders and contributors "as is" and
30 // any express or implied warranties, including, but not limited to, the implied
31 // warranties of merchantability and fitness for a particular purpose are disclaimed.
32 // In no event shall the Intel Corporation or contributors be liable for any direct,
33 // indirect, incidental, special, exemplary, or consequential damages
34 // (including, but not limited to, procurement of substitute goods or services;
35 // loss of use, data, or profits; or business interruption) however caused
36 // and on any theory of liability, whether in contract, strict liability,
37 // or tort (including negligence or otherwise) arising in any way out of
38 // the use of this software, even if advised of the possibility of such damage.
39 //
40 //M*/
41
42 #include "precomp.hpp"
43
44 #ifndef _WIN32
45
46 #if defined (HAVE_GTK)
47
48 #include <gtk/gtk.h>
49 #include <gdk/gdkkeysyms.h>
50 #include <gdk-pixbuf/gdk-pixbuf.h>
51 #include <stdio.h>
52
53 #if (GTK_MAJOR_VERSION == 3)
54   #define GTK_VERSION3 1
55 #endif //GTK_MAJOR_VERSION >= 3
56 #if (GTK_MAJOR_VERSION > 3 || (GTK_MAJOR_VERSION == 3 && GTK_MINOR_VERSION >= 4))
57   #define GTK_VERSION3_4 1
58 #endif
59
60 #ifdef HAVE_OPENGL
61     #include <gtk/gtkgl.h>
62     #include <GL/gl.h>
63     #include <GL/glu.h>
64 #endif
65
66 #include <opencv2/core/utils/logger.hpp>
67 #include "opencv2/imgproc.hpp"
68
69 using namespace cv;
70
71 #ifndef BIT_ALLIN
72     #define BIT_ALLIN(x,y) ( ((x)&(y)) == (y) )
73 #endif
74 #ifndef BIT_MAP
75     #define BIT_MAP(x,y,z) ( ((x)&(y)) ? (z) : 0 )
76 #endif
77
78 // TODO Fix the initial window size when flags=0.  Right now the initial window is by default
79 // 320x240 size.  A better default would be actual size of the image.  Problem
80 // is determining desired window size with trackbars while still allowing resizing.
81 //
82 // Gnome Totem source may be of use here, see bacon_video_widget_set_scale_ratio
83 // in totem/src/backend/bacon-video-widget-xine.c
84
85 ////////////////////////////////////////////////////////////
86 // CvImageWidget GTK Widget Public API
87 ////////////////////////////////////////////////////////////
88 typedef struct _CvImageWidget        CvImageWidget;
89 typedef struct _CvImageWidgetClass   CvImageWidgetClass;
90
91 struct _CvImageWidget {
92     GtkWidget widget;
93     CvMat * original_image;
94     CvMat * scaled_image;
95     int flags;
96 };
97
98 struct _CvImageWidgetClass
99 {
100   GtkWidgetClass parent_class;
101 };
102
103
104 /** Allocate new image viewer widget */
105 GtkWidget*     cvImageWidgetNew      (int flags);
106
107 /** Set the image to display in the widget */
108 void           cvImageWidgetSetImage(CvImageWidget * widget, const CvArr *arr);
109
110 // standard GTK object macros
111 #define CV_IMAGE_WIDGET(obj)          G_TYPE_CHECK_INSTANCE_CAST (obj, cvImageWidget_get_type (), CvImageWidget)
112 #define CV_IMAGE_WIDGET_CLASS(klass)  GTK_CHECK_CLASS_CAST (klass, cvImageWidget_get_type (), CvImageWidgetClass)
113 #define CV_IS_IMAGE_WIDGET(obj)       G_TYPE_CHECK_INSTANCE_TYPE (obj, cvImageWidget_get_type ())
114
115 /////////////////////////////////////////////////////////////////////////////
116 // Private API ////////////////////////////////////////////////////////
117 /////////////////////////////////////////////////////////////////////////////
118 GType        cvImageWidget_get_type (void);
119
120 static GtkWidgetClass * parent_class = NULL;
121
122 // flag to help size initial window
123 #define CV_WINDOW_NO_IMAGE 2
124
125 void cvImageWidgetSetImage(CvImageWidget * widget, const CvArr *arr){
126     CvMat * mat, stub;
127     int origin=0;
128
129     //printf("cvImageWidgetSetImage\n");
130
131     if( CV_IS_IMAGE_HDR( arr ))
132         origin = ((IplImage*)arr)->origin;
133
134     mat = cvGetMat(arr, &stub);
135
136     if(widget->original_image && !CV_ARE_SIZES_EQ(mat, widget->original_image)){
137         cvReleaseMat( &widget->original_image );
138     }
139     if(!widget->original_image){
140         widget->original_image = cvCreateMat( mat->rows, mat->cols, CV_8UC3 );
141         gtk_widget_queue_resize( GTK_WIDGET( widget ) );
142     }
143     cvConvertImage( mat, widget->original_image,
144                             (origin != 0 ? CV_CVTIMG_FLIP : 0) + CV_CVTIMG_SWAP_RB );
145     if(widget->scaled_image){
146         cvResize( widget->original_image, widget->scaled_image, CV_INTER_AREA );
147     }
148
149     // window does not refresh without this
150     gtk_widget_queue_draw( GTK_WIDGET(widget) );
151 }
152
153 GtkWidget*
154 cvImageWidgetNew (int flags)
155 {
156   CvImageWidget *image_widget;
157
158   image_widget = CV_IMAGE_WIDGET( gtk_widget_new (cvImageWidget_get_type (), NULL) );
159   image_widget->original_image = 0;
160   image_widget->scaled_image = 0;
161   image_widget->flags = flags | CV_WINDOW_NO_IMAGE;
162
163   return GTK_WIDGET (image_widget);
164 }
165
166 static void
167 cvImageWidget_realize (GtkWidget *widget)
168 {
169   GdkWindowAttr attributes;
170   gint attributes_mask;
171
172 #if defined(GTK_VERSION3)
173   GtkAllocation allocation;
174   gtk_widget_get_allocation(widget, &allocation);
175 #endif //GTK_VERSION3
176
177   //printf("cvImageWidget_realize\n");
178   g_return_if_fail (widget != NULL);
179   g_return_if_fail (CV_IS_IMAGE_WIDGET (widget));
180
181   gtk_widget_set_realized(widget, TRUE);
182
183 #if defined(GTK_VERSION3)
184   attributes.x = allocation.x;
185   attributes.y = allocation.y;
186   attributes.width = allocation.width;
187   attributes.height = allocation.height;
188 #else
189   attributes.x = widget->allocation.x;
190   attributes.y = widget->allocation.y;
191   attributes.width = widget->allocation.width;
192   attributes.height = widget->allocation.height;
193 #endif //GTK_VERSION3
194
195   attributes.wclass = GDK_INPUT_OUTPUT;
196   attributes.window_type = GDK_WINDOW_CHILD;
197   attributes.event_mask = gtk_widget_get_events (widget) |
198     GDK_EXPOSURE_MASK | GDK_BUTTON_PRESS_MASK |
199     GDK_BUTTON_RELEASE_MASK | GDK_POINTER_MOTION_MASK;
200   attributes.visual = gtk_widget_get_visual (widget);
201
202 #if defined(GTK_VERSION3)
203   attributes_mask = GDK_WA_X | GDK_WA_Y | GDK_WA_VISUAL;
204   gtk_widget_set_window(
205       widget,
206       gdk_window_new(
207         gtk_widget_get_parent_window(widget),
208         &attributes,
209         attributes_mask
210         )
211       );
212
213   gtk_widget_set_style(
214       widget,
215       gtk_style_attach(
216         gtk_widget_get_style(widget),
217         gtk_widget_get_window(widget)
218         )
219       );
220
221   gdk_window_set_user_data (
222       gtk_widget_get_window(widget),
223       widget
224       );
225
226   gtk_style_set_background (
227       gtk_widget_get_style(widget),
228       gtk_widget_get_window(widget),
229       GTK_STATE_ACTIVE
230       );
231  #else
232   // The following lines are included to prevent breaking
233   // compatibility with older Gtk2 (<gtk+-2.18) libraries.
234   attributes.colormap = gtk_widget_get_colormap (widget);
235   attributes_mask = GDK_WA_X | GDK_WA_Y | GDK_WA_VISUAL | GDK_WA_COLORMAP;
236   widget->window = gdk_window_new (widget->parent->window, &attributes, attributes_mask);
237
238   widget->style = gtk_style_attach (widget->style, widget->window);
239   gdk_window_set_user_data (widget->window, widget);
240
241   gtk_style_set_background (widget->style, widget->window, GTK_STATE_ACTIVE);
242 #endif // GTK_VERSION3
243 }
244
245 static CvSize cvImageWidget_calc_size( int im_width, int im_height, int max_width, int max_height ){
246     float aspect = (float)im_width/(float)im_height;
247     float max_aspect = (float)max_width/(float)max_height;
248     if(aspect > max_aspect){
249         return cvSize( max_width, cvRound(max_width/aspect) );
250     }
251     return cvSize( cvRound(max_height*aspect), max_height );
252 }
253
254 #if defined (GTK_VERSION3)
255 static void
256 cvImageWidget_get_preferred_width (GtkWidget *widget, gint *minimal_width, gint *natural_width)
257 {
258   g_return_if_fail (widget != NULL);
259   g_return_if_fail (CV_IS_IMAGE_WIDGET (widget));
260   CvImageWidget * image_widget = CV_IMAGE_WIDGET( widget );
261
262   if(image_widget->original_image != NULL) {
263     *minimal_width = (image_widget->flags & CV_WINDOW_AUTOSIZE) != CV_WINDOW_AUTOSIZE ?
264       gdk_window_get_width(gtk_widget_get_window(widget)) : image_widget->original_image->cols;
265   }
266   else {
267     *minimal_width = 320;
268   }
269
270   if(image_widget->scaled_image != NULL) {
271     *natural_width = *minimal_width < image_widget->scaled_image->cols ?
272       image_widget->scaled_image->cols : *minimal_width;
273   }
274   else {
275     *natural_width = *minimal_width;
276   }
277 }
278
279 static void
280 cvImageWidget_get_preferred_height (GtkWidget *widget, gint *minimal_height, gint *natural_height)
281 {
282   g_return_if_fail (widget != NULL);
283   g_return_if_fail (CV_IS_IMAGE_WIDGET (widget));
284   CvImageWidget * image_widget = CV_IMAGE_WIDGET( widget );
285
286   if(image_widget->original_image != NULL) {
287     *minimal_height = (image_widget->flags & CV_WINDOW_AUTOSIZE) != CV_WINDOW_AUTOSIZE ?
288       gdk_window_get_height(gtk_widget_get_window(widget)) : image_widget->original_image->rows;
289   }
290   else {
291     *minimal_height = 240;
292   }
293
294   if(image_widget->scaled_image != NULL) {
295     *natural_height = *minimal_height < image_widget->scaled_image->rows ?
296       image_widget->scaled_image->rows : *minimal_height;
297   }
298   else {
299     *natural_height = *minimal_height;
300   }
301 }
302
303 #else
304 static void
305 cvImageWidget_size_request (GtkWidget      *widget,
306                        GtkRequisition *requisition)
307 {
308     CvImageWidget * image_widget = CV_IMAGE_WIDGET( widget );
309
310     //printf("cvImageWidget_size_request ");
311     // the case the first time cvShowImage called or when AUTOSIZE
312     if( image_widget->original_image &&
313         ((image_widget->flags & CV_WINDOW_AUTOSIZE) ||
314          (image_widget->flags & CV_WINDOW_NO_IMAGE)))
315     {
316         //printf("original ");
317         requisition->width = image_widget->original_image->cols;
318         requisition->height = image_widget->original_image->rows;
319     }
320     // default case
321     else if(image_widget->scaled_image){
322         //printf("scaled ");
323         requisition->width = image_widget->scaled_image->cols;
324         requisition->height = image_widget->scaled_image->rows;
325     }
326     // the case before cvShowImage called
327     else{
328         //printf("default ");
329         requisition->width = 320;
330         requisition->height = 240;
331     }
332     //printf("%d %d\n",requisition->width, requisition->height);
333 }
334 #endif //GTK_VERSION3
335
336 static void cvImageWidget_set_size(GtkWidget * widget, int max_width, int max_height){
337     CvImageWidget * image_widget = CV_IMAGE_WIDGET( widget );
338
339     //printf("cvImageWidget_set_size %d %d\n", max_width, max_height);
340
341     // don't allow to set the size
342     if(image_widget->flags & CV_WINDOW_AUTOSIZE) return;
343     if(!image_widget->original_image) return;
344
345     CvSize scaled_image_size = cvImageWidget_calc_size( image_widget->original_image->cols,
346             image_widget->original_image->rows, max_width, max_height );
347
348     if( image_widget->scaled_image &&
349             ( image_widget->scaled_image->cols != scaled_image_size.width ||
350               image_widget->scaled_image->rows != scaled_image_size.height ))
351     {
352         cvReleaseMat( &image_widget->scaled_image );
353     }
354     if( !image_widget->scaled_image ){
355         image_widget->scaled_image = cvCreateMat( scaled_image_size.height, scaled_image_size.width, CV_8UC3 );
356
357
358     }
359     assert( image_widget->scaled_image );
360 }
361
362 static void
363 cvImageWidget_size_allocate (GtkWidget     *widget,
364                         GtkAllocation *allocation)
365 {
366   CvImageWidget *image_widget;
367
368   //printf("cvImageWidget_size_allocate\n");
369   g_return_if_fail (widget != NULL);
370   g_return_if_fail (CV_IS_IMAGE_WIDGET (widget));
371   g_return_if_fail (allocation != NULL);
372
373 #if defined (GTK_VERSION3)
374   gtk_widget_set_allocation(widget, allocation);
375 #else
376   widget->allocation = *allocation;
377 #endif //GTK_VERSION3
378   image_widget = CV_IMAGE_WIDGET (widget);
379
380
381   if( (image_widget->flags & CV_WINDOW_AUTOSIZE)==0 && image_widget->original_image ){
382       // (re) allocated scaled image
383       if( image_widget->flags & CV_WINDOW_NO_IMAGE ){
384           cvImageWidget_set_size( widget, image_widget->original_image->cols,
385                                           image_widget->original_image->rows);
386       }
387       else{
388           cvImageWidget_set_size( widget, allocation->width, allocation->height );
389       }
390       cvResize( image_widget->original_image, image_widget->scaled_image, CV_INTER_AREA );
391   }
392
393   if (gtk_widget_get_realized (widget))
394     {
395       image_widget = CV_IMAGE_WIDGET (widget);
396
397       if( image_widget->original_image &&
398               ((image_widget->flags & CV_WINDOW_AUTOSIZE) ||
399                (image_widget->flags & CV_WINDOW_NO_IMAGE)) )
400       {
401 #if defined (GTK_VERSION3)
402           allocation->width = image_widget->original_image->cols;
403           allocation->height = image_widget->original_image->rows;
404           gtk_widget_set_allocation(widget, allocation);
405 #else
406           widget->allocation.width = image_widget->original_image->cols;
407           widget->allocation.height = image_widget->original_image->rows;
408 #endif //GTK_VERSION3
409           gdk_window_move_resize( gtk_widget_get_window(widget),
410               allocation->x, allocation->y,
411               image_widget->original_image->cols, image_widget->original_image->rows );
412           if(image_widget->flags & CV_WINDOW_NO_IMAGE){
413               image_widget->flags &= ~CV_WINDOW_NO_IMAGE;
414               gtk_widget_queue_resize( GTK_WIDGET(widget) );
415           }
416       }
417       else{
418           gdk_window_move_resize (gtk_widget_get_window(widget),
419                   allocation->x, allocation->y,
420                   allocation->width, allocation->height );
421       }
422     }
423 }
424
425 #if defined (GTK_VERSION3)
426 static void
427 cvImageWidget_destroy (GtkWidget *object)
428 #else
429 static void
430 cvImageWidget_destroy (GtkObject *object)
431 #endif //GTK_VERSION3
432 {
433   CvImageWidget *image_widget;
434
435   g_return_if_fail (object != NULL);
436   g_return_if_fail (CV_IS_IMAGE_WIDGET (object));
437
438   image_widget = CV_IMAGE_WIDGET (object);
439
440   cvReleaseMat( &image_widget->scaled_image );
441   cvReleaseMat( &image_widget->original_image );
442
443 #if defined (GTK_VERSION3)
444   if (GTK_WIDGET_CLASS (parent_class)->destroy)
445     (* GTK_WIDGET_CLASS (parent_class)->destroy) (object);
446 #else
447   if (GTK_OBJECT_CLASS (parent_class)->destroy)
448     (* GTK_OBJECT_CLASS (parent_class)->destroy) (object);
449 #endif //GTK_VERSION3
450 }
451
452 static void cvImageWidget_class_init (gpointer g_class, gpointer /*class_data*/)
453 {
454   CvImageWidgetClass* klass = (CvImageWidgetClass*)g_class;
455 #if defined (GTK_VERSION3)
456   GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
457 #else
458   GtkObjectClass *object_class;
459   GtkWidgetClass *widget_class;
460
461   object_class = (GtkObjectClass*) klass;
462   widget_class = (GtkWidgetClass*) klass;
463 #endif //GTK_VERSION3
464
465   parent_class = GTK_WIDGET_CLASS( g_type_class_peek (gtk_widget_get_type ()) );
466
467 #if defined (GTK_VERSION3)
468   widget_class->destroy = cvImageWidget_destroy;
469   widget_class->get_preferred_width = cvImageWidget_get_preferred_width;
470   widget_class->get_preferred_height = cvImageWidget_get_preferred_height;
471 #else
472   object_class->destroy = cvImageWidget_destroy;
473   widget_class->size_request = cvImageWidget_size_request;
474 #endif //GTK_VERSION3
475
476   widget_class->realize = cvImageWidget_realize;
477   widget_class->size_allocate = cvImageWidget_size_allocate;
478   widget_class->button_press_event = NULL;
479   widget_class->button_release_event = NULL;
480   widget_class->motion_notify_event = NULL;
481 }
482
483 static void
484 cvImageWidget_init(GTypeInstance* instance, gpointer /*g_class*/)
485 {
486     CvImageWidget* image_widget = (CvImageWidget*)instance;
487     image_widget->original_image=0;
488     image_widget->scaled_image=0;
489     image_widget->flags=0;
490 }
491
492 GType cvImageWidget_get_type (void){
493   static GType image_type = 0;
494
495   if (!image_type)
496     {
497       image_type = g_type_register_static_simple(
498           GTK_TYPE_WIDGET,
499           (gchar*) "CvImageWidget",
500           sizeof(CvImageWidgetClass),
501           cvImageWidget_class_init,
502           sizeof(CvImageWidget),
503           cvImageWidget_init,
504           (GTypeFlags)0
505           );
506     }
507
508   return image_type;
509 }
510 /////////////////////////////////////////////////////////////////////////////
511 // End CvImageWidget
512 /////////////////////////////////////////////////////////////////////////////
513
514
515 struct CvWindow;
516
517 struct CvUIBase {
518     CvUIBase(int signature_) : signature(signature_) { }
519
520     int signature;
521 };
522
523 struct CvTrackbar : CvUIBase
524 {
525     CvTrackbar(const char* trackbar_name) :
526         CvUIBase(CV_TRACKBAR_MAGIC_VAL),
527         widget(NULL), name(trackbar_name),
528         parent(NULL), data(NULL),
529         pos(0), maxval(0), minval(0),
530         notify(NULL), notify2(NULL), userdata(NULL)
531     {
532         // nothing
533     }
534     ~CvTrackbar()
535     {
536         // destroyed by parent window
537     }
538
539     GtkWidget* widget;
540     std::string name;
541     CvWindow* parent;
542     int* data;
543     int pos;
544     int maxval;
545     int minval;
546     CvTrackbarCallback notify;
547     CvTrackbarCallback2 notify2;
548     void* userdata;
549 };
550
551
552 struct CvWindow : CvUIBase
553 {
554     CvWindow(const char* window_name) :
555         CvUIBase(CV_WINDOW_MAGIC_VAL),
556         widget(NULL), frame(NULL), paned(NULL), name(window_name),
557         last_key(0), flags(0), status(0),
558         on_mouse(NULL), on_mouse_param(NULL)
559 #ifdef HAVE_OPENGL
560         ,useGl(false), glDrawCallback(NULL), glDrawData(NULL)
561 #endif
562     {
563         // nothing
564     }
565     ~CvWindow();
566
567     GtkWidget* widget;
568     GtkWidget* frame;
569     GtkWidget* paned;
570     std::string name;
571
572     int last_key;
573     int flags;
574     int status;//0 normal, 1 fullscreen (YV)
575
576     CvMouseCallback on_mouse;
577     void* on_mouse_param;
578
579     std::vector< Ptr<CvTrackbar> > trackbars;
580
581 #ifdef HAVE_OPENGL
582     bool useGl;
583
584     CvOpenGlDrawCallback glDrawCallback;
585     void* glDrawData;
586 #endif
587 };
588
589
590 static gboolean icvOnClose( GtkWidget* widget, GdkEvent* event, gpointer user_data );
591 static gboolean icvOnKeyPress( GtkWidget* widget, GdkEventKey* event, gpointer user_data );
592 static void icvOnTrackbar( GtkWidget* widget, gpointer user_data );
593 static gboolean icvOnMouse( GtkWidget *widget, GdkEvent *event, gpointer user_data );
594
595 #ifdef HAVE_GTHREAD
596 int thread_started=0;
597 static gpointer icvWindowThreadLoop(gpointer data);
598 GMutex*                            last_key_mutex = NULL;
599 GCond*                             cond_have_key = NULL;
600 GMutex*                            window_mutex = NULL;
601 GThread*                           window_thread = NULL;
602 #endif
603
604 static int             last_key = -1;
605 static std::vector< Ptr<CvWindow> > g_windows;
606
607 CV_IMPL int cvInitSystem( int argc, char** argv )
608 {
609     static int wasInitialized = 0;
610
611     // check initialization status
612     if( !wasInitialized )
613     {
614         gtk_init( &argc, &argv );
615         setlocale(LC_NUMERIC,"C");
616
617         #ifdef HAVE_OPENGL
618             gtk_gl_init(&argc, &argv);
619         #endif
620
621         wasInitialized = 1;
622     }
623
624     return 0;
625 }
626
627 CV_IMPL int cvStartWindowThread(){
628 #ifdef HAVE_GTHREAD
629     cvInitSystem(0,NULL);
630     if (!thread_started) {
631     if (!g_thread_supported ()) {
632         /* the GThread system wasn't inited, so init it */
633         g_thread_init(NULL);
634     }
635
636     // this mutex protects the window resources
637     window_mutex = g_mutex_new();
638
639     // protects the 'last key pressed' variable
640     last_key_mutex = g_mutex_new();
641
642     // conditional that indicates a key has been pressed
643     cond_have_key = g_cond_new();
644
645 #if !GLIB_CHECK_VERSION(2, 32, 0)
646     // this is the window update thread
647     window_thread = g_thread_create(icvWindowThreadLoop,
648                     NULL, TRUE, NULL);
649 #else
650     window_thread = g_thread_new("OpenCV window update", icvWindowThreadLoop, NULL);
651 #endif
652     }
653     thread_started = window_thread!=NULL;
654     return thread_started;
655 #else
656     return 0;
657 #endif
658 }
659
660 #ifdef HAVE_GTHREAD
661 gpointer icvWindowThreadLoop(gpointer /*data*/)
662 {
663     while(1){
664         g_mutex_lock(window_mutex);
665         gtk_main_iteration_do(FALSE);
666         g_mutex_unlock(window_mutex);
667
668         // little sleep
669         g_usleep(500);
670
671         g_thread_yield();
672     }
673     return NULL;
674 }
675
676
677 class GMutexLock {
678     GMutex* mutex_;
679 public:
680     GMutexLock(GMutex* mutex) : mutex_(mutex) { if (mutex_) g_mutex_lock(mutex_); }
681     ~GMutexLock() { if (mutex_) g_mutex_unlock(mutex_); mutex_ = NULL; }
682 };
683
684 #define CV_LOCK_MUTEX() GMutexLock lock(window_mutex);
685
686 #else
687 #define CV_LOCK_MUTEX()
688 #endif
689
690 static CvWindow* icvFindWindowByName( const char* name )
691 {
692     for(size_t i = 0; i < g_windows.size(); ++i)
693     {
694         CvWindow* window = g_windows[i].get();
695         if (window->name == name)
696             return window;
697     }
698     return NULL;
699 }
700
701 static CvWindow* icvWindowByWidget( GtkWidget* widget )
702 {
703     for (size_t i = 0; i < g_windows.size(); ++i)
704     {
705         CvWindow* window = g_windows[i].get();
706         if (window->widget == widget || window->frame == widget || window->paned == widget)
707             return window;
708     }
709     return NULL;
710 }
711
712 CvRect cvGetWindowRect_GTK(const char* name)
713 {
714     CV_Assert(name && "NULL name string");
715
716     CV_LOCK_MUTEX();
717     CvWindow* window = icvFindWindowByName(name);
718     if (!window)
719         CV_Error( CV_StsNullPtr, "NULL window" );
720
721     gint wx, wy;
722 #ifdef HAVE_OPENGL
723     if (window->useGl) {
724         gtk_widget_translate_coordinates(window->widget, gtk_widget_get_toplevel(window->widget), 0, 0, &wx, &wy);
725         return cvRect(wx, wy, window->widget->allocation.width, window->widget->allocation.height);
726     }
727 #endif
728
729     CvImageWidget * image_widget = CV_IMAGE_WIDGET( window->widget );
730     gtk_widget_translate_coordinates(&image_widget->widget, gtk_widget_get_toplevel(&image_widget->widget), 0, 0, &wx, &wy);
731     if (image_widget->scaled_image) {
732 #if defined (GTK_VERSION3)
733       return cvRect(wx, wy, MIN(image_widget->scaled_image->cols, gtk_widget_get_allocated_width(window->widget)),
734           MIN(image_widget->scaled_image->rows, gtk_widget_get_allocated_height(window->widget)));
735 #else
736       return cvRect(wx, wy, MIN(image_widget->scaled_image->cols, window->widget->allocation.width),
737           MIN(image_widget->scaled_image->rows, window->widget->allocation.height));
738 #endif //GTK_VERSION3
739     } else if (image_widget->original_image) {
740 #if defined (GTK_VERSION3)
741       return cvRect(wx, wy, MIN(image_widget->original_image->cols, gtk_widget_get_allocated_width(window->widget)),
742           MIN(image_widget->original_image->rows, gtk_widget_get_allocated_height(window->widget)));
743 #else
744       return cvRect(wx, wy, MIN(image_widget->original_image->cols, window->widget->allocation.width),
745           MIN(image_widget->original_image->rows, window->widget->allocation.height));
746 #endif //GTK_VERSION3
747     }
748
749     return cvRect(-1, -1, -1, -1);
750 }
751
752 double cvGetModeWindow_GTK(const char* name)//YV
753 {
754     CV_Assert(name && "NULL name string");
755
756     CV_LOCK_MUTEX();
757     CvWindow* window = icvFindWindowByName(name);
758     if (!window)
759         CV_Error( CV_StsNullPtr, "NULL window" );
760
761     double result = window->status;
762     return result;
763 }
764
765
766 void cvSetModeWindow_GTK( const char* name, double prop_value)//Yannick Verdie
767 {
768     CV_Assert(name && "NULL name string");
769
770     CV_LOCK_MUTEX();
771
772     CvWindow* window = icvFindWindowByName(name);
773     if( !window )
774         CV_Error( CV_StsNullPtr, "NULL window" );
775
776     if(window->flags & CV_WINDOW_AUTOSIZE)//if the flag CV_WINDOW_AUTOSIZE is set
777         return;
778
779     //so easy to do fullscreen here, Linux rocks !
780
781     if (window->status==CV_WINDOW_FULLSCREEN && prop_value==CV_WINDOW_NORMAL)
782     {
783         gtk_window_unfullscreen(GTK_WINDOW(window->frame));
784         window->status=CV_WINDOW_NORMAL;
785         return;
786     }
787
788     if (window->status==CV_WINDOW_NORMAL && prop_value==CV_WINDOW_FULLSCREEN)
789     {
790         gtk_window_fullscreen(GTK_WINDOW(window->frame));
791         window->status=CV_WINDOW_FULLSCREEN;
792         return;
793     }
794 }
795
796 void cv::setWindowTitle(const String& winname, const String& title)
797 {
798     CV_LOCK_MUTEX();
799
800     CvWindow* window = icvFindWindowByName(winname.c_str());
801
802     if (!window)
803     {
804         namedWindow(winname);
805         window = icvFindWindowByName(winname.c_str());
806         CV_Assert(window);
807     }
808
809     gtk_window_set_title(GTK_WINDOW(window->frame), title.c_str());
810 }
811
812 double cvGetPropWindowAutoSize_GTK(const char* name)
813 {
814     CV_Assert(name && "NULL name string");
815
816     CV_LOCK_MUTEX();
817
818     CvWindow* window = icvFindWindowByName(name);
819     if (!window)
820         return -1; // keep silence here
821
822     double result = window->flags & CV_WINDOW_AUTOSIZE;
823     return result;
824 }
825
826 double cvGetRatioWindow_GTK(const char* name)
827 {
828     CV_Assert(name && "NULL name string");
829
830     CV_LOCK_MUTEX();
831
832     CvWindow* window = icvFindWindowByName(name);
833     if (!window)
834         return -1; // keep silence here
835
836 #if defined (GTK_VERSION3)
837     double result = static_cast<double>(
838         gtk_widget_get_allocated_width(window->widget)) / gtk_widget_get_allocated_height(window->widget);
839 #else
840     double result = static_cast<double>(window->widget->allocation.width) / window->widget->allocation.height;
841 #endif // GTK_VERSION3
842     return result;
843 }
844
845 double cvGetOpenGlProp_GTK(const char* name)
846 {
847 #ifdef HAVE_OPENGL
848     CV_Assert(name && "NULL name string");
849
850     CV_LOCK_MUTEX();
851
852     CvWindow* window = icvFindWindowByName(name);
853     if (!window)
854         return -1; // keep silence here
855
856     double result = window->useGl;
857     return result;
858 #else
859     (void)name;
860     return -1;
861 #endif
862 }
863
864
865 // OpenGL support
866
867 #ifdef HAVE_OPENGL
868
869 namespace
870 {
871     void createGlContext(CvWindow* window)
872     {
873         GdkGLConfig* glconfig;
874
875         // Try double-buffered visual
876         glconfig = gdk_gl_config_new_by_mode((GdkGLConfigMode)(GDK_GL_MODE_RGB | GDK_GL_MODE_DEPTH | GDK_GL_MODE_DOUBLE));
877         if (!glconfig)
878             CV_Error( CV_OpenGlApiCallError, "Can't Create A GL Device Context" );
879
880         // Set OpenGL-capability to the widget
881         if (!gtk_widget_set_gl_capability(window->widget, glconfig, NULL, TRUE, GDK_GL_RGBA_TYPE))
882             CV_Error( CV_OpenGlApiCallError, "Can't Create A GL Device Context" );
883
884         window->useGl = true;
885     }
886
887     void drawGl(CvWindow* window)
888     {
889         GdkGLContext* glcontext = gtk_widget_get_gl_context(window->widget);
890         GdkGLDrawable* gldrawable = gtk_widget_get_gl_drawable(window->widget);
891
892         if (!gdk_gl_drawable_gl_begin (gldrawable, glcontext))
893             CV_Error( CV_OpenGlApiCallError, "Can't Activate The GL Rendering Context" );
894
895         glViewport(0, 0, window->widget->allocation.width, window->widget->allocation.height);
896
897         glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
898
899         if (window->glDrawCallback)
900             window->glDrawCallback(window->glDrawData);
901
902         if (gdk_gl_drawable_is_double_buffered (gldrawable))
903             gdk_gl_drawable_swap_buffers(gldrawable);
904         else
905             glFlush();
906
907         gdk_gl_drawable_gl_end(gldrawable);
908     }
909 }
910
911 #endif // HAVE_OPENGL
912
913 #if defined (GTK_VERSION3)
914 static gboolean cvImageWidget_draw(GtkWidget* widget, cairo_t *cr, gpointer data)
915 {
916 #ifdef HAVE_OPENGL
917     CvWindow* window = (CvWindow*)data;
918
919     if (window->useGl)
920     {
921         drawGl(window);
922         return TRUE;
923     }
924 #else
925     (void)data;
926 #endif
927
928   CvImageWidget *image_widget = NULL;
929   GdkPixbuf *pixbuf = NULL;
930
931   g_return_val_if_fail (widget != NULL, FALSE);
932   g_return_val_if_fail (CV_IS_IMAGE_WIDGET (widget), FALSE);
933
934   image_widget = CV_IMAGE_WIDGET (widget);
935
936   if( image_widget->scaled_image ){
937       // center image in available region
938 #if defined (GTK_VERSION3)
939       int x0 = (gtk_widget_get_allocated_width(widget) - image_widget->scaled_image->cols)/2;
940       int y0 = (gtk_widget_get_allocated_height(widget) - image_widget->scaled_image->rows)/2;
941 #else
942       int x0 = (widget->allocation.width - image_widget->scaled_image->cols)/2;
943       int y0 = (widget->allocation.height - image_widget->scaled_image->rows)/2;
944 #endif //GTK_VERSION3
945
946 #if defined (GTK_VERSION3)
947       pixbuf = gdk_pixbuf_new_from_data(image_widget->scaled_image->data.ptr, GDK_COLORSPACE_RGB, false,
948           8, MIN(image_widget->scaled_image->cols, gtk_widget_get_allocated_width(widget)),
949           MIN(image_widget->scaled_image->rows, gtk_widget_get_allocated_height(widget)),
950           image_widget->scaled_image->step, NULL, NULL);
951 #else
952       pixbuf = gdk_pixbuf_new_from_data(image_widget->scaled_image->data.ptr, GDK_COLORSPACE_RGB, false,
953           8, MIN(image_widget->scaled_image->cols, widget->allocation.width),
954           MIN(image_widget->scaled_image->rows, widget->allocation.height),
955           image_widget->scaled_image->step, NULL, NULL);
956 #endif //GTK_VERSION3
957
958       gdk_cairo_set_source_pixbuf(cr, pixbuf, x0, y0);
959   }
960   else if( image_widget->original_image ){
961 #if defined (GTK_VERSION3)
962       pixbuf = gdk_pixbuf_new_from_data(image_widget->original_image->data.ptr, GDK_COLORSPACE_RGB, false,
963           8, MIN(image_widget->original_image->cols, gtk_widget_get_allocated_width(widget)),
964           MIN(image_widget->original_image->rows, gtk_widget_get_allocated_height(widget)),
965           image_widget->original_image->step, NULL, NULL);
966 #else
967       pixbuf = gdk_pixbuf_new_from_data(image_widget->original_image->data.ptr, GDK_COLORSPACE_RGB, false,
968           8, MIN(image_widget->original_image->cols, widget->allocation.width),
969           MIN(image_widget->original_image->rows, widget->allocation.height),
970           image_widget->original_image->step, NULL, NULL);
971 #endif //GTK_VERSION3
972       gdk_cairo_set_source_pixbuf(cr, pixbuf, 0, 0);
973   }
974
975   cairo_paint(cr);
976   if(pixbuf)
977       g_object_unref(pixbuf);
978   return TRUE;
979 }
980
981 #else
982 static gboolean cvImageWidget_expose(GtkWidget* widget, GdkEventExpose* event, gpointer data)
983 {
984 #ifdef HAVE_OPENGL
985     CvWindow* window = (CvWindow*)data;
986
987     if (window->useGl)
988     {
989         drawGl(window);
990         return TRUE;
991     }
992 #else
993     (void)data;
994 #endif
995
996   CvImageWidget *image_widget = NULL;
997   cairo_t *cr = NULL;
998   GdkPixbuf *pixbuf = NULL;
999
1000   g_return_val_if_fail (widget != NULL, FALSE);
1001   g_return_val_if_fail (CV_IS_IMAGE_WIDGET (widget), FALSE);
1002   g_return_val_if_fail (event != NULL, FALSE);
1003
1004   if (event->count > 0)
1005     return FALSE;
1006
1007   cr = gdk_cairo_create(widget->window);
1008   image_widget = CV_IMAGE_WIDGET (widget);
1009
1010   if( image_widget->scaled_image ){
1011       // center image in available region
1012       int x0 = (widget->allocation.width - image_widget->scaled_image->cols)/2;
1013       int y0 = (widget->allocation.height - image_widget->scaled_image->rows)/2;
1014
1015       pixbuf = gdk_pixbuf_new_from_data(image_widget->scaled_image->data.ptr, GDK_COLORSPACE_RGB, false,
1016           8, MIN(image_widget->scaled_image->cols, widget->allocation.width),
1017           MIN(image_widget->scaled_image->rows, widget->allocation.height),
1018           image_widget->scaled_image->step, NULL, NULL);
1019
1020       gdk_cairo_set_source_pixbuf(cr, pixbuf, x0, y0);
1021   }
1022   else if( image_widget->original_image ){
1023       pixbuf = gdk_pixbuf_new_from_data(image_widget->original_image->data.ptr, GDK_COLORSPACE_RGB, false,
1024           8, MIN(image_widget->original_image->cols, widget->allocation.width),
1025           MIN(image_widget->original_image->rows, widget->allocation.height),
1026           image_widget->original_image->step, NULL, NULL);
1027       gdk_cairo_set_source_pixbuf(cr, pixbuf, 0, 0);
1028   }
1029
1030   cairo_paint(cr);
1031   if(pixbuf)
1032       g_object_unref(pixbuf);
1033   cairo_destroy(cr);
1034   return TRUE;
1035 }
1036 #endif //GTK_VERSION3
1037
1038 CV_IMPL int cvNamedWindow( const char* name, int flags )
1039 {
1040     cvInitSystem(name ? 1 : 0,(char**)&name);
1041     CV_Assert(name && "NULL name string");
1042
1043     CV_LOCK_MUTEX();
1044
1045     // Check the name in the storage
1046     if (icvFindWindowByName(name))
1047     {
1048         return 1;
1049     }
1050
1051     Ptr<CvWindow> window = makePtr<CvWindow>(name);
1052     window->flags = flags;
1053     window->status = CV_WINDOW_NORMAL;//YV
1054
1055     window->frame = gtk_window_new( GTK_WINDOW_TOPLEVEL );
1056
1057     window->paned = gtk_vbox_new( FALSE, 0 );
1058     window->widget = cvImageWidgetNew( flags );
1059     gtk_box_pack_end( GTK_BOX(window->paned), window->widget, TRUE, TRUE, 0 );
1060     gtk_widget_show( window->widget );
1061     gtk_container_add( GTK_CONTAINER(window->frame), window->paned );
1062     gtk_widget_show( window->paned );
1063
1064 #ifndef HAVE_OPENGL
1065     if (flags & CV_WINDOW_OPENGL)
1066         CV_Error( CV_OpenGlNotSupported, "Library was built without OpenGL support" );
1067 #else
1068     if (flags & CV_WINDOW_OPENGL)
1069         createGlContext(window);
1070
1071     window->glDrawCallback = 0;
1072     window->glDrawData = 0;
1073 #endif
1074
1075     //
1076     // configure event handlers
1077     // TODO -- move this to CvImageWidget ?
1078     g_signal_connect( window->frame, "key-press-event",
1079                         G_CALLBACK(icvOnKeyPress), window );
1080     g_signal_connect( window->widget, "button-press-event",
1081                         G_CALLBACK(icvOnMouse), window );
1082     g_signal_connect( window->widget, "button-release-event",
1083                         G_CALLBACK(icvOnMouse), window );
1084     g_signal_connect( window->widget, "motion-notify-event",
1085                         G_CALLBACK(icvOnMouse), window );
1086     g_signal_connect( window->widget, "scroll-event",
1087                         G_CALLBACK(icvOnMouse), window );
1088     g_signal_connect( window->frame, "delete-event",
1089                         G_CALLBACK(icvOnClose), window );
1090 #if defined(GTK_VERSION3)
1091     g_signal_connect( window->widget, "draw",
1092                         G_CALLBACK(cvImageWidget_draw), window );
1093 #else
1094     g_signal_connect( window->widget, "expose-event",
1095                         G_CALLBACK(cvImageWidget_expose), window );
1096 #endif //GTK_VERSION3
1097
1098
1099 #if defined(GTK_VERSION3_4)
1100     gtk_widget_add_events (window->widget, GDK_BUTTON_RELEASE_MASK | GDK_BUTTON_PRESS_MASK | GDK_POINTER_MOTION_MASK | GDK_SCROLL_MASK | GDK_SMOOTH_SCROLL_MASK) ;
1101 #else
1102     gtk_widget_add_events (window->widget, GDK_BUTTON_RELEASE_MASK | GDK_BUTTON_PRESS_MASK | GDK_POINTER_MOTION_MASK | GDK_SCROLL_MASK) ;
1103 #endif //GTK_VERSION3_4
1104
1105     gtk_widget_show( window->frame );
1106     gtk_window_set_title( GTK_WINDOW(window->frame), name );
1107
1108     g_windows.push_back(window);
1109
1110     bool b_nautosize = ((flags & CV_WINDOW_AUTOSIZE) == 0);
1111     gtk_window_set_resizable( GTK_WINDOW(window->frame), b_nautosize );
1112
1113     // allow window to be resized
1114     if( b_nautosize ){
1115         GdkGeometry geometry;
1116         geometry.min_width = 50;
1117         geometry.min_height = 50;
1118         gtk_window_set_geometry_hints( GTK_WINDOW( window->frame ), GTK_WIDGET( window->widget ),
1119             &geometry, (GdkWindowHints) (GDK_HINT_MIN_SIZE));
1120     }
1121
1122 #ifdef HAVE_OPENGL
1123     if (window->useGl)
1124         cvSetOpenGlContext(name);
1125 #endif
1126
1127     return 1;
1128 }
1129
1130
1131 #ifdef HAVE_OPENGL
1132
1133 CV_IMPL void cvSetOpenGlContext(const char* name)
1134 {
1135     GdkGLContext* glcontext;
1136     GdkGLDrawable* gldrawable;
1137
1138     CV_Assert(name && "NULL name string");
1139
1140     CV_LOCK_MUTEX();
1141
1142     CvWindow* window = icvFindWindowByName(name);
1143     if (!window)
1144         CV_Error( CV_StsNullPtr, "NULL window" );
1145
1146     if (!window->useGl)
1147         CV_Error( CV_OpenGlNotSupported, "Window doesn't support OpenGL" );
1148
1149     glcontext = gtk_widget_get_gl_context(window->widget);
1150     gldrawable = gtk_widget_get_gl_drawable(window->widget);
1151
1152     if (!gdk_gl_drawable_make_current(gldrawable, glcontext))
1153         CV_Error( CV_OpenGlApiCallError, "Can't Activate The GL Rendering Context" );
1154 }
1155
1156 CV_IMPL void cvUpdateWindow(const char* name)
1157 {
1158     CV_Assert(name && "NULL name string");
1159
1160     CV_LOCK_MUTEX();
1161
1162     CvWindow* window = icvFindWindowByName(name);
1163     if (!window)
1164         return;
1165
1166     // window does not refresh without this
1167     gtk_widget_queue_draw( GTK_WIDGET(window->widget) );
1168 }
1169
1170 CV_IMPL void cvSetOpenGlDrawCallback(const char* name, CvOpenGlDrawCallback callback, void* userdata)
1171 {
1172     CV_Assert(name && "NULL name string");
1173
1174     CV_LOCK_MUTEX();
1175
1176     CvWindow* window = icvFindWindowByName(name);
1177     if( !window )
1178         return;
1179
1180     if (!window->useGl)
1181         CV_Error( CV_OpenGlNotSupported, "Window was created without OpenGL context" );
1182
1183     window->glDrawCallback = callback;
1184     window->glDrawData = userdata;
1185 }
1186
1187 #endif // HAVE_OPENGL
1188
1189
1190
1191 CvWindow::~CvWindow()
1192 {
1193     gtk_widget_destroy(frame);
1194 }
1195
1196 static void checkLastWindow()
1197 {
1198     // if last window...
1199     if (g_windows.empty())
1200     {
1201 #ifdef HAVE_GTHREAD
1202         if( thread_started )
1203         {
1204             // send key press signal to jump out of any waiting cvWaitKey's
1205             g_cond_broadcast( cond_have_key );
1206         }
1207         else
1208         {
1209 #endif
1210             // Some GTK+ modules (like the Unity module) use GDBusConnection,
1211             // which has a habit of postponing cleanup by performing it via
1212             // idle sources added to the main loop. Since this was the last window,
1213             // we can assume that no event processing is going to happen in the
1214             // nearest future, so we should force that cleanup (by handling all pending
1215             // events) while we still have the chance.
1216             // This is not needed if thread_started is true, because the background
1217             // thread will process events continuously.
1218             while( gtk_events_pending() )
1219                 gtk_main_iteration();
1220 #ifdef HAVE_GTHREAD
1221         }
1222 #endif
1223     }
1224 }
1225
1226 static void icvDeleteWindow( CvWindow* window )
1227 {
1228     bool found = false;
1229     for (std::vector< Ptr<CvWindow> >::iterator i = g_windows.begin();
1230          i != g_windows.end(); ++i)
1231     {
1232         if (i->get() == window)
1233         {
1234             g_windows.erase(i);
1235             found = true;
1236             break;
1237         }
1238     }
1239     CV_Assert(found && "Can't destroy non-registered window");
1240
1241     checkLastWindow();
1242 }
1243
1244 CV_IMPL void cvDestroyWindow( const char* name )
1245 {
1246     CV_Assert(name && "NULL name string");
1247
1248     CV_LOCK_MUTEX();
1249
1250     bool found = false;
1251     for (std::vector< Ptr<CvWindow> >::iterator i = g_windows.begin();
1252          i != g_windows.end(); ++i)
1253     {
1254         if (i->get()->name == name)
1255         {
1256             g_windows.erase(i);
1257             found = true;
1258             break;
1259         }
1260     }
1261     CV_Assert(found && "Can't destroy non-registered window");
1262
1263     checkLastWindow();
1264 }
1265
1266
1267 CV_IMPL void
1268 cvDestroyAllWindows( void )
1269 {
1270     CV_LOCK_MUTEX();
1271
1272     g_windows.clear();
1273     checkLastWindow();
1274 }
1275
1276 // CvSize icvCalcOptimalWindowSize( CvWindow * window, CvSize new_image_size){
1277 //     CvSize window_size;
1278 //     GtkWidget * toplevel = gtk_widget_get_toplevel( window->frame );
1279 //     gdk_drawable_get_size( GDK_DRAWABLE(toplevel->window),
1280 //             &window_size.width, &window_size.height );
1281
1282 //     window_size.width = window_size.width + new_image_size.width - window->widget->allocation.width;
1283 //     window_size.height = window_size.height + new_image_size.height - window->widget->allocation.height;
1284
1285 //     return window_size;
1286 // }
1287
1288 CV_IMPL void
1289 cvShowImage( const char* name, const CvArr* arr )
1290 {
1291     CV_Assert(name && "NULL name string");
1292
1293     CV_LOCK_MUTEX();
1294
1295     CvWindow* window = icvFindWindowByName(name);
1296     if(!window)
1297     {
1298         cvNamedWindow(name, 1);
1299         window = icvFindWindowByName(name);
1300     }
1301     CV_Assert(window);
1302
1303     if (arr)
1304     {
1305     #ifdef HAVE_OPENGL
1306         if (window->useGl)
1307         {
1308             cv::imshow(name, cv::cvarrToMat(arr));
1309             return;
1310         }
1311     #endif
1312
1313         CvImageWidget * image_widget = CV_IMAGE_WIDGET( window->widget );
1314         cvImageWidgetSetImage( image_widget, arr );
1315     }
1316 }
1317
1318 CV_IMPL void cvResizeWindow(const char* name, int width, int height )
1319 {
1320     CV_Assert(name && "NULL name string");
1321
1322     CV_LOCK_MUTEX();
1323
1324     CvWindow* window = icvFindWindowByName(name);
1325     if(!window)
1326         return;
1327
1328     CvImageWidget* image_widget = CV_IMAGE_WIDGET( window->widget );
1329     //if(image_widget->flags & CV_WINDOW_AUTOSIZE)
1330         //EXIT;
1331
1332     gtk_window_set_resizable( GTK_WINDOW(window->frame), 1 );
1333     gtk_window_resize( GTK_WINDOW(window->frame), width, height );
1334
1335     // disable initial resize since presumably user wants to keep
1336     // this window size
1337     image_widget->flags &= ~CV_WINDOW_NO_IMAGE;
1338 }
1339
1340
1341 CV_IMPL void cvMoveWindow( const char* name, int x, int y )
1342 {
1343     CV_Assert(name && "NULL name string");
1344
1345     CV_LOCK_MUTEX();
1346
1347     CvWindow* window = icvFindWindowByName(name);
1348     if(!window)
1349         return;
1350
1351     gtk_window_move( GTK_WINDOW(window->frame), x, y );
1352 }
1353
1354
1355 static CvTrackbar*
1356 icvFindTrackbarByName( const CvWindow* window, const char* name )
1357 {
1358     for (size_t i = 0; i < window->trackbars.size(); ++i)
1359     {
1360         CvTrackbar* trackbar = window->trackbars[i].get();
1361         if (trackbar->name == name)
1362             return trackbar;
1363     }
1364     return NULL;
1365 }
1366
1367 static int
1368 icvCreateTrackbar( const char* trackbar_name, const char* window_name,
1369                    int* val, int count, CvTrackbarCallback on_notify,
1370                    CvTrackbarCallback2 on_notify2, void* userdata )
1371 {
1372     CV_Assert(window_name && "NULL window name");
1373     CV_Assert(trackbar_name && "NULL trackbar name");
1374
1375     if( count <= 0 )
1376         CV_Error( CV_StsOutOfRange, "Bad trackbar maximal value" );
1377
1378     CV_LOCK_MUTEX();
1379
1380     CvWindow* window = icvFindWindowByName(window_name);
1381     if(!window)
1382         return 0;
1383
1384     CvTrackbar* trackbar = icvFindTrackbarByName(window, trackbar_name);
1385     if (!trackbar)
1386     {
1387         Ptr<CvTrackbar> trackbar_ = makePtr<CvTrackbar>(trackbar_name);
1388         trackbar = trackbar_.get();
1389         trackbar->parent = window;
1390         window->trackbars.push_back(trackbar_);
1391
1392         GtkWidget* hscale_box = gtk_hbox_new( FALSE, 10 );
1393         GtkWidget* hscale_label = gtk_label_new( trackbar_name );
1394         GtkWidget* hscale = gtk_hscale_new_with_range( 0, count, 1 );
1395         gtk_scale_set_digits( GTK_SCALE(hscale), 0 );
1396         //gtk_scale_set_value_pos( hscale, GTK_POS_TOP );
1397         gtk_scale_set_draw_value( GTK_SCALE(hscale), TRUE );
1398
1399         trackbar->widget = hscale;
1400         gtk_box_pack_start( GTK_BOX(hscale_box), hscale_label, FALSE, FALSE, 5 );
1401         gtk_widget_show( hscale_label );
1402         gtk_box_pack_start( GTK_BOX(hscale_box), hscale, TRUE, TRUE, 5 );
1403         gtk_widget_show( hscale );
1404         gtk_box_pack_start( GTK_BOX(window->paned), hscale_box, FALSE, FALSE, 5 );
1405         gtk_widget_show( hscale_box );
1406     }
1407
1408     if( val )
1409     {
1410         int value = *val;
1411         if( value < 0 )
1412             value = 0;
1413         if( value > count )
1414             value = count;
1415         gtk_range_set_value( GTK_RANGE(trackbar->widget), value );
1416         trackbar->pos = value;
1417         trackbar->data = val;
1418     }
1419
1420     trackbar->maxval = count;
1421     trackbar->notify = on_notify;
1422     trackbar->notify2 = on_notify2;
1423     trackbar->userdata = userdata;
1424     g_signal_connect( trackbar->widget, "value-changed",
1425                         G_CALLBACK(icvOnTrackbar), trackbar );
1426
1427     // queue a widget resize to trigger a window resize to
1428     // compensate for the addition of trackbars
1429     gtk_widget_queue_resize( GTK_WIDGET(window->widget) );
1430
1431     return 1;
1432 }
1433
1434
1435 CV_IMPL int
1436 cvCreateTrackbar( const char* trackbar_name, const char* window_name,
1437                   int* val, int count, CvTrackbarCallback on_notify )
1438 {
1439     return icvCreateTrackbar(trackbar_name, window_name, val, count,
1440                              on_notify, 0, 0);
1441 }
1442
1443
1444 CV_IMPL int
1445 cvCreateTrackbar2( const char* trackbar_name, const char* window_name,
1446                    int* val, int count, CvTrackbarCallback2 on_notify2,
1447                    void* userdata )
1448 {
1449     return icvCreateTrackbar(trackbar_name, window_name, val, count,
1450                              0, on_notify2, userdata);
1451 }
1452
1453
1454 CV_IMPL void
1455 cvSetMouseCallback( const char* window_name, CvMouseCallback on_mouse, void* param )
1456 {
1457     CV_Assert(window_name && "NULL window name");
1458
1459     CV_LOCK_MUTEX();
1460
1461     CvWindow* window = icvFindWindowByName(window_name);
1462     if (!window)
1463         return;
1464
1465     window->on_mouse = on_mouse;
1466     window->on_mouse_param = param;
1467 }
1468
1469
1470 CV_IMPL int cvGetTrackbarPos( const char* trackbar_name, const char* window_name )
1471 {
1472     CV_Assert(window_name && "NULL window name");
1473     CV_Assert(trackbar_name && "NULL trackbar name");
1474
1475     CV_LOCK_MUTEX();
1476
1477     CvWindow* window = icvFindWindowByName(window_name);
1478     if (!window)
1479         return -1;
1480
1481     CvTrackbar* trackbar = icvFindTrackbarByName(window,trackbar_name);
1482     if (!trackbar)
1483         return -1;
1484
1485     return trackbar->pos;
1486 }
1487
1488
1489 CV_IMPL void cvSetTrackbarPos( const char* trackbar_name, const char* window_name, int pos )
1490 {
1491     CV_Assert(window_name && "NULL window name");
1492     CV_Assert(trackbar_name && "NULL trackbar name");
1493
1494     CV_LOCK_MUTEX();
1495
1496     CvWindow* window = icvFindWindowByName(window_name);
1497     if(!window)
1498         return;
1499
1500     CvTrackbar* trackbar = icvFindTrackbarByName(window,trackbar_name);
1501     if( trackbar )
1502     {
1503         if( pos < trackbar->minval )
1504             pos = trackbar->minval;
1505
1506         if( pos > trackbar->maxval )
1507             pos = trackbar->maxval;
1508     }
1509     else
1510     {
1511         CV_Error( CV_StsNullPtr, "No trackbar found" );
1512     }
1513
1514     gtk_range_set_value( GTK_RANGE(trackbar->widget), pos );
1515 }
1516
1517
1518 CV_IMPL void cvSetTrackbarMax(const char* trackbar_name, const char* window_name, int maxval)
1519 {
1520     CV_Assert(window_name && "NULL window name");
1521     CV_Assert(trackbar_name && "NULL trackbar name");
1522
1523     CV_LOCK_MUTEX();
1524
1525     CvWindow* window = icvFindWindowByName(window_name);
1526     if(!window)
1527         return;
1528
1529     CvTrackbar* trackbar = icvFindTrackbarByName(window,trackbar_name);
1530     if(!trackbar)
1531         return;
1532
1533     trackbar->maxval = maxval;
1534     if (trackbar->maxval >= trackbar->minval)
1535         gtk_range_set_range(GTK_RANGE(trackbar->widget), trackbar->minval, trackbar->maxval);
1536 }
1537
1538
1539 CV_IMPL void cvSetTrackbarMin(const char* trackbar_name, const char* window_name, int minval)
1540 {
1541     CV_Assert(window_name && "NULL window name");
1542     CV_Assert(trackbar_name && "NULL trackbar name");
1543
1544     CV_LOCK_MUTEX();
1545
1546     CvWindow* window = icvFindWindowByName(window_name);
1547     if(!window)
1548         return;
1549
1550     CvTrackbar* trackbar = icvFindTrackbarByName(window,trackbar_name);
1551     if(!trackbar)
1552         return;
1553
1554     trackbar->minval = minval;
1555     if (trackbar->maxval >= trackbar->minval)
1556         gtk_range_set_range(GTK_RANGE(trackbar->widget), trackbar->minval, trackbar->maxval);
1557 }
1558
1559
1560 CV_IMPL void* cvGetWindowHandle( const char* window_name )
1561 {
1562     CV_Assert(window_name && "NULL window name");
1563
1564     CV_LOCK_MUTEX();
1565
1566     CvWindow* window = icvFindWindowByName(window_name);
1567     if(!window)
1568         return NULL;
1569
1570     return (void*)window->widget;
1571 }
1572
1573
1574 CV_IMPL const char* cvGetWindowName( void* window_handle )
1575 {
1576     CV_Assert(window_handle && "NULL window handle");
1577
1578     CV_LOCK_MUTEX();
1579
1580     CvWindow* window = icvWindowByWidget( (GtkWidget*)window_handle );
1581     if (window)
1582         return window->name.c_str();
1583
1584     return ""; // FIXME: NULL?
1585 }
1586
1587 static GtkFileFilter* icvMakeGtkFilter(const char* name, const char* patterns, GtkFileFilter* images)
1588 {
1589     GtkFileFilter* filter = gtk_file_filter_new();
1590     gtk_file_filter_set_name(filter, name);
1591
1592     while(patterns[0])
1593     {
1594         gtk_file_filter_add_pattern(filter, patterns);
1595         gtk_file_filter_add_pattern(images, patterns);
1596         patterns += strlen(patterns) + 1;
1597     }
1598
1599     return filter;
1600 }
1601
1602 static void icvShowSaveAsDialog(GtkWidget* widget, CvWindow* window)
1603 {
1604     if (!window || !widget)
1605         return;
1606
1607     CvImageWidget* image_widget = CV_IMAGE_WIDGET(window->widget);
1608     if (!image_widget || !image_widget->original_image)
1609         return;
1610
1611     GtkWidget* dialog = gtk_file_chooser_dialog_new("Save As...",
1612                       GTK_WINDOW(widget),
1613                       GTK_FILE_CHOOSER_ACTION_SAVE,
1614                       GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
1615                       GTK_STOCK_SAVE, GTK_RESPONSE_ACCEPT,
1616                       NULL);
1617     gtk_file_chooser_set_do_overwrite_confirmation(GTK_FILE_CHOOSER(dialog), TRUE);
1618
1619     cv::String sname = gtk_window_get_title(GTK_WINDOW(window->frame));
1620     sname = sname.substr(sname.find_last_of("\\/") + 1) + ".png";
1621     gtk_file_chooser_set_current_name(GTK_FILE_CHOOSER(dialog), sname.c_str());
1622
1623     GtkFileFilter* filter_all = gtk_file_filter_new();
1624     gtk_file_filter_set_name(filter_all, "All Files");
1625     gtk_file_filter_add_pattern(filter_all, "*");
1626
1627     GtkFileFilter* filter_images = gtk_file_filter_new();
1628     gtk_file_filter_set_name(filter_images, "All Images");
1629
1630     GtkFileFilter* file_filters[] = {
1631         icvMakeGtkFilter("Portable Network Graphics files (*.png)",               "*.png\0",                             filter_images),
1632         icvMakeGtkFilter("JPEG files (*.jpeg;*.jpg;*.jpe)",                       "*.jpeg\0*.jpg\0*.jpe\0",              filter_images),
1633         icvMakeGtkFilter("Windows bitmap (*.bmp;*.dib)",                          "*.bmp\0*.dib\0",                      filter_images),
1634         icvMakeGtkFilter("TIFF Files (*.tiff;*.tif)",                             "*.tiff\0*.tif\0",                     filter_images),
1635         icvMakeGtkFilter("JPEG-2000 files (*.jp2)",                               "*.jp2\0",                             filter_images),
1636         icvMakeGtkFilter("WebP files (*.webp)",                                   "*.webp\0",                            filter_images),
1637         icvMakeGtkFilter("Portable image format (*.pbm;*.pgm;*.ppm;*.pxm;*.pnm)", "*.pbm\0*.pgm\0*.ppm\0*.pxm\0*.pnm\0", filter_images),
1638         icvMakeGtkFilter("OpenEXR Image files (*.exr)",                           "*.exr\0",                             filter_images),
1639         icvMakeGtkFilter("Radiance HDR (*.hdr;*.pic)",                            "*.hdr\0*.pic\0",                      filter_images),
1640         icvMakeGtkFilter("Sun raster files (*.sr;*.ras)",                         "*.sr\0*.ras\0",                       filter_images),
1641         filter_images,
1642         filter_all
1643     };
1644
1645     for (size_t idx = 0; idx < sizeof(file_filters)/sizeof(file_filters[0]); ++idx)
1646         gtk_file_chooser_add_filter(GTK_FILE_CHOOSER(dialog), file_filters[idx]); // filter ownership is transferred to dialog
1647     gtk_file_chooser_set_filter(GTK_FILE_CHOOSER(dialog), filter_images);
1648
1649     cv::String filename;
1650     if (gtk_dialog_run(GTK_DIALOG(dialog)) == GTK_RESPONSE_ACCEPT)
1651     {
1652         char* fname = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(dialog));
1653         filename = fname;
1654         g_free(fname);
1655     }
1656     gtk_widget_destroy(dialog);
1657
1658     if (!filename.empty())
1659     {
1660         cv::Mat bgr;
1661         cv::cvtColor(cv::cvarrToMat(image_widget->original_image), bgr, cv::COLOR_RGB2BGR);
1662         cv::imwrite(filename, bgr);
1663     }
1664 }
1665
1666 #if defined (GTK_VERSION3)
1667 #define GDK_Escape GDK_KEY_Escape
1668 #define GDK_Return GDK_KEY_Return
1669 #define GDK_Linefeed GDK_KEY_Linefeed
1670 #define GDK_Tab GDK_KEY_Tab
1671 #define GDK_s GDK_KEY_s
1672 #define GDK_S GDK_KEY_S
1673 #endif //GTK_VERSION3
1674
1675 static gboolean icvOnKeyPress(GtkWidget* widget, GdkEventKey* event, gpointer user_data)
1676 {
1677     int code = 0;
1678
1679     if ( BIT_ALLIN(event->state, GDK_CONTROL_MASK) && (event->keyval == GDK_s || event->keyval == GDK_S))
1680     {
1681         try
1682         {
1683             icvShowSaveAsDialog(widget, (CvWindow*)user_data);
1684         }
1685         catch(...)
1686         {
1687             // suppress all exceptions here
1688         }
1689     }
1690
1691     switch( event->keyval )
1692     {
1693     case GDK_Escape:
1694         code = 27;
1695         break;
1696     case GDK_Return:
1697     case GDK_Linefeed:
1698         code = 13;
1699         break;
1700     case GDK_Tab:
1701         code = '\t';
1702     break;
1703     default:
1704         code = event->keyval;
1705     }
1706
1707     code |= event->state << 16;
1708
1709 #ifdef HAVE_GTHREAD
1710     if(thread_started)
1711     {
1712         g_mutex_lock(last_key_mutex);
1713         last_key = code;
1714         // signal any waiting threads
1715         g_cond_broadcast(cond_have_key);
1716         g_mutex_unlock(last_key_mutex);
1717     }
1718     else
1719 #endif
1720     {
1721         last_key = code;
1722     }
1723
1724     return FALSE;
1725 }
1726
1727
1728 static void icvOnTrackbar( GtkWidget* widget, gpointer user_data )
1729 {
1730     int pos = cvRound( gtk_range_get_value(GTK_RANGE(widget)));
1731     CvTrackbar* trackbar = (CvTrackbar*)user_data;
1732
1733     if( trackbar && trackbar->signature == CV_TRACKBAR_MAGIC_VAL &&
1734         trackbar->widget == widget )
1735     {
1736         trackbar->pos = pos;
1737         if( trackbar->data )
1738             *trackbar->data = pos;
1739         if( trackbar->notify2 )
1740             trackbar->notify2(pos, trackbar->userdata);
1741         else if( trackbar->notify )
1742             trackbar->notify(pos);
1743     }
1744 }
1745
1746 static gboolean icvOnClose( GtkWidget* widget, GdkEvent* /*event*/, gpointer user_data )
1747 {
1748     CvWindow* window = (CvWindow*)user_data;
1749     if( window->signature == CV_WINDOW_MAGIC_VAL &&
1750         window->frame == widget )
1751     {
1752         icvDeleteWindow(window);
1753     }
1754     return TRUE;
1755 }
1756
1757
1758 static gboolean icvOnMouse( GtkWidget *widget, GdkEvent *event, gpointer user_data )
1759 {
1760     // TODO move this logic to CvImageWidget
1761     CvWindow* window = (CvWindow*)user_data;
1762     CvPoint2D32f pt32f(-1., -1.);
1763     CvPoint pt(-1,-1);
1764     int cv_event = -1, state = 0, flags = 0;
1765     CvImageWidget * image_widget = CV_IMAGE_WIDGET( widget );
1766
1767     if( window->signature != CV_WINDOW_MAGIC_VAL ||
1768         window->widget != widget || !window->widget ||
1769         !window->on_mouse /*|| !image_widget->original_image*/)
1770         return FALSE;
1771
1772     if( event->type == GDK_MOTION_NOTIFY )
1773     {
1774         GdkEventMotion* event_motion = (GdkEventMotion*)event;
1775
1776         cv_event = CV_EVENT_MOUSEMOVE;
1777         pt32f.x = cvRound(event_motion->x);
1778         pt32f.y = cvRound(event_motion->y);
1779         state = event_motion->state;
1780     }
1781     else if( event->type == GDK_BUTTON_PRESS ||
1782              event->type == GDK_BUTTON_RELEASE ||
1783              event->type == GDK_2BUTTON_PRESS )
1784     {
1785         GdkEventButton* event_button = (GdkEventButton*)event;
1786         pt32f.x = cvRound(event_button->x);
1787         pt32f.y = cvRound(event_button->y);
1788
1789
1790         if( event_button->type == GDK_BUTTON_PRESS )
1791         {
1792             cv_event = event_button->button == 1 ? CV_EVENT_LBUTTONDOWN :
1793                        event_button->button == 2 ? CV_EVENT_MBUTTONDOWN :
1794                        event_button->button == 3 ? CV_EVENT_RBUTTONDOWN : 0;
1795         }
1796         else if( event_button->type == GDK_BUTTON_RELEASE )
1797         {
1798             cv_event = event_button->button == 1 ? CV_EVENT_LBUTTONUP :
1799                        event_button->button == 2 ? CV_EVENT_MBUTTONUP :
1800                        event_button->button == 3 ? CV_EVENT_RBUTTONUP : 0;
1801         }
1802         else if( event_button->type == GDK_2BUTTON_PRESS )
1803         {
1804             cv_event = event_button->button == 1 ? CV_EVENT_LBUTTONDBLCLK :
1805                        event_button->button == 2 ? CV_EVENT_MBUTTONDBLCLK :
1806                        event_button->button == 3 ? CV_EVENT_RBUTTONDBLCLK : 0;
1807         }
1808         state = event_button->state;
1809     }
1810     else if( event->type == GDK_SCROLL )
1811     {
1812 #if defined(GTK_VERSION3_4)
1813         // NOTE: in current implementation doesn't possible to put into callback function delta_x and delta_y separetely
1814         double delta = (event->scroll.delta_x + event->scroll.delta_y);
1815         cv_event   = (event->scroll.delta_y!=0) ? CV_EVENT_MOUSEHWHEEL : CV_EVENT_MOUSEWHEEL;
1816 #else
1817         cv_event = CV_EVENT_MOUSEWHEEL;
1818 #endif //GTK_VERSION3_4
1819
1820         state    = event->scroll.state;
1821
1822         switch(event->scroll.direction) {
1823 #if defined(GTK_VERSION3_4)
1824         case GDK_SCROLL_SMOOTH: flags |= (((int)delta << 16));
1825             break;
1826 #endif //GTK_VERSION3_4
1827         case GDK_SCROLL_LEFT:  cv_event = CV_EVENT_MOUSEHWHEEL;
1828             /* FALLTHRU */
1829         case GDK_SCROLL_UP:    flags |= ~0xffff;
1830             break;
1831         case GDK_SCROLL_RIGHT: cv_event = CV_EVENT_MOUSEHWHEEL;
1832             /* FALLTHRU */
1833         case GDK_SCROLL_DOWN:  flags |= (((int)1 << 16));
1834             break;
1835         default: ;
1836         };
1837     }
1838
1839     if( cv_event >= 0 )
1840     {
1841         // scale point if image is scaled
1842         if( (image_widget->flags & CV_WINDOW_AUTOSIZE)==0 &&
1843              image_widget->original_image &&
1844              image_widget->scaled_image )
1845         {
1846             // image origin is not necessarily at (0,0)
1847 #if defined (GTK_VERSION3)
1848             int x0 = (gtk_widget_get_allocated_width(widget) - image_widget->scaled_image->cols)/2;
1849             int y0 = (gtk_widget_get_allocated_height(widget) - image_widget->scaled_image->rows)/2;
1850 #else
1851             int x0 = (widget->allocation.width - image_widget->scaled_image->cols)/2;
1852             int y0 = (widget->allocation.height - image_widget->scaled_image->rows)/2;
1853 #endif //GTK_VERSION3
1854             pt.x = cvFloor( ((pt32f.x-x0)*image_widget->original_image->cols)/
1855                                             image_widget->scaled_image->cols );
1856             pt.y = cvFloor( ((pt32f.y-y0)*image_widget->original_image->rows)/
1857                                             image_widget->scaled_image->rows );
1858         }
1859         else
1860         {
1861             pt = cvPointFrom32f( pt32f );
1862         }
1863
1864 //        if((unsigned)pt.x < (unsigned)(image_widget->original_image->width) &&
1865 //           (unsigned)pt.y < (unsigned)(image_widget->original_image->height) )
1866         {
1867             flags |= BIT_MAP(state, GDK_SHIFT_MASK,   CV_EVENT_FLAG_SHIFTKEY) |
1868                 BIT_MAP(state, GDK_CONTROL_MASK, CV_EVENT_FLAG_CTRLKEY)  |
1869                 BIT_MAP(state, GDK_MOD1_MASK,    CV_EVENT_FLAG_ALTKEY)   |
1870                 BIT_MAP(state, GDK_MOD2_MASK,    CV_EVENT_FLAG_ALTKEY)   |
1871                 BIT_MAP(state, GDK_BUTTON1_MASK, CV_EVENT_FLAG_LBUTTON)  |
1872                 BIT_MAP(state, GDK_BUTTON2_MASK, CV_EVENT_FLAG_MBUTTON)  |
1873                 BIT_MAP(state, GDK_BUTTON3_MASK, CV_EVENT_FLAG_RBUTTON);
1874             window->on_mouse( cv_event, pt.x, pt.y, flags, window->on_mouse_param );
1875         }
1876     }
1877
1878     return FALSE;
1879 }
1880
1881
1882 static gboolean icvAlarm( gpointer user_data )
1883 {
1884     *(int*)user_data = 1;
1885     return FALSE;
1886 }
1887
1888
1889 CV_IMPL int cvWaitKey( int delay )
1890 {
1891 #ifdef HAVE_GTHREAD
1892     if (thread_started && g_thread_self() != window_thread)
1893     {
1894         gboolean expired = true;
1895         int my_last_key;
1896
1897         g_mutex_lock(last_key_mutex);
1898         // wait for signal or timeout if delay > 0
1899         if(delay>0){
1900             GTimeVal timer;
1901             g_get_current_time(&timer);
1902             g_time_val_add(&timer, delay*1000);
1903             expired = !g_cond_timed_wait(cond_have_key, last_key_mutex, &timer);
1904         }
1905         else{
1906             if (g_windows.empty())
1907             {
1908                 CV_LOG_WARNING(NULL, "cv::waitKey() is called without timeout and missing active windows. Ignoring");
1909             }
1910             else
1911             {
1912                 g_cond_wait(cond_have_key, last_key_mutex);
1913                 expired=false;
1914             }
1915         }
1916         my_last_key = last_key;
1917         g_mutex_unlock(last_key_mutex);
1918         if(expired || g_windows.empty()){
1919             return -1;
1920         }
1921         return my_last_key;
1922     }
1923     else
1924 #endif
1925     {
1926         int expired = 0;
1927         guint timer = 0;
1928         if( delay > 0 )
1929             timer = g_timeout_add( delay, icvAlarm, &expired );
1930         last_key = -1;
1931         while( gtk_main_iteration_do(TRUE) && last_key < 0 && !expired && (delay > 0 || !g_windows.empty()))
1932             ;
1933
1934         if( delay > 0 && !expired )
1935             g_source_remove(timer);
1936     }
1937     return last_key;
1938 }
1939
1940
1941 #endif  // HAVE_GTK
1942 #endif  // _WIN32
1943
1944 /* End of file. */