/*M///////////////////////////////////////////////////////////////////////////////////////
//
//  IMPORTANT: READ BEFORE DOWNLOADING, COPYING, INSTALLING OR USING.
//
//  By downloading, copying, installing or using the software you agree to this license.
//  If you do not agree to this license, do not download, install,
//  copy or use the software.
//
//
//                        Intel License Agreement
//                For Open Source Computer Vision Library
//
// Copyright (C) 2000, Intel Corporation, all rights reserved.
// Third party copyrights are property of their respective owners.
//
// Redistribution and use in source and binary forms, with or without modification,
// are permitted provided that the following conditions are met:
//
//   * Redistribution's of source code must retain the above copyright notice,
//     this list of conditions and the following disclaimer.
//
//   * Redistribution's in binary form must reproduce the above copyright notice,
//     this list of conditions and the following disclaimer in the documentation
//     and/or other materials provided with the distribution.
//
//   * The name of Intel Corporation may not be used to endorse or promote products
//     derived from this software without specific prior written permission.
//
// This software is provided by the copyright holders and contributors "as is" and
// any express or implied warranties, including, but not limited to, the implied
// warranties of merchantability and fitness for a particular purpose are disclaimed.
// In no event shall the Intel Corporation or contributors be liable for any direct,
// indirect, incidental, special, exemplary, or consequential damages
// (including, but not limited to, procurement of substitute goods or services;
// loss of use, data, or profits; or business interruption) however caused
// and on any theory of liability, whether in contract, strict liability,
// or tort (including negligence or otherwise) arising in any way out of
// the use of this software, even if advised of the possibility of such damage.
//
//M*/

#include "precomp.hpp"
#include "backend.hpp"

#if defined (HAVE_GTK)

#include <gtk/gtk.h>

#if (GTK_MAJOR_VERSION == 3) && defined(HAVE_OPENGL)
  #undef HAVE_OPENGL  // no support with GTK3
#endif
#if defined(HAVE_OPENGL) && !defined(HAVE_GTKGLEXT)
  #undef HAVE_OPENGL  // gtkglext is required
#endif

#include <gdk/gdkkeysyms.h>
#include <gdk-pixbuf/gdk-pixbuf.h>
#include <stdio.h>

#if (GTK_MAJOR_VERSION == 3)
  #define GTK_VERSION3 1
#endif //GTK_MAJOR_VERSION >= 3
#if (GTK_MAJOR_VERSION > 3 || (GTK_MAJOR_VERSION == 3 && GTK_MINOR_VERSION >= 4))
  #define GTK_VERSION3_4 1
#endif

#ifdef HAVE_OPENGL
    #include <gtk/gtkgl.h>
    #include <GL/gl.h>
    #include <GL/glu.h>
#endif

#include <opencv2/core/utils/logger.hpp>
#include "opencv2/imgproc.hpp"

using namespace cv;

#ifndef BIT_ALLIN
    #define BIT_ALLIN(x,y) ( ((x)&(y)) == (y) )
#endif
#ifndef BIT_MAP
    #define BIT_MAP(x,y,z) ( ((x)&(y)) ? (z) : 0 )
#endif

// TODO Fix the initial window size when flags=0.  Right now the initial window is by default
// 320x240 size.  A better default would be actual size of the image.  Problem
// is determining desired window size with trackbars while still allowing resizing.
//
// Gnome Totem source may be of use here, see bacon_video_widget_set_scale_ratio
// in totem/src/backend/bacon-video-widget-xine.c

////////////////////////////////////////////////////////////
// CvImageWidget GTK Widget Public API
////////////////////////////////////////////////////////////
typedef struct _CvImageWidget        CvImageWidget;
typedef struct _CvImageWidgetClass   CvImageWidgetClass;

struct _CvImageWidget {
    GtkWidget widget;
    CvMat * original_image;
    CvMat * scaled_image;
    int flags;
};

struct _CvImageWidgetClass
{
  GtkWidgetClass parent_class;
};


/** Allocate new image viewer widget */
GtkWidget*     cvImageWidgetNew      (int flags);

// standard GTK object macros
#define CV_IMAGE_WIDGET(obj)          G_TYPE_CHECK_INSTANCE_CAST (obj, cvImageWidget_get_type (), CvImageWidget)
#define CV_IMAGE_WIDGET_CLASS(klass)  GTK_CHECK_CLASS_CAST (klass, cvImageWidget_get_type (), CvImageWidgetClass)
#define CV_IS_IMAGE_WIDGET(obj)       G_TYPE_CHECK_INSTANCE_TYPE (obj, cvImageWidget_get_type ())

/////////////////////////////////////////////////////////////////////////////
// Private API ////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////
GType        cvImageWidget_get_type (void);

static GtkWidgetClass * parent_class = NULL;

// flag to help size initial window
#define CV_WINDOW_NO_IMAGE 2

/** Set the image to display in the widget */
static
void cvImageWidgetSetImage(CvImageWidget * widget, const CvArr *arr)
{
    CvMat * mat, stub;
    int origin=0;

    //printf("cvImageWidgetSetImage\n");

    if( CV_IS_IMAGE_HDR( arr ))
        origin = ((IplImage*)arr)->origin;

    mat = cvGetMat(arr, &stub);

    if(widget->original_image && !CV_ARE_SIZES_EQ(mat, widget->original_image)){
        cvReleaseMat( &widget->original_image );
    }
    if(!widget->original_image){
        widget->original_image = cvCreateMat( mat->rows, mat->cols, CV_8UC3 );
        gtk_widget_queue_resize( GTK_WIDGET( widget ) );
    }
    CV_Assert(origin == 0);
    convertToShow(cv::cvarrToMat(arr), widget->original_image);
    if(widget->scaled_image){
        cvResize( widget->original_image, widget->scaled_image, CV_INTER_AREA );
    }

    // window does not refresh without this
    gtk_widget_queue_draw( GTK_WIDGET(widget) );
}

GtkWidget*
cvImageWidgetNew (int flags)
{
  CvImageWidget *image_widget;

  image_widget = CV_IMAGE_WIDGET( gtk_widget_new (cvImageWidget_get_type (), NULL) );
  CV_Assert(image_widget && "GTK widget creation is failed. Ensure that there is no GTK2/GTK3 libraries conflict");
  image_widget->original_image = 0;
  image_widget->scaled_image = 0;
  image_widget->flags = flags | CV_WINDOW_NO_IMAGE;

  return GTK_WIDGET (image_widget);
}

static void
cvImageWidget_realize (GtkWidget *widget)
{
  GdkWindowAttr attributes;
  gint attributes_mask;

#if defined(GTK_VERSION3)
  GtkAllocation allocation;
  gtk_widget_get_allocation(widget, &allocation);
#endif //GTK_VERSION3

  //printf("cvImageWidget_realize\n");
  g_return_if_fail (widget != NULL);
  g_return_if_fail (CV_IS_IMAGE_WIDGET (widget));

  gtk_widget_set_realized(widget, TRUE);

#if defined(GTK_VERSION3)
  attributes.x = allocation.x;
  attributes.y = allocation.y;
  attributes.width = allocation.width;
  attributes.height = allocation.height;
#else
  attributes.x = widget->allocation.x;
  attributes.y = widget->allocation.y;
  attributes.width = widget->allocation.width;
  attributes.height = widget->allocation.height;
#endif //GTK_VERSION3

  attributes.wclass = GDK_INPUT_OUTPUT;
  attributes.window_type = GDK_WINDOW_CHILD;
  attributes.event_mask = gtk_widget_get_events (widget) |
    GDK_EXPOSURE_MASK | GDK_BUTTON_PRESS_MASK |
    GDK_BUTTON_RELEASE_MASK | GDK_POINTER_MOTION_MASK;
  attributes.visual = gtk_widget_get_visual (widget);

#if defined(GTK_VERSION3)
  attributes_mask = GDK_WA_X | GDK_WA_Y | GDK_WA_VISUAL;
  gtk_widget_set_window(
      widget,
      gdk_window_new(
        gtk_widget_get_parent_window(widget),
        &attributes,
        attributes_mask
        )
      );

  gtk_widget_set_style(
      widget,
      gtk_style_attach(
        gtk_widget_get_style(widget),
        gtk_widget_get_window(widget)
        )
      );

  gdk_window_set_user_data (
      gtk_widget_get_window(widget),
      widget
      );

  gtk_style_set_background (
      gtk_widget_get_style(widget),
      gtk_widget_get_window(widget),
      GTK_STATE_ACTIVE
      );
 #else
  // The following lines are included to prevent breaking
  // compatibility with older Gtk2 (<gtk+-2.18) libraries.
  attributes.colormap = gtk_widget_get_colormap (widget);
  attributes_mask = GDK_WA_X | GDK_WA_Y | GDK_WA_VISUAL | GDK_WA_COLORMAP;
  widget->window = gdk_window_new (widget->parent->window, &attributes, attributes_mask);

  widget->style = gtk_style_attach (widget->style, widget->window);
  gdk_window_set_user_data (widget->window, widget);

  gtk_style_set_background (widget->style, widget->window, GTK_STATE_ACTIVE);
#endif // GTK_VERSION3
}

static CvSize cvImageWidget_calc_size( int im_width, int im_height, int max_width, int max_height ){
    float aspect = (float)im_width/(float)im_height;
    float max_aspect = (float)max_width/(float)max_height;
    if(aspect > max_aspect){
        return cvSize( max_width, cvRound(max_width/aspect) );
    }
    return cvSize( cvRound(max_height*aspect), max_height );
}

#if defined (GTK_VERSION3)
static void
cvImageWidget_get_preferred_width (GtkWidget *widget, gint *minimal_width, gint *natural_width)
{
  g_return_if_fail (widget != NULL);
  g_return_if_fail (CV_IS_IMAGE_WIDGET (widget));
  CvImageWidget * image_widget = CV_IMAGE_WIDGET( widget );

  if(image_widget->original_image != NULL) {
    *minimal_width = (image_widget->flags & CV_WINDOW_AUTOSIZE) != CV_WINDOW_AUTOSIZE ?
      gdk_window_get_width(gtk_widget_get_window(widget)) : image_widget->original_image->cols;
  }
  else {
    *minimal_width = 320;
  }

  if(image_widget->scaled_image != NULL) {
    *natural_width = *minimal_width < image_widget->scaled_image->cols ?
      image_widget->scaled_image->cols : *minimal_width;
  }
  else {
    *natural_width = *minimal_width;
  }
}

