Search code examples
cgtk

How to hide Gtk Popup Window when user clickes outside the window


I have developed one popup window (Non decorated) using GTK+ and glade tool in C. It popup on its parent window when a button clicked. I want to destroy or hide this popup window when user clicks out side this window. User can click on parent window or any other window. I have tried to capture GDK_FOCUS_CHANGE event but I am not able to capture this event. Is there any way to achieve this? How do I know that click is on other window then pop up window? How is it clear that pop up window has lost it's focus? So that I can hide it. The relevant code is as follow:

/*
 * Compile me with:

 gcc -o popup popup.c $(pkg-config --cflags --libs gtk+-2.0 gmodule-2.0)
*/

#include <gtk/gtk.h>

static void on_popup_clicked (GtkButton*, GtkWidget*);
static gboolean on_popup_window_event(GtkWidget*, GdkEventExpose*);

int main (int argc, char *argv[])
{
    GtkWidget *window, *button, *vbox;

    gtk_init (&argc, &argv);

    window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
    gtk_window_set_title (GTK_WINDOW (window), "Parent window");
    gtk_container_set_border_width (GTK_CONTAINER (window), 10);
    gtk_widget_set_size_request (window, 300, 300);
    gtk_window_set_position (GTK_WINDOW (window),GTK_WIN_POS_CENTER);

    button = gtk_button_new_with_label("Pop Up");
    g_signal_connect (G_OBJECT (button), "clicked",G_CALLBACK (on_popup_clicked),(gpointer) window);

    vbox = gtk_vbox_new (FALSE, 3);
    gtk_box_pack_end(GTK_BOX (vbox), button, FALSE, FALSE, 5);
    gtk_container_add (GTK_CONTAINER (window), vbox);

    gtk_widget_show_all (window);
    gtk_main ();
    return 0;
}

void on_popup_clicked (GtkButton* button, GtkWidget* pWindow)
{
    GtkWidget *popup_window;
    popup_window = gtk_window_new (GTK_WINDOW_POPUP);
    gtk_window_set_title (GTK_WINDOW (popup_window), "Pop Up window");
    gtk_container_set_border_width (GTK_CONTAINER (popup_window), 10);
    gtk_window_set_resizable(GTK_WINDOW (popup_window), FALSE);
    gtk_window_set_decorated(GTK_WINDOW (popup_window), FALSE);
    gtk_widget_set_size_request (popup_window, 150, 150);
    gtk_window_set_transient_for(GTK_WINDOW (popup_window),GTK_WINDOW (pWindow));
    gtk_window_set_position (GTK_WINDOW (popup_window),GTK_WIN_POS_CENTER);
    g_signal_connect (G_OBJECT (button), "event",
                        G_CALLBACK (on_popup_window_event),NULL);

    GdkColor color;
    gdk_color_parse("#3b3131", &color);
    gtk_widget_modify_bg(GTK_WIDGET(popup_window), GTK_STATE_NORMAL, &color);


    gtk_widget_show_all (popup_window);
}

gboolean on_popup_window_event(GtkWidget *popup_window, GdkEventExpose *event)
{
    if(event->type == GDK_FOCUS_CHANGE)
        gtk_widget_hide (popup_window);

    return FALSE;
}

Here I am not able to hide this pop up window when user clicks on parent window or on other window. How can I do this?

I have to stick with Gtk+2.14 version.


Solution

  • Changes:

    • switch from GTK_WINDOW_POPUP to GTK_WINDOW_TOPLEVEL, counter-intuitive, but I could not figure out how to get a popup to accept focus.
    • add gtk_window hints to prevent popup from showing in taskbar and pager
    • intentionally set the focus on the popup window
    • set the GDK_FOCUS_CHANGE_MASK on the GDK_WINDOW with gtk_widget_set_events (required for the next step)
    • connect to the focus-out-event of the popup window
    • change the signal handler to handle a different signal

    I would also suggest reading the GTK+ source to see how it handles popup windows for tooltips and menus when they are shown... but those are usually destroyed based on the mouse moving out of range, not the popup losing focus, per se.

    
    #include 
    
    static void on_popup_clicked (GtkButton*, GtkWidget*);
    gboolean on_popup_focus_out (GtkWidget*, GdkEventFocus*, gpointer);
    
    int
    main (int argc, char *argv[])
    {
      GtkWidget *window, *button, *vbox;
    
      gtk_init (&argc, &argv);
    
      window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
      gtk_window_set_title (GTK_WINDOW (window), "Parent window");
      gtk_container_set_border_width (GTK_CONTAINER (window), 10);
      gtk_widget_set_size_request (window, 300, 300);
      gtk_window_set_position (GTK_WINDOW (window), GTK_WIN_POS_CENTER);
    
      button = gtk_button_new_with_label ("Pop Up");
      g_signal_connect (G_OBJECT (button),
                        "clicked",
                        G_CALLBACK (on_popup_clicked),
                        (gpointer) window);
    
      vbox = gtk_vbox_new (FALSE, 3);
      gtk_box_pack_end (GTK_BOX (vbox), button, FALSE, FALSE, 5);
      gtk_container_add (GTK_CONTAINER (window), vbox);
    
      gtk_widget_show_all (window);
      gtk_main ();
      return 0;
    }
    
    void
    on_popup_clicked (GtkButton* button, GtkWidget* pWindow)
    {
      GtkWidget *popup_window;
    
      popup_window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
      gtk_window_set_title (GTK_WINDOW (popup_window), "Pop Up window");
      gtk_container_set_border_width (GTK_CONTAINER (popup_window), 10);
      gtk_window_set_resizable (GTK_WINDOW (popup_window), FALSE);
      gtk_window_set_decorated (GTK_WINDOW (popup_window), FALSE);
      gtk_window_set_skip_taskbar_hint (GTK_WINDOW (popup_window), TRUE);
      gtk_window_set_skip_pager_hint (GTK_WINDOW (popup_window), TRUE);
      gtk_widget_set_size_request (popup_window, 150, 150);
      gtk_window_set_transient_for (GTK_WINDOW (popup_window), GTK_WINDOW (pWindow));
      gtk_window_set_position (GTK_WINDOW (popup_window), GTK_WIN_POS_CENTER);
    
      gtk_widget_set_events (popup_window, GDK_FOCUS_CHANGE_MASK);
      g_signal_connect (G_OBJECT (popup_window),
                        "focus-out-event",
                        G_CALLBACK (on_popup_focus_out),
                        NULL);
    
      GdkColor color;
      gdk_color_parse ("#3b3131", &color);
      gtk_widget_modify_bg (GTK_WIDGET (popup_window), GTK_STATE_NORMAL, &color);
    
      gtk_widget_show_all (popup_window);
      gtk_widget_grab_focus (popup_window);
    }
    
    gboolean
    on_popup_focus_out (GtkWidget *widget,
                        GdkEventFocus *event,
                        gpointer data)
    {
      gtk_widget_destroy (widget);
      return TRUE;
    }