/* * Copyright (C) 2012 - Juan Ferrer Toribio * * This program 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 3 of the License, or * (at your option) any later version. * * This program 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 "vn-date-chooser.h" #include #define GUI_FILE _GUI_DIR"/date-chooser.glade" #define DATE_SIZE 50 /** * SECTION:vn-date-chooser * @Short_description: an embedded popup date selector * @Title: VnDateChooser * @See_also: #VnField * @Image: date-chooser.png * * An embedded button and text field that pops-up a #GtkCalendar. */ G_DEFINE_TYPE (VnDateChooser, vn_date_chooser, VN_TYPE_FIELD); /** * vn_date_chooser_new: * * Creates a new #VnDateChooser * * Return value: a #VnDateChooser **/ VnField * vn_date_chooser_new () { return VN_FIELD (g_object_new (VN_TYPE_DATE_CHOOSER, NULL)); } //+++++++++++++++++++++++++++++++++++++++++++++++++++ Private static void vn_date_chooser_hide_popup (VnDateChooser * self) { if (self->device) { gdk_device_ungrab (self->device, GDK_CURRENT_TIME); gtk_device_grab_remove (self->popup, self->device); } gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (self->button), FALSE); gtk_widget_hide (self->popup); } static void vn_date_chooser_changed (VnDateChooser * self) { GDateTime * datetime = self->datetime; if (datetime) { gchar * str = g_date_time_format (datetime, self->format); gtk_label_set_text (self->label, str); g_free (str); } else gtk_label_set_text (self->label, ""); } static void vn_date_chooser_set_value (VnDateChooser * self, const GValue * value) { GDateTime * datetime = NULL; if (gvn_value_is_null (value) || G_VALUE_TYPE (value) == G_TYPE_DATE_TIME) datetime = g_value_get_boxed (value); else { GValue new_value = G_VALUE_INIT; g_value_init (&new_value, G_TYPE_DATE_TIME); g_value_transform (value, &new_value); datetime = g_value_get_boxed (&new_value); } if (datetime) { if (gvn_value_is_null (datetime)) self->datetime = datetime; else self->datetime = g_date_time_ref (datetime); } else if (self->datetime) { g_date_time_unref (self->datetime); self->datetime = NULL; } vn_date_chooser_changed (self); } static GDateTime * vn_date_chooser_get_datetime (VnDateChooser * self) { guint year, month, day, hour = 0, minute = 0; gdouble second = 0.0; gtk_calendar_get_date (self->calendar, &year, &month, &day); month++; hour = (guint) gtk_adjustment_get_value (self->hour); minute = (guint) gtk_adjustment_get_value (self->minute); second = gtk_adjustment_get_value (self->second); return g_date_time_new_local (year, month, day, hour, minute, second); } void vn_date_chooser_on_day_selected (GtkCalendar * calendar, VnDateChooser * self) { GValue value = G_VALUE_INIT; GDateTime * datetime = vn_date_chooser_get_datetime (self); if (!self->datetime || (self->datetime && g_date_time_compare (datetime, self->datetime))) { self->datetime = datetime; g_value_init (&value, G_TYPE_DATE_TIME); g_value_set_boxed (&value, self->datetime); } else { g_value_init (&value, GVN_TYPE_NULL); if (self->datetime) g_date_time_unref (self->datetime); self->datetime = NULL; } VN_FIELD_GET_CLASS (self)->value_changed (VN_FIELD (self), &value); g_value_unset (&value); vn_date_chooser_hide_popup (self); vn_date_chooser_changed (self); } void vn_date_chooser_on_ok_clicked (GtkButton * button, VnDateChooser * self) { GDateTime * datetime = vn_date_chooser_get_datetime (self); if (datetime) { GValue value = G_VALUE_INIT; if (self->datetime) g_date_time_unref (self->datetime); self->datetime = datetime; g_value_init (&value, G_TYPE_DATE_TIME); g_value_set_boxed (&value, datetime); VN_FIELD_GET_CLASS (self)->value_changed (VN_FIELD (self), &value); g_value_unset (&value); } vn_date_chooser_hide_popup (self); vn_date_chooser_changed (self); } void vn_date_chooser_on_clear_clicked (GtkButton * button, VnDateChooser * self) { GValue value = G_VALUE_INIT; g_value_init (&value, GVN_TYPE_NULL); if (self->datetime) g_date_time_unref (self->datetime); self->datetime = NULL; VN_FIELD_GET_CLASS (self)->value_changed (VN_FIELD (self), &value); g_value_unset (&value); vn_date_chooser_hide_popup (self); vn_date_chooser_changed (self); gtk_adjustment_set_value (self->hour, 0); gtk_adjustment_set_value (self->minute, 0); gtk_adjustment_set_value (self->second, 0); } void vn_date_chooser_on_now_clicked (GtkButton * button, VnDateChooser * self) { GValue value = G_VALUE_INIT; if (self->datetime) g_date_time_unref (self->datetime); self->datetime = g_date_time_new_now_local (); g_value_init (&value, G_TYPE_DATE_TIME); g_value_set_boxed (&value, self->datetime); VN_FIELD_GET_CLASS (self)->value_changed (VN_FIELD (self), &value); g_value_unset (&value); vn_date_chooser_hide_popup (self); vn_date_chooser_changed (self); } static gboolean vn_date_chooser_on_button_press (GtkWidget * widget, GdkEventButton * event, VnDateChooser * self) { gint x, y; GtkAllocation allocation; gdk_window_get_origin (gtk_widget_get_window (self->popup), &x, &y); gtk_widget_get_allocation (self->popup, &allocation); if (!( event->x_root >= x && event->x_root <= x + allocation.width && event->y_root >= y && event->y_root <= y + allocation.height)) { vn_date_chooser_hide_popup (self); return TRUE; } return FALSE; } static gboolean vn_date_chooser_on_key_press (GtkWidget * widget, GdkEventKey * event, VnDateChooser * self) { if (event->keyval == GDK_KEY_Escape) { vn_date_chooser_hide_popup (self); return TRUE; } return FALSE; } static void vn_date_chooser_on_toggled (GtkToggleButton * button, VnDateChooser * self) { if (gtk_toggle_button_get_active (button)) { gint x, y; GdkWindow * window; GdkScreen * screen; GtkRequisition req; GdkRectangle monitor; GtkAllocation allocation; GtkWidget * widget = GTK_WIDGET (button); // Set the date/time on the calendar if (self->datetime) { gtk_calendar_select_month (self->calendar ,g_date_time_get_month (self->datetime) - 1 ,g_date_time_get_year (self->datetime) ); gtk_calendar_select_day (self->calendar, g_date_time_get_day_of_month (self->datetime)); gtk_adjustment_set_value (self->hour, g_date_time_get_hour (self->datetime)); gtk_adjustment_set_value (self->minute, g_date_time_get_minute (self->datetime)); gtk_adjustment_set_value (self->second, g_date_time_get_second (self->datetime)); } else gtk_calendar_select_day (self->calendar, 0); // Set visibility if (!self->show_date) gtk_widget_hide (GTK_WIDGET (self->calendar)); if (!self->show_time) gtk_widget_hide (self->time); gtk_widget_set_no_show_all (GTK_WIDGET (self->calendar), !self->show_date); gtk_widget_set_no_show_all (self->time, !self->show_time); gtk_widget_get_preferred_width (self->box, &x, NULL); gtk_widget_get_preferred_height (self->box, &y, NULL); gtk_window_resize (GTK_WINDOW (self->popup), x, y); // Setting the position of the popup window = gtk_widget_get_window (widget); gtk_widget_get_allocation (widget, &allocation); gdk_window_get_origin (window, &x, &y); x += allocation.x; y += allocation.y; screen = gtk_widget_get_screen (widget); gdk_screen_get_monitor_geometry (screen, gdk_screen_get_monitor_at_point (screen, x, y), &monitor); gtk_widget_get_preferred_size (self->popup, &req, NULL); if (y - monitor.y > monitor.height) y = monitor.y + monitor.height - req.height; else if ((y - monitor.y) + req.height + allocation.height > monitor.height) y -= req.height; else y += allocation.height; if ((x + allocation.width) - monitor.x > monitor.width) x = monitor.x + monitor.width - req.width; else if ((x - monitor.x) + req.width > monitor.width) x -= req.width - allocation.width; gtk_window_set_screen (GTK_WINDOW (self->popup), screen); gtk_window_move (GTK_WINDOW (self->popup), x, y); gtk_widget_show_all (self->popup); // Grabbing the focus on the popup window self->device = gtk_get_current_event_device (); if (self->device && gdk_device_get_source (self->device) == GDK_SOURCE_KEYBOARD) self->device = gdk_device_get_associated_device (self->device); if (!self->device) { GList * devices; GdkDisplay * display; GdkDeviceManager * device_manager; display = gtk_widget_get_display (GTK_WIDGET (self->popup)); device_manager = gdk_display_get_device_manager (display); devices = gdk_device_manager_list_devices (device_manager, GDK_DEVICE_TYPE_MASTER); self->device = devices->data; g_list_free (devices); } gtk_device_grab_add (self->popup, self->device, TRUE); gdk_device_grab (self->device ,gtk_widget_get_window (self->popup) ,GDK_OWNERSHIP_WINDOW, TRUE ,GDK_BUTTON_PRESS_MASK, NULL ,GDK_CURRENT_TIME ); } else vn_date_chooser_hide_popup (self); } gboolean vn_date_chooser_on_spin_output (GtkSpinButton * spin, VnDateChooser * self) { gchar * format; GtkAdjustment * adj = gtk_spin_button_get_adjustment (spin); format = g_strdup_printf ("%02d", (guint) gtk_adjustment_get_value (adj)); gtk_entry_set_text (GTK_ENTRY (spin), format); g_free (format); return TRUE; } //+++++++++++++++++++++++++++++++++++++++++++++++++++ Properties enum { PROP_FORMAT = 1 ,PROP_SHOW_TIME ,PROP_SHOW_DATE }; static void vn_date_chooser_set_property (VnDateChooser * self, guint id, const GValue * value, GParamSpec * pspec) { switch (id) { case PROP_FORMAT: g_free (self->format); self->format = g_value_dup_string (value); break; case PROP_SHOW_TIME: self->show_time = g_value_get_boolean (value); break; case PROP_SHOW_DATE: self->show_date = g_value_get_boolean (value); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (self, id, pspec); } } static void vn_date_chooser_get_property (VnDateChooser * self, guint id, GValue * value, GParamSpec * pspec) { switch (id) { case PROP_FORMAT: g_value_set_string (value, self->format); break; case PROP_SHOW_TIME: g_value_set_boolean (value, self->show_time); break; case PROP_SHOW_DATE: g_value_set_boolean (value, self->show_date); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (self, id, pspec); } } //+++++++++++++++++++++++++++++++++++++++++++++++++++ Class static void vn_date_chooser_init (VnDateChooser * self) { GtkBuilder * builder; self->show_time = FALSE; self->show_date = TRUE; self->format = NULL; self->popup = NULL; self->device = NULL; self->datetime = NULL; self->button = gtk_toggle_button_new (); gtk_widget_set_tooltip_text (GTK_WIDGET (self->button), _("Change date")); g_signal_connect (self->button, "toggled", G_CALLBACK (vn_date_chooser_on_toggled), self); gtk_container_add (GTK_CONTAINER (self), GTK_WIDGET (self->button)); g_object_set (self->button, "relief", GTK_RELIEF_HALF, NULL); self->label = GTK_LABEL (gtk_label_new (NULL)); gtk_misc_set_alignment (GTK_MISC (self->label), 0, 0.5); gtk_container_add (GTK_CONTAINER (self->button), GTK_WIDGET (self->label)); self->popup = gtk_window_new (GTK_WINDOW_POPUP); g_object_connect (self->popup ,"signal::button-press-event", vn_date_chooser_on_button_press, self ,"signal::key-press-event", vn_date_chooser_on_key_press, self ,NULL ); builder = gtk_builder_new_from_file (GUI_FILE); gtk_builder_connect_signals (builder, self); self->calendar = GTK_CALENDAR (gtk_builder_get_object (builder, "calendar")); self->hour = GTK_ADJUSTMENT (gtk_builder_get_object (builder, "hour")); self->minute = GTK_ADJUSTMENT (gtk_builder_get_object (builder, "minute")); self->second = GTK_ADJUSTMENT (gtk_builder_get_object (builder, "second")); self->time = GTK_WIDGET (gtk_builder_get_object (builder, "time")); self->box = GTK_WIDGET (gtk_builder_get_object (builder, "box")); gtk_container_add (GTK_CONTAINER (self->popup), self->box); VN_FIELD_GET_CLASS (self)->set_widget (VN_FIELD (self), GTK_WIDGET (self->button)); } static void vn_date_chooser_finalize (VnDateChooser * self) { if (self->datetime) g_date_time_unref (self->datetime); g_free (self->format); gtk_widget_destroy (self->popup); G_OBJECT_CLASS (vn_date_chooser_parent_class)->finalize (G_OBJECT (self)); } static void vn_date_chooser_class_init (VnDateChooserClass * klass) { GObjectClass * k = G_OBJECT_CLASS (klass); k->finalize = (GObjectFinalizeFunc) vn_date_chooser_finalize; k->set_property = (GObjectSetPropertyFunc) vn_date_chooser_set_property; k->get_property = (GObjectGetPropertyFunc) vn_date_chooser_get_property; VN_FIELD_CLASS (klass)->set_value = (VnFieldSetValueFunc) vn_date_chooser_set_value; g_object_class_install_property (k, PROP_FORMAT, g_param_spec_string ("format" ,_("Format") ,_("The date format string describing the order of the elements.") ,C_("Default date format string", "%a, %d %b %Y") ,G_PARAM_CONSTRUCT | G_PARAM_READWRITE )); g_object_class_install_property (k, PROP_SHOW_TIME, g_param_spec_boolean ("show-time" ,_("Show time") ,_("Whether to show the hour, minute and second fields to set" " the time of the day in the popup.") ,FALSE ,G_PARAM_READWRITE )); g_object_class_install_property (k, PROP_SHOW_DATE, g_param_spec_boolean ("show-date" ,_("Show date") ,_("Whether to show the calendar to set the date in the popup.") ,TRUE ,G_PARAM_READWRITE )); }