static void
cvImageWidget_get_preferred_height (GtkWidget *widget, gint *minimal_height, gint *natural_height)
{
  g_return_if_fail (widget != NULL);
  g_return_if_fail (CV_IS_IMAGE_WIDGET (widget));
  CvImageWidget * image_widget = CV_IMAGE_WIDGET( widget );

  if(image_widget->original_image != NULL) {
    *minimal_height = (image_widget->flags & CV_WINDOW_AUTOSIZE) != CV_WINDOW_AUTOSIZE ?
      gdk_window_get_height(gtk_widget_get_window(widget)) : image_widget->original_image->rows;
  }
  else {
    *minimal_height = 240;
  }

  if(image_widget->scaled_image != NULL) {
    *natural_height = *minimal_height < image_widget->scaled_image->rows ?
      image_widget->scaled_image->rows : *minimal_height;
  }
  else {
    *natural_height = *minimal_height;
  }
}

#else
static void
cvImageWidget_size_request (GtkWidget      *widget,
                       GtkRequisition *requisition)
{
    CvImageWidget * image_widget = CV_IMAGE_WIDGET( widget );

    //printf("cvImageWidget_size_request ");
    // the case the first time cvShowImage called or when AUTOSIZE
    if( image_widget->original_image &&
        ((image_widget->flags & CV_WINDOW_AUTOSIZE) ||
         (image_widget->flags & CV_WINDOW_NO_IMAGE)))
    {
        //printf("original ");
        requisition->width = image_widget->original_image->cols;
        requisition->height = image_widget->original_image->rows;
    }
    // default case
    else if(image_widget->scaled_image){
        //printf("scaled ");
        requisition->width = image_widget->scaled_image->cols;
        requisition->height = image_widget->scaled_image->rows;
    }
    // the case before cvShowImage called
    else{
        //printf("default ");
        requisition->width = 320;
        requisition->height = 240;
    }
    //printf("%d %d\n",requisition->width, requisition->height);
}
#endif //GTK_VERSION3

static void cvImageWidget_set_size(GtkWidget * widget, int max_width, int max_height){
    CvImageWidget * image_widget = CV_IMAGE_WIDGET( widget );

    //printf("cvImageWidget_set_size %d %d\n", max_width, max_height);

    // don't allow to set the size
    if(image_widget->flags & CV_WINDOW_AUTOSIZE) return;
    if(!image_widget->original_image) return;

    CvSize scaled_image_size = cvImageWidget_calc_size( image_widget->original_image->cols,
            image_widget->original_image->rows, max_width, max_height );

    if( image_widget->scaled_image &&
            ( image_widget->scaled_image->cols != scaled_image_size.width ||
              image_widget->scaled_image->rows != scaled_image_size.height ))
    {
        cvReleaseMat( &image_widget->scaled_image );
    }
    if( !image_widget->scaled_image ){
        image_widget->scaled_image = cvCreateMat( scaled_image_size.height, scaled_image_size.width, CV_8UC3 );


    }
    CV_Assert(image_widget->scaled_image);
}

static void
cvImageWidget_size_allocate (GtkWidget     *widget,
                        GtkAllocation *allocation)
{
  CvImageWidget *image_widget;

  //printf("cvImageWidget_size_allocate\n");
  g_return_if_fail (widget != NULL);
  g_return_if_fail (CV_IS_IMAGE_WIDGET (widget));
  g_return_if_fail (allocation != NULL);

#if defined (GTK_VERSION3)
  gtk_widget_set_allocation(widget, allocation);
#else
  widget->allocation = *allocation;
#endif //GTK_VERSION3
  image_widget = CV_IMAGE_WIDGET (widget);


  if( (image_widget->flags & CV_WINDOW_AUTOSIZE)==0 && image_widget->original_image ){
      // (re) allocated scaled image
      if( image_widget->flags & CV_WINDOW_NO_IMAGE ){
          cvImageWidget_set_size( widget, image_widget->original_image->cols,
                                          image_widget->original_image->rows);
      }
      else{
          cvImageWidget_set_size( widget, allocation->width, allocation->height );
      }
      cvResize( image_widget->original_image, image_widget->scaled_image, CV_INTER_AREA );
  }

  if (gtk_widget_get_realized (widget))
    {
      image_widget = CV_IMAGE_WIDGET (widget);

      if( image_widget->original_image &&
              ((image_widget->flags & CV_WINDOW_AUTOSIZE) ||
               (image_widget->flags & CV_WINDOW_NO_IMAGE)) )
      {
#if defined (GTK_VERSION3)
          allocation->width = image_widget->original_image->cols;
          allocation->height = image_widget->original_image->rows;
          gtk_widget_set_allocation(widget, allocation);
#else
          widget->allocation.width = image_widget->original_image->cols;
          widget->allocation.height = image_widget->original_image->rows;
#endif //GTK_VERSION3
          gdk_window_move_resize( gtk_widget_get_window(widget),
              allocation->x, allocation->y,
              image_widget->original_image->cols, image_widget->original_image->rows );
          if(image_widget->flags & CV_WINDOW_NO_IMAGE){
              image_widget->flags &= ~CV_WINDOW_NO_IMAGE;
              gtk_widget_queue_resize( GTK_WIDGET(widget) );
          }
      }
      else{
          gdk_window_move_resize (gtk_widget_get_window(widget),
                  allocation->x, allocation->y,
                  allocation->width, allocation->height );
      }
    }
}

#if defined (GTK_VERSION3)
static void
cvImageWidget_destroy (GtkWidget *object)
#else
static void
cvImageWidget_destroy (GtkObject *object)
#endif //GTK_VERSION3
{
  CvImageWidget *image_widget;

  g_return_if_fail (object != NULL);
  g_return_if_fail (CV_IS_IMAGE_WIDGET (object));

  image_widget = CV_IMAGE_WIDGET (object);

  cvReleaseMat( &image_widget->scaled_image );
  cvReleaseMat( &image_widget->original_image );

#if defined (GTK_VERSION3)
  if (GTK_WIDGET_CLASS (parent_class)->destroy)
    (* GTK_WIDGET_CLASS (parent_class)->destroy) (object);
#else
  if (GTK_OBJECT_CLASS (parent_class)->destroy)
    (* GTK_OBJECT_CLASS (parent_class)->destroy) (object);
#endif //GTK_VERSION3
}

static void cvImageWidget_class_init (gpointer g_class, gpointer /*class_data*/)
{
  CvImageWidgetClass* klass = (CvImageWidgetClass*)g_class;
#if defined (GTK_VERSION3)
  GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
#else
  GtkObjectClass *object_class;
  GtkWidgetClass *widget_class;

  object_class = (GtkObjectClass*) klass;
  widget_class = (GtkWidgetClass*) klass;
#endif //GTK_VERSION3

  parent_class = GTK_WIDGET_CLASS( g_type_class_peek (gtk_widget_get_type ()) );

#if defined (GTK_VERSION3)
  widget_class->destroy = cvImageWidget_destroy;
  widget_class->get_preferred_width = cvImageWidget_get_preferred_width;
  widget_class->get_preferred_height = cvImageWidget_get_preferred_height;
#else
  object_class->destroy = cvImageWidget_destroy;
  widget_class->size_request = cvImageWidget_size_request;
#endif //GTK_VERSION3

  widget_class->realize = cvImageWidget_realize;
  widget_class->size_allocate = cvImageWidget_size_allocate;
  widget_class->button_press_event = NULL;
  widget_class->button_release_event = NULL;
  widget_class->motion_notify_event = NULL;
}

static void
cvImageWidget_init(GTypeInstance* instance, gpointer /*g_class*/)
{
    CvImageWidget* image_widget = (CvImageWidget*)instance;
    image_widget->original_image=0;
    image_widget->scaled_image=0;
    image_widget->flags=0;
}

GType cvImageWidget_get_type (void){
  static GType image_type = 0;

  if (!image_type)
    {
      image_type = g_type_register_static_simple(
          GTK_TYPE_WIDGET,
          (gchar*) "CvImageWidget",
          sizeof(CvImageWidgetClass),
          cvImageWidget_class_init,
          sizeof(CvImageWidget),
          cvImageWidget_init,
          (GTypeFlags)0
          );
    }

  return image_type;
}
/////////////////////////////////////////////////////////////////////////////
// End CvImageWidget
/////////////////////////////////////////////////////////////////////////////


struct CvWindow;

struct CvUIBase {
    CvUIBase(int signature_) : signature(signature_) { }

    int signature;
};

struct CvTrackbar : CvUIBase
{
    CvTrackbar(const std::string& trackbar_name) :
        CvUIBase(CV_TRACKBAR_MAGIC_VAL),
        widget(NULL), name(trackbar_name),
        parent(NULL), data(NULL),
        pos(0), maxval(0), minval(0),
        notify(NULL), notify2(NULL),  // deprecated
        onChangeCallback(NULL), userdata(NULL)
    {
        // nothing
    }
    ~CvTrackbar()
    {
        // destroyed by parent window
    }

    GtkWidget* widget;
    std::string name;
    CvWindow* parent;  // TODO weak_ptr
    int* data;
    int pos;
    int maxval;
    int minval;
    CvTrackbarCallback notify;  // deprecated
    CvTrackbarCallback2 notify2;  // deprecated
    TrackbarCallback onChangeCallback;
    void* userdata;
};


struct CvWindow : CvUIBase
{
    CvWindow(const std::string& window_name) :
        CvUIBase(CV_WINDOW_MAGIC_VAL),
        widget(NULL), frame(NULL), paned(NULL), name(window_name),
        last_key(0), flags(0), status(0),
        on_mouse(NULL), on_mouse_param(NULL)
#ifdef HAVE_OPENGL
        ,useGl(false), glDrawCallback(NULL), glDrawData(NULL)
#endif
    {
        CV_LOG_INFO(NULL, "OpenCV/UI: creating GTK window: " << window_name);
    }
    ~CvWindow();
    void destroy();

    GtkWidget* widget;
    GtkWidget* frame;
    GtkWidget* paned;
    std::string name;

    int last_key;
    int flags;
    int status;//0 normal, 1 fullscreen (YV)

