/* HomeBank -- Free, easy, personal accounting for everyone. * Copyright (C) 1995-2016 Maxime DOYEN * * This file is part of HomeBank. * * HomeBank is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * HomeBank is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include #include #include #include #include #include "gtk-dateentry.h" #define MYDEBUG 0 #if MYDEBUG #define DB(x) (x); #else #define DB(x); #endif enum { CHANGED, LAST_SIGNAL }; enum { PROPERTY_DATE = 5, }; static void gtk_date_entry_dispose (GObject *gobject); static void gtk_date_entry_finalize (GObject *gobject); static void gtk_date_entry_destroy (GtkWidget *dateentry); static void gtk_date_entry_entry_activate(GtkWidget * calendar, gpointer user_data); static gint gtk_date_entry_entry_key_pressed (GtkWidget *widget, GdkEventKey *event, gpointer user_data); static void gtk_date_entry_button_clicked (GtkWidget * widget, GtkDateEntry * dateentry); static void gtk_date_entry_popup(GtkDateEntry * dateentry, GdkEvent *event); static gint gtk_date_entry_popup_key_event (GtkWidget *widget, GdkEventKey *event, gpointer user_data); static gint gtk_date_entry_popup_button_press (GtkWidget * widget, GdkEvent * event, gpointer data); static void gtk_date_entry_calendar_year(GtkWidget * calendar, GtkDateEntry * dateentry); static void gtk_date_entry_calendar_getfrom(GtkWidget * calendar, GtkDateEntry * dateentry); static gint gtk_date_entry_calendar_select(GtkWidget * calendar, gpointer user_data); static void gtk_date_entry_entry_set_text(GtkDateEntry * dateentry); static void gtk_date_entry_popdown(GtkDateEntry *dateentry); static guint dateentry_signals[LAST_SIGNAL] = {0,}; // todo:finish this // this is to be able to seizure d or d/m or m/d in the gtkdateentry /* order of these in the current locale */ static GDateDMY dmy_order[3] = { G_DATE_DAY, G_DATE_MONTH, G_DATE_YEAR }; struct _GDateParseTokens { gint num_ints; gint n[3]; guint month; }; typedef struct _GDateParseTokens GDateParseTokens; #define NUM_LEN 10 static void hb_date_fill_parse_tokens (const gchar *str, GDateParseTokens *pt) { gchar num[4][NUM_LEN+1]; gint i; const guchar *s; DB( g_print("\n[dateentry] fill parse token\n") ); /* We count 4, but store 3; so we can give an error * if there are 4. */ num[0][0] = num[1][0] = num[2][0] = num[3][0] = '\0'; s = (const guchar *) str; pt->num_ints = 0; while (*s && pt->num_ints < 4) { i = 0; while (*s && g_ascii_isdigit (*s) && i < NUM_LEN) { num[pt->num_ints][i] = *s; ++s; ++i; } if (i > 0) { num[pt->num_ints][i] = '\0'; ++(pt->num_ints); } if (*s == '\0') break; ++s; } pt->n[0] = pt->num_ints > 0 ? atoi (num[0]) : 0; pt->n[1] = pt->num_ints > 1 ? atoi (num[1]) : 0; pt->n[2] = pt->num_ints > 2 ? atoi (num[2]) : 0; } /* static void hb_date_determine_dmy(void) { GDate d; gchar buf[128]; GDateParseTokens testpt; gint i; DB( g_print("\n[dateentry] determine dmy\n") ); g_date_clear (&d, 1); // clear for scratch use // had to pick a random day - don't change this, some strftimes // are broken on some days, and this one is good so far. g_date_set_dmy (&d, 4, 7, 1976); g_date_strftime (buf, 127, "%x", &d); hb_date_fill_parse_tokens (buf, &testpt); i = 0; while (i < testpt.num_ints) { switch (testpt.n[i]) { case 7: dmy_order[i] = G_DATE_MONTH; break; case 4: dmy_order[i] = G_DATE_DAY; break; case 1976: dmy_order[2] = G_DATE_YEAR; break; } ++i; } DB( g_print(" dmy legend: 0=day, 1=month, 2=year\n") ); DB( g_print(" dmy is: %d %d %d\n", dmy_order[0], dmy_order[1], dmy_order[2]) ); }*/ static void hb_date_parse_tokens(GtkWidget *gtkentry, gpointer user_data) { GtkDateEntry *dateentry = user_data; GtkDateEntryPrivate *priv = dateentry->priv; const gchar *str; GDateParseTokens pt; str = gtk_entry_get_text (GTK_ENTRY (priv->entry)); hb_date_fill_parse_tokens(str, &pt); DB( g_print(" -> parsetoken return is %d values :%d %d %d\n", pt.num_ints, pt.n[0], pt.n[1], pt.n[2]) ); // initialize with today's date g_date_set_time_t(priv->date, time(NULL)); switch( pt.num_ints ) { case 1: DB( g_print(" -> seizured 1 number\n") ); if(g_date_valid_day(pt.n[0])) g_date_set_day(priv->date, pt.n[0]); break; case 2: DB( g_print(" -> seizured 2 numbers\n") ); if( dmy_order[0] != G_DATE_YEAR ) { if( dmy_order[0] == G_DATE_DAY ) { if(g_date_valid_day(pt.n[0])) g_date_set_day(priv->date, pt.n[0]); if(g_date_valid_month(pt.n[1])) g_date_set_month(priv->date, pt.n[1]); } else { if(g_date_valid_day(pt.n[1])) g_date_set_day(priv->date, pt.n[1]); if(g_date_valid_month(pt.n[0])) g_date_set_month(priv->date, pt.n[0]); } } break; } } //end G_DEFINE_TYPE(GtkDateEntry, gtk_date_entry, GTK_TYPE_BOX) static void gtk_date_entry_class_init (GtkDateEntryClass *class) { GObjectClass *object_class; GtkWidgetClass *widget_class; object_class = G_OBJECT_CLASS (class); widget_class = GTK_WIDGET_CLASS (class); DB( g_print("\n[dateentry] class_init\n") ); //object_class->constructor = gtk_date_entry_constructor; //object_class->set_property = gtk_date_entry_set_property; //object_class->get_property = gtk_date_entry_get_property; object_class->dispose = gtk_date_entry_dispose; object_class->finalize = gtk_date_entry_finalize; widget_class->destroy = gtk_date_entry_destroy; dateentry_signals[CHANGED] = g_signal_new ("changed", G_TYPE_FROM_CLASS (class), G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (GtkDateEntryClass, changed), NULL, NULL, g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0); g_type_class_add_private (object_class, sizeof (GtkDateEntryPrivate)); } static gboolean gtk_date_entry_entry_focus_out(GtkWidget *widget, GdkEventFocus *event, gpointer user_data) { GtkDateEntry *dateentry = user_data; DB( g_print("\n[dateentry] entry focus-out-event %d\n", gtk_widget_is_focus(GTK_WIDGET(dateentry))) ); gtk_date_entry_entry_activate(GTK_WIDGET(dateentry), dateentry); return FALSE; } static void gtk_date_entry_init (GtkDateEntry *dateentry) { GtkDateEntryPrivate *priv; DB( g_print("\n[dateentry] init\n") ); /* yes, also priv, need to keep the code readable */ dateentry->priv = G_TYPE_INSTANCE_GET_PRIVATE (dateentry, GTK_TYPE_DATE_ENTRY, GtkDateEntryPrivate); priv = dateentry->priv; /* initialize datas */ priv->date = g_date_new(); priv->device = NULL; priv->popup_in_progress = FALSE; priv->has_grab = FALSE; g_date_set_time_t(priv->date, time(NULL)); g_date_set_dmy(&priv->mindate, 1, 1, 1900); g_date_set_dmy(&priv->maxdate, 31, 12, 2200); gtk_style_context_add_class (gtk_widget_get_style_context (GTK_WIDGET(dateentry)), GTK_STYLE_CLASS_LINKED); //widget=GTK_WIDGET(dateentry); priv->entry = gtk_entry_new (); //gtk_entry_set_width_chars(GTK_ENTRY(priv->entry), 10); //gtk_entry_set_max_width_chars(GTK_ENTRY(priv->entry), 4); gtk_box_pack_start (GTK_BOX (dateentry), priv->entry, TRUE, TRUE, 0); g_signal_connect (priv->entry, "key-press-event", G_CALLBACK (gtk_date_entry_entry_key_pressed), dateentry); g_signal_connect_after (priv->entry, "focus-out-event", G_CALLBACK (gtk_date_entry_entry_focus_out), dateentry); g_signal_connect (priv->entry, "activate", G_CALLBACK (gtk_date_entry_entry_activate), dateentry); priv->button = gtk_button_new (); priv->arrow = gtk_image_new_from_icon_name ("pan-down-symbolic", GTK_ICON_SIZE_BUTTON); gtk_container_add (GTK_CONTAINER (priv->button), priv->arrow); gtk_box_pack_end (GTK_BOX (dateentry), priv->button, FALSE, FALSE, 0); gtk_widget_show_all (priv->button); g_signal_connect (priv->button, "clicked", G_CALLBACK (gtk_date_entry_button_clicked), dateentry); priv->popup_window = gtk_window_new (GTK_WINDOW_POPUP); gtk_window_set_type_hint ( GTK_WINDOW (priv->popup_window), GDK_WINDOW_TYPE_HINT_COMBO); gtk_widget_set_events (priv->popup_window, gtk_widget_get_events(priv->popup_window) | GDK_KEY_PRESS_MASK); priv->frame = gtk_frame_new (NULL); gtk_container_add (GTK_CONTAINER (priv->popup_window), priv->frame); gtk_frame_set_shadow_type (GTK_FRAME (priv->frame), GTK_SHADOW_ETCHED_IN); gtk_widget_show (priv->frame); g_signal_connect (priv->popup_window, "key-press-event", G_CALLBACK (gtk_date_entry_popup_key_event), dateentry); g_signal_connect (priv->popup_window, "button-press-event", G_CALLBACK (gtk_date_entry_popup_button_press), dateentry); priv->calendar = gtk_calendar_new (); gtk_container_add (GTK_CONTAINER (priv->frame), priv->calendar); gtk_widget_show (priv->calendar); g_signal_connect (priv->calendar, "prev-year", G_CALLBACK (gtk_date_entry_calendar_year), dateentry); g_signal_connect (priv->calendar, "next-year", G_CALLBACK (gtk_date_entry_calendar_year), dateentry); g_signal_connect (priv->calendar, "prev-month", G_CALLBACK (gtk_date_entry_calendar_year), dateentry); g_signal_connect (priv->calendar, "next-month", G_CALLBACK (gtk_date_entry_calendar_year), dateentry); g_signal_connect (priv->calendar, "day-selected", G_CALLBACK (gtk_date_entry_calendar_getfrom), dateentry); g_signal_connect (priv->calendar, "day-selected-double-click", G_CALLBACK (gtk_date_entry_calendar_select), dateentry); } GtkWidget * gtk_date_entry_new () { GtkDateEntry *dateentry; DB( g_print("\n[dateentry] new\n") ); dateentry = g_object_new (GTK_TYPE_DATE_ENTRY, NULL); return GTK_WIDGET(dateentry); } static void gtk_date_entry_destroy (GtkWidget *object) { GtkDateEntry *dateentry = GTK_DATE_ENTRY (object); GtkDateEntryPrivate *priv = dateentry->priv; g_return_if_fail(object != NULL); g_return_if_fail(GTK_IS_DATE_ENTRY(object)); DB( g_print(" \n[dateentry] destroy\n") ); DB( g_print(" free gtkentry: %p\n", priv->entry) ); DB( g_print(" free arrow: %p\n", priv->button) ); DB( g_print(" free popup_window: %p\n", priv->popup_window) ); DB( g_print(" free dateentry: %p\n", dateentry) ); if(priv->popup_window) gtk_widget_destroy (priv->popup_window); priv->popup_window = NULL; if(priv->date) g_date_free(priv->date); priv->date = NULL; GTK_WIDGET_CLASS (gtk_date_entry_parent_class)->destroy (object); } static void gtk_date_entry_dispose (GObject *gobject) { //GtkDateEntry *self = GTK_DATE_ENTRY (gobject); DB( g_print(" \n[dateentry] dispose\n") ); //g_clear_object (&self->priv->an_object); G_OBJECT_CLASS (gtk_date_entry_parent_class)->dispose (gobject); } static void gtk_date_entry_finalize (GObject *gobject) { //GtkDateEntry *self = GTK_DATE_ENTRY (gobject); DB( g_print(" \n[dateentry] finalize\n") ); //g_date_free(self->date); //g_free (self->priv->a_string); /* Always chain up to the parent class; as with dispose(), finalize() * is guaranteed to exist on the parent's class virtual function table */ G_OBJECT_CLASS(gtk_date_entry_parent_class)->finalize (gobject); } /* ** */ void gtk_date_entry_set_date(GtkDateEntry *dateentry, guint32 julian_days) { GtkDateEntryPrivate *priv = dateentry->priv; DB( g_print(" \n[dateentry] set date\n") ); g_return_if_fail (GTK_IS_DATE_ENTRY (dateentry)); if(g_date_valid_julian(julian_days)) { g_date_set_julian (priv->date, julian_days); } else { g_date_set_time_t(priv->date, time(NULL)); } gtk_date_entry_entry_set_text(dateentry); } /* ** */ void gtk_date_entry_set_mindate(GtkDateEntry *dateentry, guint32 julian_days) { GtkDateEntryPrivate *priv = dateentry->priv; DB( g_print(" \n[dateentry] set mindate\n") ); g_return_if_fail (GTK_IS_DATE_ENTRY (dateentry)); if(g_date_valid_julian(julian_days)) { g_date_set_julian (&priv->mindate, julian_days); } } /* ** */ void gtk_date_entry_set_maxdate(GtkDateEntry *dateentry, guint32 julian_days) { GtkDateEntryPrivate *priv = dateentry->priv; DB( g_print(" \n[dateentry] set maxdate\n") ); g_return_if_fail (GTK_IS_DATE_ENTRY (dateentry)); if(g_date_valid_julian(julian_days)) { g_date_set_julian (&priv->maxdate, julian_days); } } guint32 gtk_date_entry_get_date(GtkDateEntry * dateentry) { GtkDateEntryPrivate *priv = dateentry->priv; DB( g_print(" \n[dateentry] get date\n") ); g_return_val_if_fail (GTK_IS_DATE_ENTRY (dateentry), 0); return(g_date_get_julian(priv->date)); } static void gtk_date_entry_entry_set_text(GtkDateEntry * dateentry) { GtkDateEntryPrivate *priv = dateentry->priv; gchar buffer[256]; DB( g_print("\n[dateentry] entry set text\n") ); g_date_clamp(priv->date, &priv->mindate, &priv->maxdate); if(g_date_valid(priv->date) == TRUE) { g_date_strftime (buffer, 256 - 1, "%x", priv->date); gtk_entry_set_text (GTK_ENTRY (priv->entry), buffer); DB( g_print(" = %s\n", buffer) ); } else gtk_entry_set_text (GTK_ENTRY (priv->entry), "??"); /* emit the signal */ if(priv->lastdate != g_date_get_julian(priv->date)) { DB( g_print(" **emit 'changed' signal**\n") ); g_signal_emit_by_name (dateentry, "changed", NULL, NULL); } priv->lastdate = g_date_get_julian(priv->date); } static void gtk_date_entry_entry_activate(GtkWidget *gtkentry, gpointer user_data) { GtkDateEntry *dateentry = user_data; GtkDateEntryPrivate *priv = dateentry->priv; const gchar *str; DB( g_print("\n[dateentry] entry_activate\n") ); str = gtk_entry_get_text (GTK_ENTRY (priv->entry)); //1) we parse the string according to the locale g_date_set_parse (priv->date, str); if(g_date_valid(priv->date) == FALSE) { //2) give a try to tokens: day, day/month, month/day hb_date_parse_tokens(gtkentry, user_data); } //3) at last if date still invalid, put today's dateentry_signals // we should consider just warn the user here if(g_date_valid(priv->date) == FALSE) { /* today's date */ g_date_set_time_t(priv->date, time(NULL)); } gtk_date_entry_entry_set_text(dateentry); } static void gtk_date_entry_calendar_year(GtkWidget *calendar, GtkDateEntry *dateentry) { GtkDateEntryPrivate *priv = dateentry->priv; guint year, month, day; DB( g_print(" (dateentry) year changed\n") ); gtk_calendar_get_date (GTK_CALENDAR (priv->calendar), &year, &month, &day); if( year < 1900) g_object_set(calendar, "year", 1900, NULL); if( year > 2200) g_object_set(calendar, "year", 2200, NULL); } static void gtk_date_entry_calendar_getfrom(GtkWidget * calendar, GtkDateEntry * dateentry) { GtkDateEntryPrivate *priv = dateentry->priv; guint year, month, day; DB( g_print(" (dateentry) calendar_getfrom\n") ); gtk_calendar_get_date (GTK_CALENDAR (priv->calendar), &year, &month, &day); g_date_set_dmy (priv->date, day, month + 1, year); gtk_date_entry_entry_set_text(dateentry); } static gint gtk_date_entry_calendar_select(GtkWidget * calendar, gpointer user_data) { GtkDateEntry *dateentry = user_data; DB( g_print(" (dateentry) calendar_select\n") ); gtk_date_entry_calendar_getfrom(NULL, dateentry); gtk_date_entry_popdown(dateentry); return FALSE; } static gint gtk_date_entry_entry_key_pressed (GtkWidget *widget, GdkEventKey *event, gpointer user_data) { GtkDateEntry *dateentry = user_data; GtkDateEntryPrivate *priv = dateentry->priv; DB( g_print("\n[dateentry] entry key pressed: state=%04x, keyval=%04x\n", event->state, event->keyval) ); if( event->keyval == GDK_KEY_Up ) { if( !(event->state & (GDK_SHIFT_MASK | GDK_CONTROL_MASK)) ) { g_date_add_days (priv->date, 1); gtk_date_entry_entry_set_text(dateentry); } else if( event->state & GDK_SHIFT_MASK ) { g_date_add_months (priv->date, 1); gtk_date_entry_entry_set_text(dateentry); } else if( event->state & GDK_CONTROL_MASK ) { g_date_add_years (priv->date, 1); gtk_date_entry_entry_set_text(dateentry); } return TRUE; } else if( event->keyval == GDK_KEY_Down ) { if( !(event->state & (GDK_SHIFT_MASK | GDK_CONTROL_MASK)) ) { g_date_subtract_days (priv->date, 1); gtk_date_entry_entry_set_text(dateentry); } else if( event->state & GDK_SHIFT_MASK ) { g_date_subtract_months (priv->date, 1); gtk_date_entry_entry_set_text(dateentry); } else if( event->state & GDK_CONTROL_MASK ) { g_date_subtract_years (priv->date, 1); gtk_date_entry_entry_set_text(dateentry); } return TRUE; } return FALSE; } static void gtk_date_entry_popup_position (GtkDateEntry * dateentry) { GtkDateEntryPrivate *priv = dateentry->priv; gint x, y; gint bwidth, bheight; GtkRequisition req; GdkWindow *gdkwindow; GtkAllocation allocation; DB( g_print("\n[dateentry] position popup\n") ); gtk_widget_get_preferred_size (priv->popup_window, NULL, &req); gdkwindow = gtk_widget_get_window(priv->button); gdk_window_get_origin (gdkwindow, &x, &y); gtk_widget_get_allocation(priv->button, &allocation); x += allocation.x; y += allocation.y; bwidth = allocation.width; bheight = allocation.height; x += bwidth - req.width; y += bheight; if (x < 0) x = 0; if (y < 0) y = 0; gtk_window_move (GTK_WINDOW (priv->popup_window), x, y); } static void gtk_date_entry_button_clicked (GtkWidget * widget, GtkDateEntry * dateentry) { GdkEvent *event; DB( g_print("\n[dateentry] button_clicked\n") ); /* Obtain the GdkEvent that triggered * the date button's "clicked" signal. */ event = gtk_get_current_event (); gtk_date_entry_popup(dateentry, event); } static void gtk_date_entry_popup(GtkDateEntry * dateentry, GdkEvent *event) { GtkDateEntryPrivate *priv = dateentry->priv; const char *str; int month; GdkDevice *event_device; GdkDevice *assoc_device; GdkDevice *keyboard_device; GdkDevice *pointer_device; GdkWindow *window; GdkGrabStatus grab_status; guint event_time; DB( g_print("\n[dateentry] popup_display\n****\n\n") ); /* update */ str = gtk_entry_get_text (GTK_ENTRY (priv->entry)); g_date_set_parse (priv->date, str); if(g_date_valid(priv->date) == TRUE) { /* GtkCalendar expects month to be in 0-11 range (inclusive) */ month = g_date_get_month (priv->date) - 1; gtk_calendar_select_month (GTK_CALENDAR (priv->calendar), CLAMP (month, 0, 11), g_date_get_year (priv->date)); gtk_calendar_select_day (GTK_CALENDAR (priv->calendar), g_date_get_day (priv->date)); } /* popup */ gtk_date_entry_popup_position(dateentry); gtk_widget_show (priv->popup_window); gtk_widget_grab_focus (priv->popup_window); gtk_grab_add (priv->popup_window); window = gtk_widget_get_window (priv->popup_window); g_return_if_fail (priv->grab_keyboard == NULL); g_return_if_fail (priv->grab_pointer == NULL); event_device = gdk_event_get_device (event); assoc_device = gdk_device_get_associated_device (event_device); event_time = gdk_event_get_time (event); if (gdk_device_get_source (event_device) == GDK_SOURCE_KEYBOARD) { keyboard_device = event_device; pointer_device = assoc_device; } else { keyboard_device = assoc_device; pointer_device = event_device; } if (keyboard_device != NULL) { grab_status = gdk_device_grab ( keyboard_device, window, GDK_OWNERSHIP_WINDOW, TRUE, GDK_KEY_PRESS_MASK | GDK_KEY_RELEASE_MASK, NULL, event_time); if (grab_status == GDK_GRAB_SUCCESS) { priv->grab_keyboard = g_object_ref (keyboard_device); } } if (pointer_device != NULL) { grab_status = gdk_device_grab ( pointer_device, window, GDK_OWNERSHIP_WINDOW, TRUE, GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK | GDK_POINTER_MOTION_MASK, NULL, event_time); if (grab_status == GDK_GRAB_SUCCESS) { priv->grab_pointer = g_object_ref (pointer_device); } else if (priv->grab_keyboard != NULL) { gdk_device_ungrab ( priv->grab_keyboard, event_time); g_object_unref (priv->grab_keyboard); priv->grab_keyboard = NULL; } } gdk_window_focus (window, event_time); } static void gtk_date_entry_popdown(GtkDateEntry *dateentry) { GtkDateEntryPrivate *priv = dateentry->priv; DB( g_print("\n[dateentry] popdown\n") ); gtk_widget_hide (priv->popup_window); gtk_grab_remove (priv->popup_window); if (priv->grab_keyboard != NULL) { gdk_device_ungrab ( priv->grab_keyboard, GDK_CURRENT_TIME); g_object_unref (priv->grab_keyboard); priv->grab_keyboard = NULL; } if (priv->grab_pointer != NULL) { gdk_device_ungrab ( priv->grab_pointer, GDK_CURRENT_TIME); g_object_unref (priv->grab_pointer); priv->grab_pointer = NULL; } } static gint gtk_date_entry_popup_key_event (GtkWidget *widget, GdkEventKey *event, gpointer user_data) { GtkDateEntry *dateentry = user_data; DB( g_print("\n[dateentry] popup_key_event%d\n", event->keyval) ); DB( g_print(" -> key=%d\n", event->keyval) ); if (event->keyval != GDK_KEY_Escape && event->keyval != GDK_KEY_Return) return FALSE; g_signal_stop_emission_by_name (widget, "key-press-event"); gtk_date_entry_popdown(dateentry); return TRUE; } static gint gtk_date_entry_popup_button_press (GtkWidget * widget, GdkEvent * event, gpointer user_data) { GtkDateEntry *dateentry = user_data; //GtkDateEntryPrivate *priv = dateentry->priv; GtkWidget *child; DB( g_print("\n[dateentry] popup_button_press\n") ); child = gtk_get_event_widget (event); /* We don't ask for button press events on the grab widget, so * if an event is reported directly to the grab widget, it must * be on a window outside the application (and thus we remove * the popup window). Otherwise, we check if the widget is a child * of the grab widget, and only remove the popup window if it * is not. */ if (child != widget) { while (child) { if (child == widget) return FALSE; child = gtk_widget_get_parent (child); } } gtk_date_entry_popdown(dateentry); return TRUE; }