diff --git a/_wip/gtkdtp/dtp.h b/_wip/gtkdtp/dtp.h index 0155ce6e..4781c722 100644 --- a/_wip/gtkdtp/dtp.h +++ b/_wip/gtkdtp/dtp.h @@ -5,23 +5,4 @@ #define GDK_VERSION_MAX_ALLOWED GDK_VERSION_3_4 #include -#define dateTimePickerWidgetType (dateTimePickerWidget_get_type()) -#define dateTimePickerWidget(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), dateTimePickerWidgetType, dateTimePickerWidget)) -#define isDateTimePickerWidget(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), dateTimePickerWidgetType)) -#define dateTimePickerWidgetClass(class) (G_TYPE_CHECK_CLASS_CAST((class), dateTimePickerWidgetType, dateTimePickerWidgetClass)) -#define isDateTimePickerWidgetClass(class) (G_TYPE_CHECK_CLASS_TYPE((class), dateTimePickerWidget)) -#define getDateTimePickerWidgetClass(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), dateTimePickerWidgetType, dateTimePickerWidgetClass)) - -typedef struct dateTimePickerWidget dateTimePickerWidget; -typedef struct dateTimePickerWidgetClass dateTimePickerWidgetClass; - -struct dateTimePickerWidget { - GtkBox parent_instance; - struct dtpPrivate *priv; -}; - -struct dateTimePickerWidgetClass { - GtkBoxClass parent_class; -}; - -extern GType dateTimePickerWidget_get_type(void); +extern GtkWidget *newDTP(void); diff --git a/_wip/gtkdtp/dtpwidget.c b/_wip/gtkdtp/dtpwidget.c index 02284488..d8cc8b74 100644 --- a/_wip/gtkdtp/dtpwidget.c +++ b/_wip/gtkdtp/dtpwidget.c @@ -2,18 +2,206 @@ #include "dtp.h" // TODO imitate gnome-calendar's day/month/year entries -// TODO connect to ::output to add a leading 0 to minutes and seconds -struct dtpPrivate { +#define dateTimePickerWidgetType (dateTimePickerWidget_get_type()) +#define dateTimePickerWidget(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), dateTimePickerWidgetType, dateTimePickerWidget)) +#define isDateTimePickerWidget(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), dateTimePickerWidgetType)) +#define dateTimePickerWidgetClass(class) (G_TYPE_CHECK_CLASS_CAST((class), dateTimePickerWidgetType, dateTimePickerWidgetClass)) +#define isDateTimePickerWidgetClass(class) (G_TYPE_CHECK_CLASS_TYPE((class), dateTimePickerWidget)) +#define getDateTimePickerWidgetClass(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), dateTimePickerWidgetType, dateTimePickerWidgetClass)) + +typedef struct dateTimePickerWidget dateTimePickerWidget; +typedef struct dateTimePickerWidgetClass dateTimePickerWidgetClass; + +struct dateTimePickerWidget { + GtkToggleButton parent_instance; + + gulong toggledSignal; + + GtkWidget *window; + GtkWidget *box; GtkWidget *calendar; GtkWidget *timebox; GtkWidget *hours; GtkWidget *minutes; GtkWidget *seconds; GtkWidget *ampm; + + GdkDevice *keyboard; + GdkDevice *mouse; }; -G_DEFINE_TYPE(dateTimePickerWidget, dateTimePickerWidget, GTK_TYPE_BOX) +struct dateTimePickerWidgetClass { + GtkToggleButtonClass parent_class; +}; + +G_DEFINE_TYPE(dateTimePickerWidget, dateTimePickerWidget, GTK_TYPE_TOGGLE_BUTTON) + +static void setLabel(dateTimePickerWidget *d) +{ + guint year, month, day; + struct tm tm; + + gtk_calendar_get_date(GTK_CALENDAR(d->calendar), &year, &month, &day); + tm.tm_hour = (int) gtk_spin_button_get_value(GTK_SPIN_BUTTON(d->hours)) - 1; + tm.tm_min = (int) gtk_spin_button_get_value(GTK_SPIN_BUTTON(d->minutes)); + tm.tm_sec = (int) gtk_spin_button_get_value(GTK_SPIN_BUTTON(d->seconds)); + tm.tm_mon = month; + tm.tm_mday = day; + tm.tm_year = year - 1900; + tm.tm_isdst = -1; // not available + // TODO strip \n + // TODO find the locale-preferred short date format + gtk_button_set_label(GTK_BUTTON(d), asctime(&tm)); +} + +// we don't want ::toggled to be sent again +static void setActive(dateTimePickerWidget *d, gboolean active) +{ + g_signal_handler_block(d, d->toggledSignal); + gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(d), active); + g_signal_handler_unblock(d, d->toggledSignal); +} + +// like startGrab() below, a lot of this is in the order that GtkComboBox does it +static void endGrab(dateTimePickerWidget *d) +{ + if (d->keyboard != NULL) + gdk_device_ungrab(d->keyboard, GDK_CURRENT_TIME); + gdk_device_ungrab(d->mouse, GDK_CURRENT_TIME); + gtk_device_grab_remove(d->window, d->mouse); + d->keyboard = NULL; + d->mouse = NULL; +} + +static void hidePopup(dateTimePickerWidget *d) +{ + endGrab(d); + gtk_widget_hide(d->window); + setActive(d, FALSE); +} + +// this consolidates a good chunk of what GtkComboBox does +static gboolean startGrab(dateTimePickerWidget *d) +{ + GdkDevice *dev; + guint32 time; + GdkWindow *window; + GdkDevice *keyboard, *mouse; + + dev = gtk_get_current_event_device(); + if (dev == NULL) + return FALSE; // TODO + + time = gtk_get_current_event_time(); + keyboard = dev; + mouse = gdk_device_get_associated_device(dev); + if (gdk_device_get_source(dev) != GDK_SOURCE_KEYBOARD) { + dev = mouse; + mouse = keyboard; + keyboard = dev; + } + + window = gtk_widget_get_window(d->window); + if (keyboard != NULL) + if (gdk_device_grab(keyboard, window, + GDK_OWNERSHIP_WINDOW, TRUE, + GDK_KEY_PRESS_MASK | GDK_KEY_RELEASE_MASK, + NULL, time) != GDK_GRAB_SUCCESS) + return FALSE; + if (mouse != NULL) + if (gdk_device_grab(mouse, window, + GDK_OWNERSHIP_WINDOW, TRUE, + GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK | GDK_POINTER_MOTION_MASK, + NULL, time) != GDK_GRAB_SUCCESS) { + if (keyboard != NULL) + gdk_device_ungrab(keyboard, time); + return FALSE; + } + + gtk_device_grab_add(d->window, mouse, TRUE); + d->keyboard = keyboard; + d->mouse = mouse; + return TRUE; +} + +// based on gtk_combo_box_list_position() in the GTK+ source code +static void allocationToScreen(dateTimePickerWidget *d, gint *x, gint *y) +{ + GdkWindow *window; + GtkAllocation a; + + gtk_widget_get_allocation(GTK_WIDGET(d), &a); + *x = 0; + *y = 0; + if (!gtk_widget_get_has_window(GTK_WIDGET(d))) { + *x = a.x; + *y = a.y; + } + window = gtk_widget_get_window(GTK_WIDGET(d)); + gdk_window_get_root_coords(window, *x, *y, x, y); + if (gtk_widget_get_direction(GTK_WIDGET(d)) == GTK_TEXT_DIR_RTL) + *x += a.width; // TODO subtract target width + // TODO monitor detection + *y += a.height; +} + +static void showPopup(dateTimePickerWidget *d) +{ + GtkWidget *toplevel; + gint x, y; + + // GtkComboBox does it + toplevel = gtk_widget_get_toplevel(GTK_WIDGET(d)); + if (GTK_IS_WINDOW(toplevel)) + gtk_window_group_add_window(gtk_window_get_group(GTK_WINDOW(toplevel)), GTK_WINDOW(d->window)); + + allocationToScreen(d, &x, &y); + gtk_window_move(GTK_WINDOW(d->window), x, y); + + gtk_widget_show(d->window); + setActive(d, TRUE); + + if (!startGrab(d)) + hidePopup(d); +} + +static void onToggled(GtkToggleButton *b, gpointer data) +{ + dateTimePickerWidget *d = dateTimePickerWidget(b); + + if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(d))) + showPopup(d); + else + hidePopup(d); +} + +static gboolean grabBroken(GtkWidget *w, GdkEventGrabBroken *e, gpointer data) +{ + dateTimePickerWidget *d = dateTimePickerWidget(data); + GdkWindow *cboxWindow; + + cboxWindow = gtk_widget_get_window(d->ampm); +g_print("%p %p %p %p", +cboxWindow, e->window, e->grab_window, +gtk_get_event_widget((GdkEvent*)e)); + if (e->grab_window == cboxWindow) + ; // TODO + else + hidePopup(d); + return TRUE; // this is what GtkComboBox does +} + +static gboolean buttonReleased(GtkWidget *w, GdkEventButton *e, gpointer data) +{ + dateTimePickerWidget *d = dateTimePickerWidget(data); + GtkAllocation a; + + gtk_widget_get_allocation(d->window, &a); + g_print("%d %d %d %d | %g %g\n", a.x, a.y, a.width, a.height, e->x, e->y); + hidePopup(d); + return TRUE; // this is what GtkComboBox does +} static gboolean zeroPadSpinbox(GtkSpinButton *sb, gpointer data) { @@ -42,41 +230,62 @@ static GtkWidget *newSpinbox(int min, int max, gboolean zeroPad) static void dateTimePickerWidget_init(dateTimePickerWidget *d) { - d->priv = G_TYPE_INSTANCE_GET_PRIVATE(d, dateTimePickerWidgetType, struct dtpPrivate); + d->window = gtk_window_new(GTK_WINDOW_POPUP); + gtk_window_set_resizable(GTK_WINDOW(d->window), FALSE); + gtk_window_set_attached_to(GTK_WINDOW(d->window), GTK_WIDGET(d)); + // TODO set_keep_above()? + gtk_window_set_decorated(GTK_WINDOW(d->window), FALSE); + gtk_window_set_deletable(GTK_WINDOW(d->window), FALSE); + gtk_window_set_type_hint(GTK_WINDOW(d->window), GDK_WINDOW_TYPE_HINT_COMBO); + gtk_window_set_skip_taskbar_hint(GTK_WINDOW(d->window), TRUE); + gtk_window_set_skip_pager_hint(GTK_WINDOW(d->window), TRUE); + // TODO accept_focus()? + // TODO focus_on_map()? + gtk_window_set_has_resize_grip(GTK_WINDOW(d->window), FALSE); + gtk_container_set_border_width(GTK_CONTAINER(d->window), 12); - gtk_orientable_set_orientation(GTK_ORIENTABLE(d), GTK_ORIENTATION_VERTICAL); - gtk_box_set_spacing(GTK_BOX(d), 6); + d->box = gtk_box_new(GTK_ORIENTATION_VERTICAL, 6); + gtk_container_add(GTK_CONTAINER(d->window), d->box); - d->priv->calendar = gtk_calendar_new(); - gtk_container_add(GTK_CONTAINER(d), d->priv->calendar); + d->calendar = gtk_calendar_new(); + gtk_container_add(GTK_CONTAINER(d->box), d->calendar); - d->priv->timebox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 6); - gtk_widget_set_valign(d->priv->timebox, GTK_ALIGN_CENTER); - gtk_container_add(GTK_CONTAINER(d), d->priv->timebox); + d->timebox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 6); + gtk_widget_set_valign(d->timebox, GTK_ALIGN_CENTER); + gtk_container_add(GTK_CONTAINER(d->box), d->timebox); - d->priv->hours = newSpinbox(1, 12, FALSE); - gtk_container_add(GTK_CONTAINER(d->priv->timebox), d->priv->hours); + d->hours = newSpinbox(1, 12, FALSE); + gtk_container_add(GTK_CONTAINER(d->timebox), d->hours); - gtk_container_add(GTK_CONTAINER(d->priv->timebox), + gtk_container_add(GTK_CONTAINER(d->timebox), gtk_label_new(":")); - d->priv->minutes = newSpinbox(0, 59, TRUE); - gtk_container_add(GTK_CONTAINER(d->priv->timebox), d->priv->minutes); + d->minutes = newSpinbox(0, 59, TRUE); + gtk_container_add(GTK_CONTAINER(d->timebox), d->minutes); - gtk_container_add(GTK_CONTAINER(d->priv->timebox), + gtk_container_add(GTK_CONTAINER(d->timebox), gtk_label_new(":")); - d->priv->seconds = newSpinbox(0, 59, TRUE); - gtk_container_add(GTK_CONTAINER(d->priv->timebox), d->priv->seconds); + d->seconds = newSpinbox(0, 59, TRUE); + gtk_container_add(GTK_CONTAINER(d->timebox), d->seconds); - d->priv->ampm = gtk_combo_box_text_new(); - gtk_combo_box_text_append(GTK_COMBO_BOX_TEXT(d->priv->ampm), NULL, "AM"); - gtk_combo_box_text_append(GTK_COMBO_BOX_TEXT(d->priv->ampm), NULL, "PM"); - gtk_widget_set_valign(d->priv->ampm, GTK_ALIGN_CENTER); - gtk_container_add(GTK_CONTAINER(d->priv->timebox), d->priv->ampm); + d->ampm = gtk_combo_box_text_new(); + gtk_combo_box_text_append(GTK_COMBO_BOX_TEXT(d->ampm), NULL, "AM"); + gtk_combo_box_text_append(GTK_COMBO_BOX_TEXT(d->ampm), NULL, "PM"); + gtk_widget_set_valign(d->ampm, GTK_ALIGN_CENTER); + gtk_container_add(GTK_CONTAINER(d->timebox), d->ampm); - gtk_widget_show(d->priv->calendar); - gtk_widget_show_all(d->priv->timebox); + gtk_widget_show_all(d->box); + + g_signal_connect(d->window, "grab-broken-event", G_CALLBACK(grabBroken), d); + g_signal_connect(d->window, "button-release-event", G_CALLBACK(buttonReleased), d); + + d->toggledSignal = g_signal_connect(d, "toggled", G_CALLBACK(onToggled), NULL); + d->keyboard = NULL; + d->mouse = NULL; + + // TODO set current time to now + setLabel(d); } static void dateTimePickerWidget_dispose(GObject *obj) @@ -93,6 +302,9 @@ static void dateTimePickerWidget_class_init(dateTimePickerWidgetClass *class) { G_OBJECT_CLASS(class)->dispose = dateTimePickerWidget_dispose; G_OBJECT_CLASS(class)->finalize = dateTimePickerWidget_finalize; - - g_type_class_add_private(G_OBJECT_CLASS(class), sizeof (struct dtpPrivate)); +} + +GtkWidget *newDTP(void) +{ + return GTK_WIDGET(g_object_new(dateTimePickerWidgetType, NULL)); } diff --git a/_wip/gtkdtp/main.c b/_wip/gtkdtp/main.c index ba839206..dc5f1ad6 100644 --- a/_wip/gtkdtp/main.c +++ b/_wip/gtkdtp/main.c @@ -3,123 +3,6 @@ // #qo pkg-config: gtk+-3.0 -GtkWidget *win; -GtkWidget *dtp; -GdkDevice *keyboard; -GdkDevice *mouse; - -// again, a lot of this is in the order that GtkComboBox does it -static void hidePopup(void) -{ - if (keyboard != NULL) - gdk_device_ungrab(keyboard, GDK_CURRENT_TIME); - gdk_device_ungrab(mouse, GDK_CURRENT_TIME); - gtk_device_grab_remove(win, mouse); - keyboard = NULL; - mouse = NULL; - - gtk_widget_hide(win); -} - -static gboolean stopPopup(GtkWidget *widget, GdkEvent *event, gpointer data) -{ - hidePopup(); - return TRUE; // this is what GtkComboBox does -} - -// this consolidates a good chunk of what GtkComboBox does -static gboolean startGrab(void) -{ - GdkDevice *dev; - guint32 time; - GdkWindow *window; - - dev = gtk_get_current_event_device(); - if (dev == NULL) - return FALSE; // TODO - - time = gtk_get_current_event_time(); - keyboard = dev; - mouse = gdk_device_get_associated_device(dev); - if (gdk_device_get_source(dev) != GDK_SOURCE_KEYBOARD) { - dev = mouse; - mouse = keyboard; - keyboard = dev; - } - - window = gtk_widget_get_window(win); - if (keyboard != NULL) - if (gdk_device_grab(keyboard, window, - GDK_OWNERSHIP_WINDOW, TRUE, - GDK_KEY_PRESS_MASK | GDK_KEY_RELEASE_MASK, - NULL, time) != GDK_GRAB_SUCCESS) - return FALSE; - if (mouse != NULL) - if (gdk_device_grab(mouse, window, - GDK_OWNERSHIP_WINDOW, TRUE, - GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK | GDK_POINTER_MOTION_MASK, - NULL, time) != GDK_GRAB_SUCCESS) { - if (keyboard != NULL) - gdk_device_ungrab(keyboard, time); - return FALSE; - } - - gtk_device_grab_add(win, mouse, TRUE); - // TODO store keyboard and mouse - g_signal_connect(win, "grab-broken-event", G_CALLBACK(stopPopup), NULL); - g_signal_connect(win, "button-release-event", G_CALLBACK(stopPopup), NULL); - return TRUE; -} - -// based on gtk_combo_box_list_position() in the GTK+ source code -static void allocationToScreen(GtkWidget *w, GtkAllocation *a, gint *x, gint *y) -{ - GdkWindow *window; - - *x = 0; - *y = 0; - if (!gtk_widget_get_has_window(w)) { - *x = a->x; - *y = a->y; - } - window = gtk_widget_get_window(w); - gdk_window_get_root_coords(window, *x, *y, x, y); - if (gtk_widget_get_direction(w) == GTK_TEXT_DIR_RTL) - *x += a->width; // TODO subtract target width - // TODO monitor detection - *y += a->height; -} - -static void showPopup(GtkWidget *button) -{ - GtkWidget *toplevel; - GtkAllocation allocation; - gint x, y; - - toplevel = gtk_widget_get_toplevel(button); - if (GTK_IS_WINDOW(toplevel)) - gtk_window_group_add_window(gtk_window_get_group(GTK_WINDOW(toplevel)), - GTK_WINDOW(win)); - - gtk_widget_get_allocation(button, &allocation); - allocationToScreen(button, &allocation, &x, &y); - gtk_window_move(GTK_WINDOW(win), x, y); - - gtk_widget_show(win); - - if (!startGrab()) - hidePopup(); -} - -// TODO verify signature -static void toggled(GtkToggleButton *t, gpointer data) -{ - if (gtk_toggle_button_get_active(t)) - showPopup(GTK_WIDGET(t)); - else - hidePopup(); -} - int main(void) { GtkWidget *mainwin; @@ -133,29 +16,10 @@ int main(void) gtk_container_set_border_width(GTK_CONTAINER(mainwin), 12); box = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 6); gtk_container_add(GTK_CONTAINER(mainwin), box); - button = gtk_toggle_button_new_with_label("Click"); - g_signal_connect(button, "toggled", G_CALLBACK(toggled), NULL); + button = newDTP(); gtk_container_add(GTK_CONTAINER(box), button); gtk_container_add(GTK_CONTAINER(box), gtk_entry_new()); - win = gtk_window_new(GTK_WINDOW_POPUP); - gtk_window_set_resizable(GTK_WINDOW(win), FALSE); - gtk_window_set_attached_to(GTK_WINDOW(win), button); - // TODO set_keep_above()? - gtk_window_set_decorated(GTK_WINDOW(win), FALSE); - gtk_window_set_deletable(GTK_WINDOW(win), FALSE); - gtk_window_set_type_hint(GTK_WINDOW(win), GDK_WINDOW_TYPE_HINT_COMBO); - gtk_window_set_skip_taskbar_hint(GTK_WINDOW(win), TRUE); - gtk_window_set_skip_pager_hint(GTK_WINDOW(win), TRUE); - // TODO accept_focus()? - // TODO focus_on_map()? - gtk_window_set_has_resize_grip(GTK_WINDOW(win), FALSE); - gtk_container_set_border_width(GTK_CONTAINER(win), 12); - - dtp = GTK_WIDGET(g_object_new(dateTimePickerWidget_get_type(), NULL)); - gtk_container_add(GTK_CONTAINER(win), dtp); - gtk_widget_show(dtp); - gtk_widget_show_all(mainwin); gtk_main(); return 0;