    CvMouseCallback on_mouse;
    void* on_mouse_param;

    std::vector< std::shared_ptr<CvTrackbar> > trackbars;

#ifdef HAVE_OPENGL
    bool useGl;

    CvOpenGlDrawCallback glDrawCallback;
    void* glDrawData;
#endif
};


static gboolean icvOnClose( GtkWidget* widget, GdkEvent* event, gpointer user_data );
static gboolean icvOnKeyPress( GtkWidget* widget, GdkEventKey* event, gpointer user_data );
static void icvOnTrackbar( GtkWidget* widget, gpointer user_data );
static gboolean icvOnMouse( GtkWidget *widget, GdkEvent *event, gpointer user_data );

#ifdef HAVE_GTHREAD
int thread_started=0;
static gpointer icvWindowThreadLoop(gpointer data);
GMutex*				   last_key_mutex = NULL;
GCond*				   cond_have_key = NULL;
GThread*			   window_thread = NULL;
#endif

static int             last_key = -1;

static
std::vector< std::shared_ptr<CvWindow> >& getGTKWindows()
{
    static std::vector< std::shared_ptr<CvWindow> > g_windows;
    return g_windows;
}

CV_IMPL int cvInitSystem( int argc, char** argv )
{
    static int wasInitialized = 0;
    static bool hasError = false;

    // check initialization status
    if( !wasInitialized )
    {
        if (!gtk_init_check(&argc, &argv))
        {
            hasError = true;
            wasInitialized = true;
            CV_Error(Error::StsError, "Can't initialize GTK backend");
        }

        setlocale(LC_NUMERIC,"C");

        #ifdef HAVE_OPENGL
            if (!gtk_gl_init_check(&argc, &argv))
            {
                hasError = true;
                wasInitialized = true;
                CV_Error(Error::StsError, "Can't initialize GTK-OpenGL backend");
            }
        #endif

        wasInitialized = 1;
    }
    if (hasError)
       CV_Error(Error::StsError, "GTK backend is not available");

    return 0;
}

CV_IMPL int cvStartWindowThread(){
#ifdef HAVE_GTHREAD
    cvInitSystem(0,NULL);
    if (!thread_started)
    {
#if !GLIB_CHECK_VERSION(2, 32, 0)  // https://github.com/GNOME/glib/blame/b4d58a7105bb9d75907233968bb534b38f9a6e43/glib/deprecated/gthread.h#L274
       if (!g_thread_supported ())
       {
           /* the GThread system wasn't inited, so init it */
           g_thread_init(NULL);
       }
#endif

       (void)getWindowMutex();  // force mutex initialization

       // protects the 'last key pressed' variable
       last_key_mutex = g_mutex_new();

       // conditional that indicates a key has been pressed
       cond_have_key = g_cond_new();

   #if !GLIB_CHECK_VERSION(2, 32, 0)
       // this is the window update thread
       window_thread = g_thread_create(icvWindowThreadLoop,
                       NULL, TRUE, NULL);
   #else
       window_thread = g_thread_new("OpenCV window update", icvWindowThreadLoop, NULL);
   #endif
    }
    thread_started = window_thread!=NULL;
    return thread_started;
#else
    return 0;
#endif
}

#ifdef HAVE_GTHREAD
gpointer icvWindowThreadLoop(gpointer /*data*/)
{
    while(1){
        {
            cv::AutoLock lock(getWindowMutex());
            gtk_main_iteration_do(FALSE);
        }

        // little sleep
        g_usleep(500);

        g_thread_yield();
    }
    return NULL;
}

#endif

#define CV_LOCK_MUTEX() cv::AutoLock lock(getWindowMutex())

static
std::shared_ptr<CvWindow> icvFindWindowByName(const std::string& name)
{
    auto& g_windows = getGTKWindows();
    for(size_t i = 0; i < g_windows.size(); ++i)
    {
        auto window = g_windows[i];
        if (!window)
            continue;
        if (window->name == name)
            return window;
    }
    return std::shared_ptr<CvWindow>();
}

static inline
std::shared_ptr<CvWindow> icvFindWindowByName(const char* name)
{
    CV_Assert(name);
    return icvFindWindowByName(std::string(name));
}


static CvWindow* icvWindowByWidget( GtkWidget* widget )
{
    auto& g_windows = getGTKWindows();
    for (size_t i = 0; i < g_windows.size(); ++i)
    {
        CvWindow* window = g_windows[i].get();
        if (window->widget == widget || window->frame == widget || window->paned == widget)
            return window;
    }
    return NULL;
}

static Rect getImageRect_(const std::shared_ptr<CvWindow>& window);

CvRect cvGetWindowRect_GTK(const char* name)
{
    CV_Assert(name && "NULL name string");

    CV_LOCK_MUTEX();
    const auto window = icvFindWindowByName(name);
    if (!window)
        CV_Error( CV_StsNullPtr, "NULL window" );

    return cvRect(getImageRect_(window));
}

static Rect getImageRect_(const std::shared_ptr<CvWindow>& window)
{
    CV_Assert(window);

    gint wx, wy;
#ifdef HAVE_OPENGL
    if (window->useGl) {
        gtk_widget_translate_coordinates(window->widget, gtk_widget_get_toplevel(window->widget), 0, 0, &wx, &wy);
        return Rect(wx, wy, window->widget->allocation.width, window->widget->allocation.height);
    }
#endif

    CvImageWidget * image_widget = CV_IMAGE_WIDGET( window->widget );
    gtk_widget_translate_coordinates(&image_widget->widget, gtk_widget_get_toplevel(&image_widget->widget), 0, 0, &wx, &wy);
    if (image_widget->scaled_image) {
#if defined (GTK_VERSION3)
      return Rect(wx, wy, MIN(image_widget->scaled_image->cols, gtk_widget_get_allocated_width(window->widget)),
          MIN(image_widget->scaled_image->rows, gtk_widget_get_allocated_height(window->widget)));
#else
      return Rect(wx, wy, MIN(image_widget->scaled_image->cols, window->widget->allocation.width),
          MIN(image_widget->scaled_image->rows, window->widget->allocation.height));
#endif //GTK_VERSION3
    } else if (image_widget->original_image) {
#if defined (GTK_VERSION3)
      return Rect(wx, wy, MIN(image_widget->original_image->cols, gtk_widget_get_allocated_width(window->widget)),
          MIN(image_widget->original_image->rows, gtk_widget_get_allocated_height(window->widget)));
#else
      return Rect(wx, wy, MIN(image_widget->original_image->cols, window->widget->allocation.width),
          MIN(image_widget->original_image->rows, window->widget->allocation.height));
#endif //GTK_VERSION3
    }

    return Rect(-1, -1, -1, -1);
}

double cvGetModeWindow_GTK(const char* name)//YV
{
    CV_Assert(name && "NULL name string");

    CV_LOCK_MUTEX();
    const auto window = icvFindWindowByName(name);
    if (!window)
        CV_Error( CV_StsNullPtr, "NULL window" );

    double result = window->status;
    return result;
}

static bool setModeWindow_(const std::shared_ptr<CvWindow>& window, int mode);
void cvSetModeWindow_GTK( const char* name, double prop_value)//Yannick Verdie
{
    CV_Assert(name && "NULL name string");

    CV_LOCK_MUTEX();

    const auto window = icvFindWindowByName(name);
    if (!window)
        CV_Error( CV_StsNullPtr, "NULL window" );

    setModeWindow_(window, (int)prop_value);
}

static bool setModeWindow_(const std::shared_ptr<CvWindow>& window, int mode)
{
    if (window->flags & CV_WINDOW_AUTOSIZE) //if the flag CV_WINDOW_AUTOSIZE is set
        return false;

    //so easy to do fullscreen here, Linux rocks !

    if (window->status == mode)
        return true;

    if (window->status==CV_WINDOW_FULLSCREEN && mode==CV_WINDOW_NORMAL)
    {
        gtk_window_unfullscreen(GTK_WINDOW(window->frame));
        window->status=CV_WINDOW_NORMAL;
        return true;
    }

    if (window->status==CV_WINDOW_NORMAL && mode==CV_WINDOW_FULLSCREEN)
    {
        gtk_window_fullscreen(GTK_WINDOW(window->frame));
        window->status=CV_WINDOW_FULLSCREEN;
        return true;
    }

    return false;
}

void setWindowTitle_GTK(const String& winname, const String& title)
{
    CV_LOCK_MUTEX();

    auto window = icvFindWindowByName(winname.c_str());

    if (!window)
    {
        namedWindow(winname);
        window = icvFindWindowByName(winname.c_str());
        CV_Assert(window);
    }

    gtk_window_set_title(GTK_WINDOW(window->frame), title.c_str());
}

double cvGetPropWindowAutoSize_GTK(const char* name)
{
    CV_Assert(name && "NULL name string");

    CV_LOCK_MUTEX();

    const auto window = icvFindWindowByName(name);
    if (!window)
        return -1; // keep silence here

    double result = window->flags & CV_WINDOW_AUTOSIZE;
    return result;
}

static double getRatioWindow_(const std::shared_ptr<CvWindow>& window);
double cvGetRatioWindow_GTK(const char* name)
{
    CV_Assert(name && "NULL name string");

    CV_LOCK_MUTEX();

    const auto window = icvFindWindowByName(name);
    if (!window)
        return -1; // keep silence here

    return getRatioWindow_(window);
}

static double getRatioWindow_(const std::shared_ptr<CvWindow>& window)
{
#if defined (GTK_VERSION3)
    double result = static_cast<double>(
        gtk_widget_get_allocated_width(window->widget)) / gtk_widget_get_allocated_height(window->widget);
#else
    double result = static_cast<double>(window->widget->allocation.width) / window->widget->allocation.height;
#endif // GTK_VERSION3
    return result;
}

double cvGetOpenGlProp_GTK(const char* name)
{
#ifdef HAVE_OPENGL
    CV_Assert(name && "NULL name string");

    CV_LOCK_MUTEX();

    const auto window = icvFindWindowByName(name);
    if (!window)
        return -1; // keep silence here

    double result = window->useGl;
    return result;
#else
    (void)name;
    return -1;
#endif
}


// OpenGL support

#ifdef HAVE_OPENGL

namespace
{
    void createGlContext(CvWindow* window)
    {
        GdkGLConfig* glconfig;

        // Try double-buffered visual
        glconfig = gdk_gl_config_new_by_mode((GdkGLConfigMode)(GDK_GL_MODE_RGB | GDK_GL_MODE_DEPTH | GDK_GL_MODE_DOUBLE));
        if (!glconfig)
            CV_Error( CV_OpenGlApiCallError, "Can't Create A GL Device Context" );

        // Set OpenGL-capability to the widget
        if (!gtk_widget_set_gl_capability(window->widget, glconfig, NULL, TRUE, GDK_GL_RGBA_TYPE))
            CV_Error( CV_OpenGlApiCallError, "Can't Create A GL Device Context" );

        window->useGl = true;
    }

    void drawGl(CvWindow* window)
    {
        GdkGLContext* glcontext = gtk_widget_get_gl_context(window->widget);
        GdkGLDrawable* gldrawable = gtk_widget_get_gl_drawable(window->widget);

        if (!gdk_gl_drawable_gl_begin (gldrawable, glcontext))
            CV_Error( CV_OpenGlApiCallError, "Can't Activate The GL Rendering Context" );

        glViewport(0, 0, window->widget->allocation.width, window->widget->allocation.height);

        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

        if (window->glDrawCallback)
            window->glDrawCallback(window->glDrawData);

        if (gdk_gl_drawable_is_double_buffered (gldrawable))
            gdk_gl_drawable_swap_buffers(gldrawable);
        else
            glFlush();

        gdk_gl_drawable_gl_end(gldrawable);
    }
}

#endif // HAVE_OPENGL

#if defined (GTK_VERSION3)
static gboolean cvImageWidget_draw(GtkWidget* widget, cairo_t *cr, gpointer data)
{
#ifdef HAVE_OPENGL
    CvWindow* window = (CvWindow*)data;

    if (window->useGl)
    {
        drawGl(window);
        return TRUE;
    }
#else
    (void)data;
#endif

  CvImageWidget *image_widget = NULL;
  GdkPixbuf *pixbuf = NULL;

  g_return_val_if_fail (widget != NULL, FALSE);
  g_return_val_if_fail (CV_IS_IMAGE_WIDGET (widget), FALSE);

  image_widget = CV_IMAGE_WIDGET (widget);

  if( image_widget->scaled_image ){
      // center image in available region
#if defined (GTK_VERSION3)
      int x0 = (gtk_widget_get_allocated_width(widget) - image_widget->scaled_image->cols)/2;
      int y0 = (gtk_widget_get_allocated_height(widget) - image_widget->scaled_image->rows)/2;
#else
      int x0 = (widget->allocation.width - image_widget->scaled_image->cols)/2;
      int y0 = (widget->allocation.height - image_widget->scaled_image->rows)/2;
#endif //GTK_VERSION3

#if defined (GTK_VERSION3)
      pixbuf = gdk_pixbuf_new_from_data(image_widget->scaled_image->data.ptr, GDK_COLORSPACE_RGB, false,
          8, MIN(image_widget->scaled_image->cols, gtk_widget_get_allocated_width(widget)),
          MIN(image_widget->scaled_image->rows, gtk_widget_get_allocated_height(widget)),
          image_widget->scaled_image->step, NULL, NULL);
#else
      pixbuf = gdk_pixbuf_new_from_data(image_widget->scaled_image->data.ptr, GDK_COLORSPACE_RGB, false,
          8, MIN(image_widget->scaled_image->cols, widget->allocation.width),
          MIN(image_widget->scaled_image->rows, widget->allocation.height),
          image_widget->scaled_image->step, NULL, NULL);
#endif //GTK_VERSION3

      gdk_cairo_set_source_pixbuf(cr, pixbuf, x0, y0);
  }
  else if( image_widget->original_image ){
#if defined (GTK_VERSION3)
      pixbuf = gdk_pixbuf_new_from_data(image_widget->original_image->data.ptr, GDK_COLORSPACE_RGB, false,
          8, MIN(image_widget->original_image->cols, gtk_widget_get_allocated_width(widget)),
          MIN(image_widget->original_image->rows, gtk_widget_get_allocated_height(widget)),
          image_widget->original_image->step, NULL, NULL);
#else
      pixbuf = gdk_pixbuf_new_from_data(image_widget->original_image->data.ptr, GDK_COLORSPACE_RGB, false,
          8, MIN(image_widget->original_image->cols, widget->allocation.width),
          MIN(image_widget->original_image->rows, widget->allocation.height),
          image_widget->original_image->step, NULL, NULL);
#endif //GTK_VERSION3
      gdk_cairo_set_source_pixbuf(cr, pixbuf, 0, 0);
  }

  cairo_paint(cr);
  if(pixbuf)
      g_object_unref(pixbuf);
  return TRUE;
}

#else
static gboolean cvImageWidget_expose(GtkWidget* widget, GdkEventExpose* event, gpointer data)
{
#ifdef HAVE_OPENGL
    CvWindow* window = (CvWindow*)data;

    if (window->useGl)
    {
        drawGl(window);
        return TRUE;
    }
#else
    (void)data;
#endif

  CvImageWidget *image_widget = NULL;
  cairo_t *cr = NULL;
  GdkPixbuf *pixbuf = NULL;

  g_return_val_if_fail (widget != NULL, FALSE);
  g_return_val_if_fail (CV_IS_IMAGE_WIDGET (widget), FALSE);
  g_return_val_if_fail (event != NULL, FALSE);

  if (event->count > 0)
    return FALSE;

  cr = gdk_cairo_create(widget->window);
  image_widget = CV_IMAGE_WIDGET (widget);

  if( image_widget->scaled_image ){
      // center image in available region
      int x0 = (widget->allocation.width - image_widget->scaled_image->cols)/2;
      int y0 = (widget->allocation.height - image_widget->scaled_image->rows)/2;

      pixbuf = gdk_pixbuf_new_from_data(image_widget->scaled_image->data.ptr, GDK_COLORSPACE_RGB, false,
          8, MIN(image_widget->scaled_image->cols, widget->allocation.width),
          MIN(image_widget->scaled_image->rows, widget->allocation.height),
          image_widget->scaled_image->step, NULL, NULL);

      gdk_cairo_set_source_pixbuf(cr, pixbuf, x0, y0);
  }
  else if( image_widget->original_image ){
      pixbuf = gdk_pixbuf_new_from_data(image_widget->original_image->data.ptr, GDK_COLORSPACE_RGB, false,
          8, MIN(image_widget->original_image->cols, widget->allocation.width),
          MIN(image_widget->original_image->rows, widget->allocation.height),
          image_widget->original_image->step, NULL, NULL);
      gdk_cairo_set_source_pixbuf(cr, pixbuf, 0, 0);
  }

  cairo_paint(cr);
  if(pixbuf)
      g_object_unref(pixbuf);
  cairo_destroy(cr);
  return TRUE;
}
#endif //GTK_VERSION3

static std::shared_ptr<CvWindow> namedWindow_(const std::string& name, int flags);
CV_IMPL int cvNamedWindow( const char* name, int flags )
{
    cvInitSystem(name ? 1 : 0,(char**)&name);
    CV_Assert(name && "NULL name string");

    CV_LOCK_MUTEX();

    // Check the name in the storage
    if (icvFindWindowByName(name))
    {
        return 1;
    }
    auto window = namedWindow_(name, flags);
    return window ? 1 : 0;
}

static std::shared_ptr<CvWindow> namedWindow_(const std::string& name, int flags)
{
    cvInitSystem(0, NULL);

    auto window_ptr = std::make_shared<CvWindow>(name);
    CvWindow* window = window_ptr.get();
    window->flags = flags;
    window->status = CV_WINDOW_NORMAL;//YV

    window->frame = gtk_window_new( GTK_WINDOW_TOPLEVEL );

    window->paned = gtk_vbox_new( FALSE, 0 );
    window->widget = cvImageWidgetNew( flags );
    gtk_box_pack_end( GTK_BOX(window->paned), window->widget, TRUE, TRUE, 0 );
    gtk_widget_show( window->widget );
    gtk_container_add( GTK_CONTAINER(window->frame), window->paned );
    gtk_widget_show( window->paned );

#ifndef HAVE_OPENGL
    if (flags & CV_WINDOW_OPENGL)
        CV_Error( CV_OpenGlNotSupported, "Library was built without OpenGL support" );
#else
    if (flags & CV_WINDOW_OPENGL)
        createGlContext(window);

    window->glDrawCallback = 0;
    window->glDrawData = 0;
#endif

    //
    // configure event handlers
    // TODO -- move this to CvImageWidget ?
    g_signal_connect( window->frame, "key-press-event",
                        G_CALLBACK(icvOnKeyPress), window );
    g_signal_connect( window->widget, "button-press-event",
                        G_CALLBACK(icvOnMouse), window );
    g_signal_connect( window->widget, "button-release-event",
                        G_CALLBACK(icvOnMouse), window );
    g_signal_connect( window->widget, "motion-notify-event",
                        G_CALLBACK(icvOnMouse), window );
    g_signal_connect( window->widget, "scroll-event",
                        G_CALLBACK(icvOnMouse), window );
    g_signal_connect( window->frame, "delete-event",
                        G_CALLBACK(icvOnClose), window );
#if defined(GTK_VERSION3)
    g_signal_connect( window->widget, "draw",
                        G_CALLBACK(cvImageWidget_draw), window );
#else
    g_signal_connect( window->widget, "expose-event",
                        G_CALLBACK(cvImageWidget_expose), window );
#endif //GTK_VERSION3


#if defined(GTK_VERSION3_4)
    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) ;
#else
    gtk_widget_add_events (window->widget, GDK_BUTTON_RELEASE_MASK | GDK_BUTTON_PRESS_MASK | GDK_POINTER_MOTION_MASK | GDK_SCROLL_MASK) ;
#endif //GTK_VERSION3_4

    gtk_widget_show( window->frame );
    gtk_window_set_title(GTK_WINDOW(window->frame), name.c_str());

    {
        AutoLock lock(getWindowMutex());
        getGTKWindows().push_back(window_ptr);
    }

    bool b_nautosize = ((flags & CV_WINDOW_AUTOSIZE) == 0);
    gtk_window_set_resizable( GTK_WINDOW(window->frame), b_nautosize );

    // allow window to be resized
    if( b_nautosize ){
        GdkGeometry geometry;
        geometry.min_width = 50;
        geometry.min_height = 50;
        gtk_window_set_geometry_hints( GTK_WINDOW( window->frame ), GTK_WIDGET( window->widget ),
            &geometry, (GdkWindowHints) (GDK_HINT_MIN_SIZE));
    }

#ifdef HAVE_OPENGL
    if (window->useGl)
        cvSetOpenGlContext(name.c_str());
#endif

    return window_ptr;
}


#ifdef HAVE_OPENGL

CV_IMPL void cvSetOpenGlContext(const char* name)
{
    GdkGLContext* glcontext;
    GdkGLDrawable* gldrawable;

    CV_Assert(name && "NULL name string");

    CV_LOCK_MUTEX();

    auto window = icvFindWindowByName(name);
    if (!window)
        CV_Error( CV_StsNullPtr, "NULL window" );

    if (!window->useGl)
        CV_Error( CV_OpenGlNotSupported, "Window doesn't support OpenGL" );

    glcontext = gtk_widget_get_gl_context(window->widget);
    gldrawable = gtk_widget_get_gl_drawable(window->widget);

    if (!gdk_gl_drawable_make_current(gldrawable, glcontext))
        CV_Error( CV_OpenGlApiCallError, "Can't Activate The GL Rendering Context" );
}

CV_IMPL void cvUpdateWindow(const char* name)
{
    CV_Assert(name && "NULL name string");

    CV_LOCK_MUTEX();

    auto window = icvFindWindowByName(name);
    if (!window)
        return;

    // window does not refresh without this
    gtk_widget_queue_draw( GTK_WIDGET(window->widget) );
}

CV_IMPL void cvSetOpenGlDrawCallback(const char* name, CvOpenGlDrawCallback callback, void* userdata)
{
    CV_Assert(name && "NULL name string");

    CV_LOCK_MUTEX();

    auto window = icvFindWindowByName(name);
    if( !window )
        return;

    if (!window->useGl)
        CV_Error( CV_OpenGlNotSupported, "Window was created without OpenGL context" );

    window->glDrawCallback = callback;
    window->glDrawData = userdata;
}

#endif // HAVE_OPENGL



CvWindow::~CvWindow()
{
    if (frame)
        destroy();
}

inline void CvWindow::destroy()
{
    CV_LOG_INFO(NULL, "OpenCV/UI: destroying GTK window: " << name);
    gtk_widget_destroy(frame);
    frame = nullptr;
}

static void checkLastWindow()
{
    // if last window...
    if (getGTKWindows().empty())
    {
#ifdef HAVE_GTHREAD
        if( thread_started )
        {
            // send key press signal to jump out of any waiting cvWaitKey's
            g_cond_broadcast( cond_have_key );
        }
        else
        {
#endif
            // Some GTK+ modules (like the Unity module) use GDBusConnection,
            // which has a habit of postponing cleanup by performing it via
            // idle sources added to the main loop. Since this was the last window,
            // we can assume that no event processing is going to happen in the
            // nearest future, so we should force that cleanup (by handling all pending
            // events) while we still have the chance.
            // This is not needed if thread_started is true, because the background
            // thread will process events continuously.
            while( gtk_events_pending() )
                gtk_main_iteration();
#ifdef HAVE_GTHREAD
        }
#endif
    }
}

static
void icvDeleteWindow_( CvWindow* window )
{
    AutoLock lock(getWindowMutex());
    auto& g_windows = getGTKWindows();
    bool found = false;
    for (auto i = g_windows.begin(); i != g_windows.end(); ++i)
    {
        if (i->get() == window)
        {
            g_windows.erase(i);
            found = true;
            break;
        }
    }
    CV_LOG_IF_WARNING(NULL, !found, "OpenCV/GTK: Can't destroy non-registered window");
    checkLastWindow();
}

CV_IMPL void cvDestroyWindow( const char* name )
{
    CV_Assert(name && "NULL name string");

    CV_LOCK_MUTEX();
    auto& g_windows = getGTKWindows();

    bool found = false;
    for (auto i = g_windows.begin(); i != g_windows.end(); ++i)
    {
        if (i->get()->name == name)
        {
            g_windows.erase(i);
            found = true;
            break;
        }
    }
    CV_LOG_IF_ERROR(NULL, !found, "OpenCV/GTK: Can't destroy non-registered window: '" << name << "'");

    checkLastWindow();
}


CV_IMPL void
cvDestroyAllWindows( void )
{
    CV_LOCK_MUTEX();

    getGTKWindows().clear();
    checkLastWindow();
}

// CvSize icvCalcOptimalWindowSize( CvWindow * window, CvSize new_image_size){
//     CvSize window_size;
//     GtkWidget * toplevel = gtk_widget_get_toplevel( window->frame );
//     gdk_drawable_get_size( GDK_DRAWABLE(toplevel->window),
//             &window_size.width, &window_size.height );

//     window_size.width = window_size.width + new_image_size.width - window->widget->allocation.width;
//     window_size.height = window_size.height + new_image_size.height - window->widget->allocation.height;

//     return window_size;
// }

CV_IMPL void
cvShowImage( const char* name, const CvArr* arr )
{
    CV_Assert(name && "NULL name string");

    CV_LOCK_MUTEX();

    auto window = icvFindWindowByName(name);
    if(!window)
    {
        cvNamedWindow(name, 1);
        window = icvFindWindowByName(name);
    }
    CV_Assert(window);

    if (arr)
    {
    #ifdef HAVE_OPENGL
        if (window->useGl)
        {
            cv::imshow(name, cv::cvarrToMat(arr));
            return;
        }
    #endif

        CvImageWidget * image_widget = CV_IMAGE_WIDGET( window->widget );
        cvImageWidgetSetImage( image_widget, arr );
    }
}

static void resizeWindow_(const std::shared_ptr<CvWindow>& window, int width, int height);
CV_IMPL void cvResizeWindow(const char* name, int width, int height )
{
    CV_Assert(name && "NULL name string");

    CV_LOCK_MUTEX();

    auto window = icvFindWindowByName(name);
    if(!window)
        return;

    return resizeWindow_(window, width, height);
}

static
void resizeWindow_(const std::shared_ptr<CvWindow>& window, int width, int height)
{
    CV_Assert(window);
    CvImageWidget* image_widget = CV_IMAGE_WIDGET( window->widget );
    //if(image_widget->flags & CV_WINDOW_AUTOSIZE)
        //EXIT;

    gtk_window_set_resizable( GTK_WINDOW(window->frame), 1 );
    gtk_window_resize( GTK_WINDOW(window->frame), width, height );

    // disable initial resize since presumably user wants to keep
    // this window size
    image_widget->flags &= ~CV_WINDOW_NO_IMAGE;
}


CV_IMPL void cvMoveWindow( const char* name, int x, int y )
{
    CV_Assert(name && "NULL name string");

    CV_LOCK_MUTEX();

    const auto window = icvFindWindowByName(name);
    if(!window)
        return;

    gtk_window_move( GTK_WINDOW(window->frame), x, y );
}

static
std::shared_ptr<CvTrackbar> icvFindTrackbarByName(const std::shared_ptr<CvWindow>& window, const std::string& name)
{
    CV_Assert(window);
    auto& trackbars = window->trackbars;
    for(size_t i = 0; i < trackbars.size(); ++i)
    {
        auto trackbar = trackbars[i];
        if (!trackbar)
            continue;
        if (trackbar->name == name)
            return trackbar;
    }
    return std::shared_ptr<CvTrackbar>();
}


static int
icvCreateTrackbar( const char* trackbar_name, const char* window_name,
                   int* val, int count, CvTrackbarCallback on_notify,
                   CvTrackbarCallback2 on_notify2, void* userdata )
{
    CV_Assert(window_name && "NULL window name");
    CV_Assert(trackbar_name && "NULL trackbar name");

    if( count <= 0 )
        CV_Error( CV_StsOutOfRange, "Bad trackbar maximal value" );

    CV_LOCK_MUTEX();

    const auto window = icvFindWindowByName(window_name);
    if(!window)
        return 0;

    auto trackbar_ = icvFindTrackbarByName(window, trackbar_name);
    if (!trackbar_)
    {
        trackbar_ = std::make_shared<CvTrackbar>(trackbar_name);
        CvTrackbar* trackbar = trackbar_.get();
        trackbar->parent = window.get();
        window->trackbars.push_back(trackbar_);

        GtkWidget* hscale_box = gtk_hbox_new( FALSE, 10 );
        GtkWidget* hscale_label = gtk_label_new( trackbar_name );
        GtkWidget* hscale = gtk_hscale_new_with_range( 0, count, 1 );
        gtk_scale_set_digits( GTK_SCALE(hscale), 0 );
        //gtk_scale_set_value_pos( hscale, GTK_POS_TOP );
        gtk_scale_set_draw_value( GTK_SCALE(hscale), TRUE );

        trackbar->widget = hscale;
        gtk_box_pack_start( GTK_BOX(hscale_box), hscale_label, FALSE, FALSE, 5 );
        gtk_widget_show( hscale_label );
        gtk_box_pack_start( GTK_BOX(hscale_box), hscale, TRUE, TRUE, 5 );
        gtk_widget_show( hscale );
        gtk_box_pack_start( GTK_BOX(window->paned), hscale_box, FALSE, FALSE, 5 );
        gtk_widget_show( hscale_box );
    }

    CvTrackbar* trackbar = trackbar_.get(); CV_DbgAssert(trackbar);

    if( val )
    {
        int value = *val;
        if( value < 0 )
            value = 0;
        if( value > count )
            value = count;
        gtk_range_set_value( GTK_RANGE(trackbar->widget), value );
        trackbar->pos = value;
        trackbar->data = val;
    }

    trackbar->maxval = count;
    trackbar->notify = on_notify;
    trackbar->notify2 = on_notify2;
    trackbar->userdata = userdata;
    g_signal_connect( trackbar->widget, "value-changed",
                        G_CALLBACK(icvOnTrackbar), trackbar );

    // queue a widget resize to trigger a window resize to
    // compensate for the addition of trackbars
    gtk_widget_queue_resize( GTK_WIDGET(window->widget) );

    return 1;
}

CV_IMPL int
cvCreateTrackbar( const char* trackbar_name, const char* window_name,
                  int* val, int count, CvTrackbarCallback on_notify )
{
    return icvCreateTrackbar(trackbar_name, window_name, val, count,
                             on_notify, 0, 0);
}

CV_IMPL int
cvCreateTrackbar2( const char* trackbar_name, const char* window_name,
                   int* val, int count, CvTrackbarCallback2 on_notify2,
                   void* userdata )
{
    return icvCreateTrackbar(trackbar_name, window_name, val, count,
                             0, on_notify2, userdata);
}

static
std::shared_ptr<CvTrackbar> createTrackbar_(
    const std::shared_ptr<CvWindow>& window, const std::string& name,
    int count,
    TrackbarCallback onChange, void* userdata
)
{
    CV_Assert(window);
    CV_Assert(!name.empty());

    if (count <= 0)
        CV_Error(Error::StsOutOfRange, "Bad trackbar maximal value");

    auto trackbar_ = std::make_shared<CvTrackbar>(name);
    CvTrackbar* trackbar = trackbar_.get();
    trackbar->parent = window.get();
    window->trackbars.push_back(trackbar_);

    GtkWidget* hscale_box = gtk_hbox_new( FALSE, 10 );
    GtkWidget* hscale_label = gtk_label_new(name.c_str());
    GtkWidget* hscale = gtk_hscale_new_with_range( 0, count, 1 );
    gtk_scale_set_digits( GTK_SCALE(hscale), 0 );
    //gtk_scale_set_value_pos( hscale, GTK_POS_TOP );
    gtk_scale_set_draw_value( GTK_SCALE(hscale), TRUE );

    trackbar->widget = hscale;
    gtk_box_pack_start( GTK_BOX(hscale_box), hscale_label, FALSE, FALSE, 5 );
    gtk_widget_show( hscale_label );
    gtk_box_pack_start( GTK_BOX(hscale_box), hscale, TRUE, TRUE, 5 );
    gtk_widget_show( hscale );
    gtk_box_pack_start( GTK_BOX(window->paned), hscale_box, FALSE, FALSE, 5 );
    gtk_widget_show( hscale_box );

    trackbar->maxval = count;
    trackbar->onChangeCallback = onChange;
    trackbar->userdata = userdata;
    g_signal_connect(trackbar->widget, "value-changed",
                     G_CALLBACK(icvOnTrackbar), trackbar);

    // queue a widget resize to trigger a window resize to
    // compensate for the addition of trackbars
    gtk_widget_queue_resize(GTK_WIDGET(window->widget));

    return trackbar_;
}


CV_IMPL void
cvSetMouseCallback( const char* window_name, CvMouseCallback on_mouse, void* param )
{
    CV_Assert(window_name && "NULL window name");

    CV_LOCK_MUTEX();

    const auto window = icvFindWindowByName(window_name);
    if (!window)
        return;

    window->on_mouse = on_mouse;
    window->on_mouse_param = param;
}


CV_IMPL int cvGetTrackbarPos( const char* trackbar_name, const char* window_name )
{
    CV_Assert(window_name && "NULL window name");
    CV_Assert(trackbar_name && "NULL trackbar name");

    CV_LOCK_MUTEX();

    const auto window = icvFindWindowByName(window_name);
    if (!window)
        return -1;

    const auto trackbar = icvFindTrackbarByName(window,trackbar_name);
    if (!trackbar)
        return -1;

    return trackbar->pos;
}

static void setTrackbarPos_(const std::shared_ptr<CvTrackbar>& trackbar, int pos);
CV_IMPL void cvSetTrackbarPos( const char* trackbar_name, const char* window_name, int pos )
{
    CV_Assert(window_name && "NULL window name");
    CV_Assert(trackbar_name && "NULL trackbar name");

    CV_LOCK_MUTEX();

    const auto window = icvFindWindowByName(window_name);
    if(!window)
        return;

    const auto trackbar = icvFindTrackbarByName(window, trackbar_name);
    if (!trackbar)
    {
        CV_Error( CV_StsNullPtr, "No trackbar found" );
    }

    return setTrackbarPos_(trackbar, pos);
}

static void setTrackbarPos_(const std::shared_ptr<CvTrackbar>& trackbar, int pos)
{
    CV_Assert(trackbar);
    CV_CheckLE(trackbar->minval, trackbar->maxval, "");

    pos = std::max(pos, trackbar->minval);
    pos = std::min(pos, trackbar->maxval);

    gtk_range_set_value( GTK_RANGE(trackbar->widget), pos );
}


CV_IMPL void cvSetTrackbarMax(const char* trackbar_name, const char* window_name, int maxval)
{
    CV_Assert(window_name && "NULL window name");
    CV_Assert(trackbar_name && "NULL trackbar name");

    CV_LOCK_MUTEX();

    const auto window = icvFindWindowByName(window_name);
    if(!window)
        return;

    const auto trackbar = icvFindTrackbarByName(window,trackbar_name);
    if(!trackbar)
        return;

    trackbar->maxval = maxval;
    if (trackbar->maxval >= trackbar->minval)
        gtk_range_set_range(GTK_RANGE(trackbar->widget), trackbar->minval, trackbar->maxval);
}


CV_IMPL void cvSetTrackbarMin(const char* trackbar_name, const char* window_name, int minval)
{
    CV_Assert(window_name && "NULL window name");
    CV_Assert(trackbar_name && "NULL trackbar name");

    CV_LOCK_MUTEX();

    const auto window = icvFindWindowByName(window_name);
    if(!window)
        return;

    const auto trackbar = icvFindTrackbarByName(window,trackbar_name);
    if(!trackbar)
        return;

    trackbar->minval = minval;
    if (trackbar->maxval >= trackbar->minval)
        gtk_range_set_range(GTK_RANGE(trackbar->widget), trackbar->minval, trackbar->maxval);
}


CV_IMPL void* cvGetWindowHandle( const char* window_name )
{
    CV_Assert(window_name && "NULL window name");

    CV_LOCK_MUTEX();

    const auto window = icvFindWindowByName(window_name);
    if(!window)
        return NULL;

    return (void*)window->widget;
}


CV_IMPL const char* cvGetWindowName( void* window_handle )
{
    CV_Assert(window_handle && "NULL window handle");

    CV_LOCK_MUTEX();

    CvWindow* window = icvWindowByWidget( (GtkWidget*)window_handle );
    if (window)
        return window->name.c_str();

    return ""; // FIXME: NULL?
}

static GtkFileFilter* icvMakeGtkFilter(const char* name, const char* patterns, GtkFileFilter* images)
{
    GtkFileFilter* filter = gtk_file_filter_new();
    gtk_file_filter_set_name(filter, name);

    while(patterns[0])
    {
        gtk_file_filter_add_pattern(filter, patterns);
        gtk_file_filter_add_pattern(images, patterns);
        patterns += strlen(patterns) + 1;
    }

    return filter;
}

static void icvShowSaveAsDialog(GtkWidget* widget, CvWindow* window)
{
    if (!window || !widget)
        return;

    CvImageWidget* image_widget = CV_IMAGE_WIDGET(window->widget);
    if (!image_widget || !image_widget->original_image)
        return;

    GtkWidget* dialog = gtk_file_chooser_dialog_new("Save As...",
                      GTK_WINDOW(widget),
                      GTK_FILE_CHOOSER_ACTION_SAVE,
                      GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
                      GTK_STOCK_SAVE, GTK_RESPONSE_ACCEPT,
                      NULL);
    gtk_file_chooser_set_do_overwrite_confirmation(GTK_FILE_CHOOSER(dialog), TRUE);

    cv::String sname = gtk_window_get_title(GTK_WINDOW(window->frame));
    sname = sname.substr(sname.find_last_of("\\/") + 1) + ".png";
    gtk_file_chooser_set_current_name(GTK_FILE_CHOOSER(dialog), sname.c_str());

    GtkFileFilter* filter_all = gtk_file_filter_new();
    gtk_file_filter_set_name(filter_all, "All Files");
    gtk_file_filter_add_pattern(filter_all, "*");

    GtkFileFilter* filter_images = gtk_file_filter_new();
    gtk_file_filter_set_name(filter_images, "All Images");

    GtkFileFilter* file_filters[] = {
        icvMakeGtkFilter("Portable Network Graphics files (*.png)",               "*.png\0",                             filter_images),
        icvMakeGtkFilter("JPEG files (*.jpeg;*.jpg;*.jpe)",                       "*.jpeg\0*.jpg\0*.jpe\0",              filter_images),
        icvMakeGtkFilter("Windows bitmap (*.bmp;*.dib)",                          "*.bmp\0*.dib\0",                      filter_images),
        icvMakeGtkFilter("TIFF Files (*.tiff;*.tif)",                             "*.tiff\0*.tif\0",                     filter_images),
        icvMakeGtkFilter("JPEG-2000 files (*.jp2)",                               "*.jp2\0",                             filter_images),
        icvMakeGtkFilter("WebP files (*.webp)",                                   "*.webp\0",                            filter_images),
        icvMakeGtkFilter("Portable image format (*.pbm;*.pgm;*.ppm;*.pxm;*.pnm)", "*.pbm\0*.pgm\0*.ppm\0*.pxm\0*.pnm\0", filter_images),
        icvMakeGtkFilter("OpenEXR Image files (*.exr)",                           "*.exr\0",                             filter_images),
        icvMakeGtkFilter("Radiance HDR (*.hdr;*.pic)",                            "*.hdr\0*.pic\0",                      filter_images),
        icvMakeGtkFilter("Sun raster files (*.sr;*.ras)",                         "*.sr\0*.ras\0",                       filter_images),
        filter_images,
        filter_all
    };

    for (size_t idx = 0; idx < sizeof(file_filters)/sizeof(file_filters[0]); ++idx)
        gtk_file_chooser_add_filter(GTK_FILE_CHOOSER(dialog), file_filters[idx]); // filter ownership is transferred to dialog
    gtk_file_chooser_set_filter(GTK_FILE_CHOOSER(dialog), filter_images);

    cv::String filename;
    if (gtk_dialog_run(GTK_DIALOG(dialog)) == GTK_RESPONSE_ACCEPT)
    {
        char* fname = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(dialog));
        filename = fname;
        g_free(fname);
    }
    gtk_widget_destroy(dialog);

    if (!filename.empty())
    {
        cv::Mat bgr;
        cv::cvtColor(cv::cvarrToMat(image_widget->original_image), bgr, cv::COLOR_RGB2BGR);
        cv::imwrite(filename, bgr);
    }
}

#if defined (GTK_VERSION3)
#define GDK_Escape GDK_KEY_Escape
#define GDK_Return GDK_KEY_Return
#define GDK_Linefeed GDK_KEY_Linefeed
#define GDK_Tab GDK_KEY_Tab
#define GDK_s GDK_KEY_s
#define GDK_S GDK_KEY_S
#endif //GTK_VERSION3

static gboolean icvOnKeyPress(GtkWidget* widget, GdkEventKey* event, gpointer user_data)
{
    int code = 0;

    if ( BIT_ALLIN(event->state, GDK_CONTROL_MASK) && (event->keyval == GDK_s || event->keyval == GDK_S))
    {
        try
        {
            icvShowSaveAsDialog(widget, (CvWindow*)user_data);
        }
        catch(...)
        {
            // suppress all exceptions here
        }
    }

    switch( event->keyval )
    {
    case GDK_Escape:
        code = 27;
        break;
    case GDK_Return:
    case GDK_Linefeed:
        code = 13;
        break;
    case GDK_Tab:
        code = '\t';
    break;
    default:
        code = event->keyval;
    }

    code |= event->state << 16;

#ifdef HAVE_GTHREAD
    if(thread_started)
    {
        g_mutex_lock(last_key_mutex);
        last_key = code;
        // signal any waiting threads
        g_cond_broadcast(cond_have_key);
        g_mutex_unlock(last_key_mutex);
    }
    else
#endif
    {
        last_key = code;
    }

    return FALSE;
}


static void icvOnTrackbar( GtkWidget* widget, gpointer user_data )
{
    int pos = cvRound( gtk_range_get_value(GTK_RANGE(widget)));
    CvTrackbar* trackbar = (CvTrackbar*)user_data;

    if( trackbar && trackbar->signature == CV_TRACKBAR_MAGIC_VAL &&
        trackbar->widget == widget )
    {
        trackbar->pos = pos;
        if (trackbar->onChangeCallback)
            trackbar->onChangeCallback(pos, trackbar->userdata);

        // deprecated
        if( trackbar->data )
            *trackbar->data = pos;
        if( trackbar->notify2 )
            trackbar->notify2(pos, trackbar->userdata);
        else if( trackbar->notify )
            trackbar->notify(pos);
    }
}

static gboolean icvOnClose( GtkWidget* widget, GdkEvent* /*event*/, gpointer user_data )
{
    CvWindow* window = (CvWindow*)user_data;
    if( window->signature == CV_WINDOW_MAGIC_VAL &&
        window->frame == widget )
    {
        try
        {
            icvDeleteWindow_(window);
        }
        catch (...)
        {
            CV_LOG_WARNING(NULL, "OpenCV/GTK: unexpected C++ exception in icvDeleteWindow_");
        }
    }
    return TRUE;
}


static gboolean icvOnMouse( GtkWidget *widget, GdkEvent *event, gpointer user_data )
{
    // TODO move this logic to CvImageWidget
    // TODO add try-catch wrappers into all callbacks
    CvWindow* window = (CvWindow*)user_data;
    if (!window || !widget ||
        window->signature != CV_WINDOW_MAGIC_VAL ||
        window->widget != widget ||
        !window->on_mouse)
        return FALSE;

    CvPoint2D32f pt32f = {-1., -1.};
    CvPoint pt = {-1,-1};
    int cv_event = -1, state = 0, flags = 0;
    CvImageWidget * image_widget = CV_IMAGE_WIDGET( widget );

    if( event->type == GDK_MOTION_NOTIFY )
    {
        GdkEventMotion* event_motion = (GdkEventMotion*)event;

        cv_event = CV_EVENT_MOUSEMOVE;
        pt32f.x = cvFloor(event_motion->x);
        pt32f.y = cvFloor(event_motion->y);
        state = event_motion->state;
    }
    else if( event->type == GDK_BUTTON_PRESS ||
             event->type == GDK_BUTTON_RELEASE ||
             event->type == GDK_2BUTTON_PRESS )
    {
        GdkEventButton* event_button = (GdkEventButton*)event;
        pt32f.x = cvFloor(event_button->x);
        pt32f.y = cvFloor(event_button->y);


        if( event_button->type == GDK_BUTTON_PRESS )
        {
            cv_event = event_button->button == 1 ? CV_EVENT_LBUTTONDOWN :
                       event_button->button == 2 ? CV_EVENT_MBUTTONDOWN :
                       event_button->button == 3 ? CV_EVENT_RBUTTONDOWN : 0;
        }
        else if( event_button->type == GDK_BUTTON_RELEASE )
        {
            cv_event = event_button->button == 1 ? CV_EVENT_LBUTTONUP :
                       event_button->button == 2 ? CV_EVENT_MBUTTONUP :
                       event_button->button == 3 ? CV_EVENT_RBUTTONUP : 0;
        }
        else if( event_button->type == GDK_2BUTTON_PRESS )
        {
            cv_event = event_button->button == 1 ? CV_EVENT_LBUTTONDBLCLK :
                       event_button->button == 2 ? CV_EVENT_MBUTTONDBLCLK :
                       event_button->button == 3 ? CV_EVENT_RBUTTONDBLCLK : 0;
        }
        state = event_button->state;
    }
    else if( event->type == GDK_SCROLL )
    {
#if defined(GTK_VERSION3_4)
        // NOTE: in current implementation doesn't possible to put into callback function delta_x and delta_y separately
        double delta = (event->scroll.delta_x + event->scroll.delta_y);
        cv_event   = (event->scroll.delta_y!=0) ? CV_EVENT_MOUSEHWHEEL : CV_EVENT_MOUSEWHEEL;
#else
        cv_event = CV_EVENT_MOUSEWHEEL;
#endif //GTK_VERSION3_4

        state    = event->scroll.state;

        switch(event->scroll.direction) {
#if defined(GTK_VERSION3_4)
        case GDK_SCROLL_SMOOTH: flags |= (((int)delta << 16));
            break;
#endif //GTK_VERSION3_4
        case GDK_SCROLL_LEFT:  cv_event = CV_EVENT_MOUSEHWHEEL;
            /* FALLTHRU */
        case GDK_SCROLL_UP:    flags |= ~0xffff;
            break;
        case GDK_SCROLL_RIGHT: cv_event = CV_EVENT_MOUSEHWHEEL;
            /* FALLTHRU */
        case GDK_SCROLL_DOWN:  flags |= (((int)1 << 16));
            break;
        default: ;
        };
    }

    if( cv_event >= 0 )
    {
        // scale point if image is scaled
        if( (image_widget->flags & CV_WINDOW_AUTOSIZE)==0 &&
             image_widget->original_image &&
             image_widget->scaled_image )
        {
            // image origin is not necessarily at (0,0)
#if defined (GTK_VERSION3)
            int x0 = (gtk_widget_get_allocated_width(widget) - image_widget->scaled_image->cols)/2;
            int y0 = (gtk_widget_get_allocated_height(widget) - image_widget->scaled_image->rows)/2;
#else
            int x0 = (widget->allocation.width - image_widget->scaled_image->cols)/2;
            int y0 = (widget->allocation.height - image_widget->scaled_image->rows)/2;
#endif //GTK_VERSION3
            pt.x = cvFloor( ((pt32f.x-x0)*image_widget->original_image->cols)/
                                            image_widget->scaled_image->cols );
            pt.y = cvFloor( ((pt32f.y-y0)*image_widget->original_image->rows)/
                                            image_widget->scaled_image->rows );
        }
        else
        {
            pt = cvPointFrom32f( pt32f );
        }

        if (!image_widget->original_image/*OpenGL*/ || (
               (unsigned)pt.x < (unsigned)(image_widget->original_image->width) &&
               (unsigned)pt.y < (unsigned)(image_widget->original_image->height)
            ))
        {
            state &= gtk_accelerator_get_default_mod_mask();
            flags |= BIT_MAP(state, GDK_SHIFT_MASK,   CV_EVENT_FLAG_SHIFTKEY) |
                BIT_MAP(state, GDK_CONTROL_MASK, CV_EVENT_FLAG_CTRLKEY)  |
                BIT_MAP(state, GDK_MOD1_MASK,    CV_EVENT_FLAG_ALTKEY)   |
                BIT_MAP(state, GDK_MOD2_MASK,    CV_EVENT_FLAG_ALTKEY)   |
                BIT_MAP(state, GDK_BUTTON1_MASK, CV_EVENT_FLAG_LBUTTON)  |
                BIT_MAP(state, GDK_BUTTON2_MASK, CV_EVENT_FLAG_MBUTTON)  |
                BIT_MAP(state, GDK_BUTTON3_MASK, CV_EVENT_FLAG_RBUTTON);
            window->on_mouse( cv_event, pt.x, pt.y, flags, window->on_mouse_param );
        }
    }

    return FALSE;
}


static gboolean icvAlarm( gpointer user_data )
{
    *(int*)user_data = 1;
    return FALSE;
}


CV_IMPL int cvWaitKey( int delay )
{
#ifdef HAVE_GTHREAD
    if (thread_started && g_thread_self() != window_thread)
    {
        gboolean expired = true;
        int my_last_key;

        g_mutex_lock(last_key_mutex);
        // wait for signal or timeout if delay > 0
        if(delay>0){
            GTimeVal timer;
            g_get_current_time(&timer);
            g_time_val_add(&timer, delay*1000);
            expired = !g_cond_timed_wait(cond_have_key, last_key_mutex, &timer);
        }
        else{
            if (getGTKWindows().empty())
            {
                CV_LOG_WARNING(NULL, "cv::waitKey() is called without timeout and missing active windows. Ignoring");
            }
            else
            {
                g_cond_wait(cond_have_key, last_key_mutex);
                expired=false;
            }
        }
        my_last_key = last_key;
        g_mutex_unlock(last_key_mutex);
        if (expired || getGTKWindows().empty())
        {
            return -1;
        }
        return my_last_key;
    }
    else
#endif
    {
        int expired = 0;
        guint timer = 0;
        if( delay > 0 )
            timer = g_timeout_add( delay, icvAlarm, &expired );
        last_key = -1;
        while( gtk_main_iteration_do(TRUE) && last_key < 0 && !expired && (delay > 0 || !getGTKWindows().empty()))
            ;

        if( delay > 0 && !expired )
            g_source_remove(timer);
    }
    return last_key;
}

namespace cv { namespace impl {

using namespace cv::highgui_backend;

class GTKTrackbar;

class GTKWindow
        : public UIWindow
        , public std::enable_shared_from_this<GTKWindow>
{
protected:
    const std::string name_;
    std::weak_ptr<CvWindow> window_;
    std::map<std::string, std::shared_ptr<GTKTrackbar> > trackbars_;
public:
    GTKWindow(const std::string& name, const std::shared_ptr<CvWindow>& window)
        : name_(name)
        , window_(window)
    {
        // nothing
    }

    ~GTKWindow() CV_OVERRIDE
    {
        if (!window_.expired())
            destroy();
        CV_LOG_DEBUG(NULL, "OpenCV/UI/GTK: GTKWindow(" << name_ << ") is disposed");
    }

    const std::string& getID() const CV_OVERRIDE { return name_; }

    bool isActive() const CV_OVERRIDE { return !window_.expired(); }

    void destroy() CV_OVERRIDE
    {
        cv::AutoLock lock(getWindowMutex());
        if (!window_.expired())
        {
            auto window = window_.lock();
            if (window)
                window->destroy();
            window_.reset();
        }
    }

    void imshow(InputArray image) CV_OVERRIDE
    {
        auto window = window_.lock();
        CV_Assert(window);
        CvImageWidget* image_widget = CV_IMAGE_WIDGET(window->widget);
        CV_Assert(image_widget);
        Mat img = image.getMat();
        CvMat c_img = cvMat(img);  // TODO Drop C-API
        cvImageWidgetSetImage(image_widget, &c_img);
    }

    double getProperty(int prop) const CV_OVERRIDE
    {
        auto window = window_.lock();
        CV_Assert(window);
        // see cvGetWindowProperty
        switch (prop)
        {
        case CV_WND_PROP_FULLSCREEN:
            return (double)window->status;

        case CV_WND_PROP_AUTOSIZE:
            return (window->flags & CV_WINDOW_AUTOSIZE) ? 1.0 : 0.0;

        case CV_WND_PROP_ASPECTRATIO:
            return getRatioWindow_(window);

#ifdef HAVE_OPENGL
        case CV_WND_PROP_OPENGL:
            return window->useGl ? 1.0 : 0.0;
#endif

        default:
            break;
        }
        return std::numeric_limits<double>::quiet_NaN();
    }

    bool setProperty(int prop, double value) CV_OVERRIDE
    {
        auto window = window_.lock();
        CV_Assert(window);
        // see cvSetWindowProperty
        switch (prop)
        {
        case CV_WND_PROP_FULLSCREEN:
            if (value != CV_WINDOW_NORMAL && value != CV_WINDOW_FULLSCREEN)  // bad arg
                break;
            setModeWindow_(window, value);
            return true;

        default:
            break;
        }
        return false;
    }

    void resize(int width, int height) CV_OVERRIDE
    {
        auto window = window_.lock();
        CV_Assert(window);
        resizeWindow_(window, width, height);
    }

    void move(int x, int y) CV_OVERRIDE
    {
        auto window = window_.lock();
        CV_Assert(window);
        gtk_window_move(GTK_WINDOW(window->frame), x, y);
    }

    Rect getImageRect() const CV_OVERRIDE
    {
        auto window = window_.lock();
        CV_Assert(window);
        return getImageRect_(window);
    }

    void setTitle(const std::string& title) CV_OVERRIDE
    {
        auto window = window_.lock();
        CV_Assert(window);
        gtk_window_set_title(GTK_WINDOW(window->frame), title.c_str());
    }

    void setMouseCallback(MouseCallback onMouse, void* userdata /*= 0*/) CV_OVERRIDE
    {
        auto window = window_.lock();
        CV_Assert(window);
        window->on_mouse = onMouse;
        window->on_mouse_param = userdata;
    }

    std::shared_ptr<UITrackbar> createTrackbar(
        const std::string& name,
        int count,
        TrackbarCallback onChange /*= 0*/,
        void* userdata /*= 0*/
    ) CV_OVERRIDE
    {
        auto window = window_.lock();
        CV_Assert(window);
        CV_LOG_INFO(NULL, "OpenCV/UI: Creating GTK trackbar at '" << name_ << "': '" << name << "'");
        auto trackbar = createTrackbar_(window, name, count, onChange, userdata);
        auto ui_trackbar = std::make_shared<GTKTrackbar>(name, trackbar, shared_from_this());
        {
            cv::AutoLock lock(getWindowMutex());
            trackbars_.emplace(name, ui_trackbar);
        }
        return std::static_pointer_cast<UITrackbar>(ui_trackbar);
    }

    std::shared_ptr<UITrackbar> findTrackbar(const std::string& name) CV_OVERRIDE
    {
        cv::AutoLock lock(getWindowMutex());
        auto i = trackbars_.find(name);
        if (i != trackbars_.end())
        {
            return std::static_pointer_cast<UITrackbar>(i->second);
        }
        return std::shared_ptr<UITrackbar>();
    }
};  // GTKWindow


class GTKTrackbar : public UITrackbar
{
protected:
    /*const*/ std::string name_;
    std::weak_ptr<CvTrackbar> trackbar_;
    std::weak_ptr<GTKWindow> parent_;
    std::map<std::string, std::shared_ptr<GTKTrackbar> > trackbars_;
public:
    GTKTrackbar(const std::string& name, const std::shared_ptr<CvTrackbar>& trackbar, const std::shared_ptr<GTKWindow>& parent)
        : trackbar_(trackbar)
        , parent_(parent)
    {
        name_ = std::string("<") + name + ">@" + parent->getID();
    }

    ~GTKTrackbar() CV_OVERRIDE
    {
        if (!trackbar_.expired())
            destroy();
        CV_LOG_DEBUG(NULL, "OpenCV/UI/GTK: GTKTrackbar(" << name_ << ") is disposed");
    }

    const std::string& getID() const CV_OVERRIDE { return name_; }

    bool isActive() const CV_OVERRIDE { return !trackbar_.expired(); }

    void destroy() CV_OVERRIDE
    {
        // nothing (destroyed with parent window, dedicated trackbar removal is not supported)
    }

    int getPos() const CV_OVERRIDE
    {
        auto trackbar = trackbar_.lock();
        CV_Assert(trackbar);
        return trackbar->pos;
    }
    void setPos(int pos) CV_OVERRIDE
    {
        auto trackbar = trackbar_.lock();
        CV_Assert(trackbar);
        return setTrackbarPos_(trackbar, pos);
    }

    cv::Range getRange() const CV_OVERRIDE
    {
        auto trackbar = trackbar_.lock();
        CV_Assert(trackbar);
        return cv::Range(trackbar->minval, trackbar->maxval);
    }

    void setRange(const cv::Range& range) CV_OVERRIDE
    {
        auto trackbar = trackbar_.lock();
        CV_Assert(trackbar);
        CV_CheckLE(range.start, range.end, "Invalid trackbar range");
        gtk_range_set_range(GTK_RANGE(trackbar->widget), range.start, range.end);
    }
};  // GTKTrackbar


class GTKBackendUI : public UIBackend
{
public:
    GTKBackendUI()
    {
        // NB: avoid static initialization order fiasco
        (void)getGTKWindows();
    }
    ~GTKBackendUI() CV_OVERRIDE
    {
        destroyAllWindows();
    }

    void destroyAllWindows() CV_OVERRIDE
    {
        cvDestroyAllWindows();
    }

    // namedWindow
    virtual std::shared_ptr<UIWindow> createWindow(
        const std::string& winname,
        int flags
    ) CV_OVERRIDE
    {
        CV_LOG_INFO(NULL, "OpenCV/UI: Creating GTK window: " << winname << " (" << flags << ")");
        auto window = namedWindow_(winname, flags);
        auto ui_window = std::make_shared<GTKWindow>(winname, window);
        return ui_window;
    }

    int waitKeyEx(int delay) CV_OVERRIDE
    {
        return cvWaitKey(delay);
    }
    int pollKey() CV_OVERRIDE
    {
        return cvWaitKey(1);  // TODO
    }
};  // GTKBackendUI

static
std::shared_ptr<GTKBackendUI>& getInstance()
{
    static std::shared_ptr<GTKBackendUI> g_instance = std::make_shared<GTKBackendUI>();
    return g_instance;
}

} // namespace impl

#ifndef BUILD_PLUGIN
namespace highgui_backend {

std::shared_ptr<UIBackend> createUIBackendGTK()
{
    return impl::getInstance();
}

}  // namespace highgui_backend
#endif

}  // namespace

#ifdef BUILD_PLUGIN

#define ABI_VERSION 0
#define API_VERSION 0
#include "plugin_api.hpp"

static
CvResult cv_getInstance(CV_OUT CvPluginUIBackend* handle) CV_NOEXCEPT
{
    try
    {
        if (!handle)
            return CV_ERROR_FAIL;
        *handle = cv::impl::getInstance().get();
        return CV_ERROR_OK;
    }
    catch (...)
    {
        return CV_ERROR_FAIL;
    }
}

static const OpenCV_UI_Plugin_API plugin_api =
{
    {
        sizeof(OpenCV_UI_Plugin_API), ABI_VERSION, API_VERSION,
        CV_VERSION_MAJOR, CV_VERSION_MINOR, CV_VERSION_REVISION, CV_VERSION_STATUS,
        "GTK" CVAUX_STR(GTK_MAJOR_VERSION) " OpenCV UI plugin"
    },
    {
        /*  1*/cv_getInstance
    }
};

const OpenCV_UI_Plugin_API* CV_API_CALL opencv_ui_plugin_init_v0(int requested_abi_version, int requested_api_version, void* /*reserved=NULL*/) CV_NOEXCEPT
{
    if (requested_abi_version == ABI_VERSION && requested_api_version <= API_VERSION)
        return &plugin_api;
    return NULL;
}

#endif  // BUILD_PLUGIN

#endif  // HAVE_GTK
