diff --git a/darwin/datetimepicker.m b/darwin/datetimepicker.m index 44364d9d..54c6ba38 100644 --- a/darwin/datetimepicker.m +++ b/darwin/datetimepicker.m @@ -4,10 +4,113 @@ struct uiDateTimePicker { uiDarwinControl c; NSDatePicker *dp; + void (*onChanged)(uiDateTimePicker *, void *); + void *onChangedData; }; +@interface uiprivDatePickerDelegateClass : NSObject { + struct uiprivMap *pickers; +} +- (void)datePickerCell:(NSDatePickerCell *)aDatePickerCell validateProposedDateValue:(NSDate **)proposedDateValue timeInterval:(NSTimeInterval *)proposedTimeInterval; +- (void)doTimer:(NSTimer *)timer; +- (void)registerPicker:(uiDateTimePicker *)b; +- (void)unregisterPicker:(uiDateTimePicker *)b; +@end + +@implementation uiprivDatePickerDelegateClass + +- (id)init +{ + self = [super init]; + if (self) + self->pickers = uiprivNewMap(); + return self; +} + +- (void)dealloc +{ + uiprivMapDestroy(self->pickers); + [super dealloc]; +} + +- (void)datePickerCell:(NSDatePickerCell *)aDatePickerCell + validateProposedDateValue:(NSDate **)proposedDateValue + timeInterval:(NSTimeInterval *)proposedTimeInterval +{ + uiDateTimePicker *d; + + d = (uiDateTimePicker *) uiprivMapGet(self->pickers, aDatePickerCell); + [NSTimer scheduledTimerWithTimeInterval:0 + target:self + selector:@selector(doTimer:) + userInfo:[NSValue valueWithPointer:d] + repeats:NO]; +} + +- (void)doTimer:(NSTimer *)timer +{ + uiDateTimePicker *d; + + d = (uiDateTimePicker *) [((NSValue *)[timer userInfo]) pointerValue]; + (*(d->onChanged))(d, d->onChangedData); +} + +- (void)registerPicker:(uiDateTimePicker *)d +{ + uiprivMapSet(self->pickers, d->dp.cell, d); + [d->dp setDelegate:self]; +} + +- (void)unregisterPicker:(uiDateTimePicker *)d +{ + [d->dp setDelegate:nil]; + uiprivMapDelete(self->pickers, d->dp.cell); +} + +@end + +static uiprivDatePickerDelegateClass *datePickerDelegate = nil; + uiDarwinControlAllDefaults(uiDateTimePicker, dp) +static void defaultOnChanged(uiDateTimePicker *d, void *data) +{ + // do nothing +} + +void uiDateTimePickerTime(uiDateTimePicker *d, struct tm *time) +{ + time_t t; + struct tm tmbuf; + NSDate *date; + + date = [d->dp dateValue]; + t = (time_t) [date timeIntervalSince1970]; + + // Copy time to minimize a race condition + // time.h functions use global non-thread-safe data + tmbuf = *localtime(&t); + memcpy(time, &tmbuf, sizeof(struct tm)); +} + +void uiDateTimePickerSetTime(uiDateTimePicker *d, const struct tm *time) +{ + time_t t; + struct tm tmbuf; + + // Copy time because mktime() modifies its argument + memcpy(&tmbuf, time, sizeof(struct tm)); + t = mktime(&tmbuf); + + [d->dp setDateValue:[NSDate dateWithTimeIntervalSince1970:t]]; +} + +void uiDateTimePickerOnChanged(uiDateTimePicker *d, void (*f)(uiDateTimePicker *, void *), void *data) +{ + d->onChanged = f; + d->onChangedData = data; +} + static uiDateTimePicker *finishNewDateTimePicker(NSDatePickerElementFlags elements) { uiDateTimePicker *d; @@ -15,6 +118,7 @@ static uiDateTimePicker *finishNewDateTimePicker(NSDatePickerElementFlags elemen uiDarwinNewControl(uiDateTimePicker, d); d->dp = [[NSDatePicker alloc] initWithFrame:NSZeroRect]; + [d->dp setDateValue:[NSDate date]]; [d->dp setBordered:NO]; [d->dp setBezeled:YES]; [d->dp setDrawsBackground:YES]; @@ -23,6 +127,13 @@ static uiDateTimePicker *finishNewDateTimePicker(NSDatePickerElementFlags elemen [d->dp setDatePickerMode:NSSingleDateMode]; uiDarwinSetControlFont(d->dp, NSRegularControlSize); + if (datePickerDelegate == nil) { + datePickerDelegate = [[uiprivDatePickerDelegateClass new] autorelease]; + [uiprivDelegates addObject:datePickerDelegate]; + } + [datePickerDelegate registerPicker:d]; + uiDateTimePickerOnChanged(d, defaultOnChanged, NULL); + return d; } diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index 8d83566e..4c049b9a 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -23,6 +23,11 @@ _add_example(histogram ${_EXAMPLE_RESOURCES_RC} ) +_add_example(datetime + datetime/main.c + ${_EXAMPLE_RESOURCES_RC} +) + _add_example(cpp-multithread cpp-multithread/main.cpp ${_EXAMPLE_RESOURCES_RC} @@ -53,4 +58,5 @@ add_custom_target(examples histogram cpp-multithread drawtext + datetime timer) diff --git a/examples/datetime/main.c b/examples/datetime/main.c new file mode 100644 index 00000000..e71b54a6 --- /dev/null +++ b/examples/datetime/main.c @@ -0,0 +1,123 @@ +#include +#include +#include +#include "../../ui.h" + +uiDateTimePicker *dtboth, *dtdate, *dttime; + +const char *timeFormat(uiDateTimePicker *d) +{ + const char *fmt; + + if (d == dtboth) + fmt = "%c"; + else if (d == dtdate) + fmt = "%x"; + else if (d == dttime) + fmt = "%X"; + else + fmt = ""; + + return fmt; +} + +void onChanged(uiDateTimePicker *d, void *data) +{ + struct tm time; + char buf[64]; + + uiDateTimePickerTime(d, &time); + strftime(buf, sizeof (buf), timeFormat(d), &time); + uiLabelSetText(uiLabel(data), buf); +} + +void onClicked(uiButton *b, void *data) +{ + intptr_t now; + time_t t; + struct tm tmbuf; + + now = (intptr_t) data; + t = 0; + if (now) + t = time(NULL); + tmbuf = *localtime(&t); + + if (now) { + uiDateTimePickerSetTime(dtdate, &tmbuf); + uiDateTimePickerSetTime(dttime, &tmbuf); + } else + uiDateTimePickerSetTime(dtboth, &tmbuf); +} + +int onClosing(uiWindow *w, void *data) +{ + uiQuit(); + return 1; +} + +int main(void) +{ + uiInitOptions o; + uiWindow *w; + uiGrid *g; + uiLabel *l; + uiButton *b; + + memset(&o, 0, sizeof (uiInitOptions)); + if (uiInit(&o) != NULL) + abort(); + + w = uiNewWindow("Date / Time", 320, 240, 0); + uiWindowSetMargined(w, 1); + + g = uiNewGrid(); + uiGridSetPadded(g, 1); + uiWindowSetChild(w, uiControl(g)); + + dtboth = uiNewDateTimePicker(); + dtdate = uiNewDatePicker(); + dttime = uiNewTimePicker(); + + uiGridAppend(g, uiControl(dtboth), + 0, 0, 2, 1, + 1, uiAlignFill, 0, uiAlignFill); + uiGridAppend(g, uiControl(dtdate), + 0, 1, 1, 1, + 1, uiAlignFill, 0, uiAlignFill); + uiGridAppend(g, uiControl(dttime), + 1, 1, 1, 1, + 1, uiAlignFill, 0, uiAlignFill); + + l = uiNewLabel(""); + uiGridAppend(g, uiControl(l), + 0, 2, 2, 1, + 1, uiAlignCenter, 0, uiAlignFill); + uiDateTimePickerOnChanged(dtboth, onChanged, l); + l = uiNewLabel(""); + uiGridAppend(g, uiControl(l), + 0, 3, 1, 1, + 1, uiAlignCenter, 0, uiAlignFill); + uiDateTimePickerOnChanged(dtdate, onChanged, l); + l = uiNewLabel(""); + uiGridAppend(g, uiControl(l), + 1, 3, 1, 1, + 1, uiAlignCenter, 0, uiAlignFill); + uiDateTimePickerOnChanged(dttime, onChanged, l); + + b = uiNewButton("Now"); + uiButtonOnClicked(b, onClicked, (void *) 1); + uiGridAppend(g, uiControl(b), + 0, 4, 1, 1, + 1, uiAlignFill, 1, uiAlignEnd); + b = uiNewButton("Unix epoch"); + uiButtonOnClicked(b, onClicked, (void *) 0); + uiGridAppend(g, uiControl(b), + 1, 4, 1, 1, + 1, uiAlignFill, 1, uiAlignEnd); + + uiWindowOnClosing(w, onClosing, NULL); + uiControlShow(uiControl(w)); + uiMain(); + return 0; +} diff --git a/ui.h b/ui.h index ce3e4104..b5fb9a27 100644 --- a/ui.h +++ b/ui.h @@ -248,8 +248,13 @@ _UI_EXTERN void uiRadioButtonsSetSelected(uiRadioButtons *r, int n); _UI_EXTERN void uiRadioButtonsOnSelected(uiRadioButtons *r, void (*f)(uiRadioButtons *, void *), void *data); _UI_EXTERN uiRadioButtons *uiNewRadioButtons(void); +struct tm; typedef struct uiDateTimePicker uiDateTimePicker; #define uiDateTimePicker(this) ((uiDateTimePicker *) (this)) +// TODO document that tm_wday and tm_yday are undefined, and tm_isdst should be -1 +_UI_EXTERN void uiDateTimePickerTime(uiDateTimePicker *d, struct tm *time); +_UI_EXTERN void uiDateTimePickerSetTime(uiDateTimePicker *d, const struct tm *time); +_UI_EXTERN void uiDateTimePickerOnChanged(uiDateTimePicker *d, void (*f)(uiDateTimePicker *, void *), void *data); _UI_EXTERN uiDateTimePicker *uiNewDateTimePicker(void); _UI_EXTERN uiDateTimePicker *uiNewDatePicker(void); _UI_EXTERN uiDateTimePicker *uiNewTimePicker(void); diff --git a/unix/datetimepicker.c b/unix/datetimepicker.c index 19689a22..5f052dca 100644 --- a/unix/datetimepicker.c +++ b/unix/datetimepicker.c @@ -4,17 +4,17 @@ // LONGTERM imitate gnome-calendar's day/month/year entries above the calendar // LONGTERM allow entering a 24-hour hour in the hour spinbutton and adjust accordingly -#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)) +#define uiprivDateTimePickerWidgetType (uiprivDateTimePickerWidget_get_type()) +#define uiprivDateTimePickerWidget(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), uiprivDateTimePickerWidgetType, uiprivDateTimePickerWidget)) +#define isDateTimePickerWidget(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), uiprivDateTimePickerWidgetType)) +#define uiprivDateTimePickerWidgetClass(class) (G_TYPE_CHECK_CLASS_CAST((class), uiprivDateTimePickerWidgetType, uiprivDateTimePickerWidgetClass)) +#define isDateTimePickerWidgetClass(class) (G_TYPE_CHECK_CLASS_TYPE((class), uiprivDateTimePickerWidget)) +#define getDateTimePickerWidgetClass(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), uiprivDateTimePickerWidgetType, uiprivDateTimePickerWidgetClass)) -typedef struct dateTimePickerWidget dateTimePickerWidget; -typedef struct dateTimePickerWidgetClass dateTimePickerWidgetClass; +typedef struct uiprivDateTimePickerWidget uiprivDateTimePickerWidget; +typedef struct uiprivDateTimePickerWidgetClass uiprivDateTimePickerWidgetClass; -struct dateTimePickerWidget { +struct uiprivDateTimePickerWidget { GtkToggleButton parent_instance; gulong toggledSignal; @@ -31,6 +31,7 @@ struct dateTimePickerWidget { GtkWidget *seconds; GtkWidget *ampm; + gulong calendarBlock; gulong hoursBlock; gulong minutesBlock; gulong secondsBlock; @@ -40,11 +41,11 @@ struct dateTimePickerWidget { GdkDevice *mouse; }; -struct dateTimePickerWidgetClass { +struct uiprivDateTimePickerWidgetClass { GtkToggleButtonClass parent_class; }; -G_DEFINE_TYPE(dateTimePickerWidget, dateTimePickerWidget, GTK_TYPE_TOGGLE_BUTTON) +G_DEFINE_TYPE(uiprivDateTimePickerWidget, uiprivDateTimePickerWidget, GTK_TYPE_TOGGLE_BUTTON) static int realSpinValue(GtkSpinButton *spinButton) { @@ -64,7 +65,7 @@ static void setRealSpinValue(GtkSpinButton *spinButton, int value, gulong block) g_signal_handler_unblock(spinButton, block); } -static GDateTime *selected(dateTimePickerWidget *d) +static GDateTime *selected(uiprivDateTimePickerWidget *d) { // choose a day for which all times are likely to be valid for the default date in case we're only dealing with time guint year = 1970, month = 1, day = 1; @@ -84,7 +85,7 @@ static GDateTime *selected(dateTimePickerWidget *d) return g_date_time_new_local(year, month, day, hour, minute, second); } -static void setLabel(dateTimePickerWidget *d) +static void setLabel(uiprivDateTimePickerWidget *d) { GDateTime *dt; char *fmt; @@ -109,14 +110,16 @@ static void setLabel(dateTimePickerWidget *d) g_date_time_unref(dt); } -static void dateTimeChanged(dateTimePickerWidget *d) +static int changedSignal; + +static void dateTimeChanged(uiprivDateTimePickerWidget *d) { + g_signal_emit(d, changedSignal, 0); setLabel(d); - // TODO fire event here } // we don't want ::toggled to be sent again -static void setActive(dateTimePickerWidget *d, gboolean active) +static void setActive(uiprivDateTimePickerWidget *d, gboolean active) { g_signal_handler_block(d, d->toggledSignal); gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(d), active); @@ -124,7 +127,7 @@ static void setActive(dateTimePickerWidget *d, gboolean active) } // like startGrab() below, a lot of this is in the order that GtkComboBox does it -static void endGrab(dateTimePickerWidget *d) +static void endGrab(uiprivDateTimePickerWidget *d) { if (d->keyboard != NULL) gdk_device_ungrab(d->keyboard, GDK_CURRENT_TIME); @@ -134,7 +137,7 @@ static void endGrab(dateTimePickerWidget *d) d->mouse = NULL; } -static void hidePopup(dateTimePickerWidget *d) +static void hidePopup(uiprivDateTimePickerWidget *d) { endGrab(d); gtk_widget_hide(d->window); @@ -142,7 +145,7 @@ static void hidePopup(dateTimePickerWidget *d) } // this consolidates a good chunk of what GtkComboBox does -static gboolean startGrab(dateTimePickerWidget *d) +static gboolean startGrab(uiprivDateTimePickerWidget *d) { GdkDevice *dev; guint32 time; @@ -197,7 +200,7 @@ static gboolean startGrab(dateTimePickerWidget *d) } // based on gtk_combo_box_list_position() in the GTK+ source code -static void allocationToScreen(dateTimePickerWidget *d, gint *x, gint *y) +static void allocationToScreen(uiprivDateTimePickerWidget *d, gint *x, gint *y) { GdkWindow *window; GtkAllocation a; @@ -237,7 +240,7 @@ static void allocationToScreen(dateTimePickerWidget *d, gint *x, gint *y) *y = otherY; } -static void showPopup(dateTimePickerWidget *d) +static void showPopup(uiprivDateTimePickerWidget *d) { GtkWidget *toplevel; gint x, y; @@ -259,7 +262,7 @@ static void showPopup(dateTimePickerWidget *d) static void onToggled(GtkToggleButton *b, gpointer data) { - dateTimePickerWidget *d = dateTimePickerWidget(b); + uiprivDateTimePickerWidget *d = uiprivDateTimePickerWidget(b); if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(d))) showPopup(d); @@ -269,7 +272,7 @@ static void onToggled(GtkToggleButton *b, gpointer data) static gboolean grabBroken(GtkWidget *w, GdkEventGrabBroken *e, gpointer data) { - dateTimePickerWidget *d = dateTimePickerWidget(data); + uiprivDateTimePickerWidget *d = uiprivDateTimePickerWidget(data); hidePopup(d); return TRUE; // this is what GtkComboBox does @@ -277,7 +280,7 @@ static gboolean grabBroken(GtkWidget *w, GdkEventGrabBroken *e, gpointer data) static gboolean buttonReleased(GtkWidget *w, GdkEventButton *e, gpointer data) { - dateTimePickerWidget *d = dateTimePickerWidget(data); + uiprivDateTimePickerWidget *d = uiprivDateTimePickerWidget(data); int winx, winy; GtkAllocation wina; gboolean in; @@ -382,12 +385,12 @@ static gboolean ampmSpinboxOutput(GtkSpinButton *sb, gpointer data) static void spinboxChanged(GtkSpinButton *sb, gpointer data) { - dateTimePickerWidget *d = dateTimePickerWidget(data); + uiprivDateTimePickerWidget *d = uiprivDateTimePickerWidget(data); dateTimeChanged(d); } -static GtkWidget *newSpinbox(dateTimePickerWidget *d, int min, int max, gint (*input)(GtkSpinButton *, gpointer, gpointer), gboolean (*output)(GtkSpinButton *, gpointer), gulong *block) +static GtkWidget *newSpinbox(uiprivDateTimePickerWidget *d, int min, int max, gint (*input)(GtkSpinButton *, gpointer, gpointer), gboolean (*output)(GtkSpinButton *, gpointer), gulong *block) { GtkWidget *sb; @@ -405,30 +408,52 @@ static GtkWidget *newSpinbox(dateTimePickerWidget *d, int min, int max, gint (*i static void dateChanged(GtkCalendar *c, gpointer data) { - dateTimePickerWidget *d = dateTimePickerWidget(data); + uiprivDateTimePickerWidget *d = uiprivDateTimePickerWidget(data); dateTimeChanged(d); } -static void setDateOnly(dateTimePickerWidget *d) +static void setDateOnly(uiprivDateTimePickerWidget *d) { d->hasTime = FALSE; gtk_container_remove(GTK_CONTAINER(d->box), d->timebox); } -static void setTimeOnly(dateTimePickerWidget *d) +static void setTimeOnly(uiprivDateTimePickerWidget *d) { d->hasDate = FALSE; gtk_container_remove(GTK_CONTAINER(d->box), d->calendar); } -static void dateTimePickerWidget_init(dateTimePickerWidget *d) +static void uiprivDateTimePickerWidget_setTime(uiprivDateTimePickerWidget *d, GDateTime *dt) { - GDateTime *dt; gint year, month, day; gint hour; - gulong calendarBlock; + // notice how we block signals from firing + if (d->hasDate) { + g_date_time_get_ymd(dt, &year, &month, &day); + month--; // GDateTime/GtkCalendar differences + g_signal_handler_block(d->calendar, d->calendarBlock); + gtk_calendar_select_month(GTK_CALENDAR(d->calendar), month, year); + gtk_calendar_select_day(GTK_CALENDAR(d->calendar), day); + g_signal_handler_unblock(d->calendar, d->calendarBlock); + } + if (d->hasTime) { + hour = g_date_time_get_hour(dt); + if (hour >= 12) { + hour -= 12; + setRealSpinValue(GTK_SPIN_BUTTON(d->ampm), 1, d->ampmBlock); + } + setRealSpinValue(GTK_SPIN_BUTTON(d->hours), hour, d->hoursBlock); + setRealSpinValue(GTK_SPIN_BUTTON(d->minutes), g_date_time_get_minute(dt), d->minutesBlock); + setRealSpinValue(GTK_SPIN_BUTTON(d->seconds), g_date_time_get_seconds(dt), d->secondsBlock); + } + g_date_time_unref(dt); +} + +static void uiprivDateTimePickerWidget_init(uiprivDateTimePickerWidget *d) +{ 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)); @@ -446,7 +471,7 @@ static void dateTimePickerWidget_init(dateTimePickerWidget *d) gtk_container_add(GTK_CONTAINER(d->window), d->box); d->calendar = gtk_calendar_new(); - calendarBlock = g_signal_connect(d->calendar, "day-selected", G_CALLBACK(dateChanged), d); + d->calendarBlock = g_signal_connect(d->calendar, "day-selected", G_CALLBACK(dateChanged), d); gtk_container_add(GTK_CONTAINER(d->box), d->calendar); d->timebox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 6); @@ -493,53 +518,103 @@ static void dateTimePickerWidget_init(dateTimePickerWidget *d) d->hasDate = TRUE; // set the current date/time - // notice how we block signals from firing - dt = g_date_time_new_now_local(); - g_date_time_get_ymd(dt, &year, &month, &day); - month--; // GDateTime/GtkCalendar differences - g_signal_handler_block(d->calendar, calendarBlock); - gtk_calendar_select_month(GTK_CALENDAR(d->calendar), month, year); - gtk_calendar_select_day(GTK_CALENDAR(d->calendar), day); - g_signal_handler_unblock(d->calendar, calendarBlock); - hour = g_date_time_get_hour(dt); - if (hour >= 12) { - hour -= 12; - setRealSpinValue(GTK_SPIN_BUTTON(d->ampm), 1, d->ampmBlock); - } - setRealSpinValue(GTK_SPIN_BUTTON(d->hours), hour, d->hoursBlock); - setRealSpinValue(GTK_SPIN_BUTTON(d->minutes), g_date_time_get_minute(dt), d->minutesBlock); - setRealSpinValue(GTK_SPIN_BUTTON(d->seconds), g_date_time_get_seconds(dt), d->secondsBlock); - g_date_time_unref(dt); + uiprivDateTimePickerWidget_setTime(d, g_date_time_new_now_local()); } -static void dateTimePickerWidget_dispose(GObject *obj) +static void uiprivDateTimePickerWidget_dispose(GObject *obj) { - dateTimePickerWidget *d = dateTimePickerWidget(obj); + uiprivDateTimePickerWidget *d = uiprivDateTimePickerWidget(obj); if (d->window != NULL) { gtk_widget_destroy(d->window); d->window = NULL; } - G_OBJECT_CLASS(dateTimePickerWidget_parent_class)->dispose(obj); + G_OBJECT_CLASS(uiprivDateTimePickerWidget_parent_class)->dispose(obj); } -static void dateTimePickerWidget_finalize(GObject *obj) +static void uiprivDateTimePickerWidget_finalize(GObject *obj) { - G_OBJECT_CLASS(dateTimePickerWidget_parent_class)->finalize(obj); + G_OBJECT_CLASS(uiprivDateTimePickerWidget_parent_class)->finalize(obj); } -static void dateTimePickerWidget_class_init(dateTimePickerWidgetClass *class) +static void uiprivDateTimePickerWidget_class_init(uiprivDateTimePickerWidgetClass *class) { - G_OBJECT_CLASS(class)->dispose = dateTimePickerWidget_dispose; - G_OBJECT_CLASS(class)->finalize = dateTimePickerWidget_finalize; + G_OBJECT_CLASS(class)->dispose = uiprivDateTimePickerWidget_dispose; + G_OBJECT_CLASS(class)->finalize = uiprivDateTimePickerWidget_finalize; + + changedSignal = g_signal_new("changed", + G_TYPE_FROM_CLASS(class), + G_SIGNAL_RUN_LAST, + 0, + NULL, NULL, NULL, + G_TYPE_NONE, + 0); +} + +struct uiDateTimePicker { + uiUnixControl c; + GtkWidget *widget; + uiprivDateTimePickerWidget *d; + void (*onChanged)(uiDateTimePicker *, void *); + void *onChangedData; +}; + +uiUnixControlAllDefaults(uiDateTimePicker) + +static void defaultOnChanged(uiDateTimePicker *d, void *data) +{ + // do nothing +} + +void uiDateTimePickerTime(uiDateTimePicker *d, struct tm *time) +{ + time_t t; + struct tm tmbuf; + GDateTime *dt; + + dt = selected(d->d); + t = g_date_time_to_unix(dt); + g_date_time_unref(dt); + + // Copy time to minimize a race condition + // time.h functions use global non-thread-safe data + tmbuf = *localtime(&t); + memcpy(time, &tmbuf, sizeof(struct tm)); +} + +void uiDateTimePickerSetTime(uiDateTimePicker *d, const struct tm *time) +{ + time_t t; + struct tm tmbuf; + + // Copy time because mktime() modifies its argument + memcpy(&tmbuf, time, sizeof(struct tm)); + t = mktime(&tmbuf); + + uiprivDateTimePickerWidget_setTime(d->d, g_date_time_new_from_unix_local(t)); + dateTimeChanged(d->d); +} + +void uiDateTimePickerOnChanged(uiDateTimePicker *d, void (*f)(uiDateTimePicker *, void *), void *data) +{ + d->onChanged = f; + d->onChangedData = data; +} + +static void onChanged(uiprivDateTimePickerWidget *d, gpointer data) +{ + uiDateTimePicker *c; + + c = uiDateTimePicker(data); + (*(c->onChanged))(c, c->onChangedData); } static GtkWidget *newDTP(void) { GtkWidget *w; - w = GTK_WIDGET(g_object_new(dateTimePickerWidgetType, "label", "", NULL)); - setLabel(dateTimePickerWidget(w)); + w = GTK_WIDGET(g_object_new(uiprivDateTimePickerWidgetType, "label", "", NULL)); + setLabel(uiprivDateTimePickerWidget(w)); return w; } @@ -547,9 +622,9 @@ static GtkWidget *newDP(void) { GtkWidget *w; - w = GTK_WIDGET(g_object_new(dateTimePickerWidgetType, "label", "", NULL)); - setDateOnly(dateTimePickerWidget(w)); - setLabel(dateTimePickerWidget(w)); + w = GTK_WIDGET(g_object_new(uiprivDateTimePickerWidgetType, "label", "", NULL)); + setDateOnly(uiprivDateTimePickerWidget(w)); + setLabel(uiprivDateTimePickerWidget(w)); return w; } @@ -557,20 +632,12 @@ static GtkWidget *newTP(void) { GtkWidget *w; - w = GTK_WIDGET(g_object_new(dateTimePickerWidgetType, "label", "", NULL)); - setTimeOnly(dateTimePickerWidget(w)); - setLabel(dateTimePickerWidget(w)); + w = GTK_WIDGET(g_object_new(uiprivDateTimePickerWidgetType, "label", "", NULL)); + setTimeOnly(uiprivDateTimePickerWidget(w)); + setLabel(uiprivDateTimePickerWidget(w)); return w; } -struct uiDateTimePicker { - uiUnixControl c; - GtkWidget *widget; - dateTimePickerWidget *d; -}; - -uiUnixControlAllDefaults(uiDateTimePicker) - uiDateTimePicker *finishNewDateTimePicker(GtkWidget *(*fn)(void)) { uiDateTimePicker *d; @@ -578,7 +645,9 @@ uiDateTimePicker *finishNewDateTimePicker(GtkWidget *(*fn)(void)) uiUnixNewControl(uiDateTimePicker, d); d->widget = (*fn)(); - d->d = dateTimePickerWidget(d->widget); + d->d = uiprivDateTimePickerWidget(d->widget); + g_signal_connect(d->widget, "changed", G_CALLBACK(onChanged), d); + uiDateTimePickerOnChanged(d, defaultOnChanged, NULL); return d; } diff --git a/windows/datetimepicker.cpp b/windows/datetimepicker.cpp index 6784fec2..26841dae 100644 --- a/windows/datetimepicker.cpp +++ b/windows/datetimepicker.cpp @@ -4,6 +4,8 @@ struct uiDateTimePicker { uiWindowsControl c; HWND hwnd; + void(*onChanged)(uiDateTimePicker *, void *); + void *onChangedData; }; // utility functions @@ -103,6 +105,7 @@ static void uiDateTimePickerDestroy(uiControl *c) uiDateTimePicker *d = uiDateTimePicker(c); uiWindowsUnregisterReceiveWM_WININICHANGE(d->hwnd); + uiWindowsUnregisterWM_NOTIFYHandler(d->hwnd); uiWindowsEnsureDestroyWindow(d->hwnd); uiFreeControl(uiControl(d)); } @@ -131,6 +134,72 @@ static void uiDateTimePickerMinimumSize(uiWindowsControl *c, int *width, int *he *height = y; } +static BOOL onWM_NOTIFY(uiControl *c, HWND hwnd, NMHDR *nmhdr, LRESULT *lResult) +{ + uiDateTimePicker *d = uiDateTimePicker(c); + + if (nmhdr->code != DTN_DATETIMECHANGE) + return FALSE; + (*(d->onChanged))(d, d->onChangedData); + *lResult = 0; + return TRUE; +} + +static void fromSystemTime(SYSTEMTIME *systime, struct tm *time) +{ + ZeroMemory(time, sizeof (struct tm)); + time->tm_sec = systime->wSecond; + time->tm_min = systime->wMinute; + time->tm_hour = systime->wHour; + time->tm_mday = systime->wDay; + time->tm_mon = systime->wMonth - 1; + time->tm_year = systime->wYear - 1900; + time->tm_wday = systime->wDayOfWeek; + time->tm_isdst = -1; +} + +static void toSystemTime(const struct tm *time, SYSTEMTIME *systime) +{ + ZeroMemory(systime, sizeof (SYSTEMTIME)); + systime->wYear = time->tm_year + 1900; + systime->wMonth = time->tm_mon + 1; + systime->wDayOfWeek = time->tm_wday; + systime->wDay = time->tm_mday; + systime->wHour = time->tm_hour; + systime->wMinute = time->tm_min; + systime->wSecond = time->tm_sec; +} + +static void defaultOnChanged(uiDateTimePicker *d, void *data) +{ + // do nothing +} + +void uiDateTimePickerTime(uiDateTimePicker *d, struct tm *time) +{ + SYSTEMTIME systime; + + if (SendMessageW(d->hwnd, DTM_GETSYSTEMTIME, 0, (LPARAM) (&systime)) != GDT_VALID) + logLastError(L"error getting date and time"); + fromSystemTime(&systime, time); +} + +void uiDateTimePickerSetTime(uiDateTimePicker *d, const struct tm *time) +{ + SYSTEMTIME systime; + + toSystemTime(time, &systime); + if (SendMessageW(d->hwnd, DTM_SETSYSTEMTIME, GDT_VALID, (LPARAM) (&systime)) == 0) + logLastError(L"error setting date and time"); + (*(d->onChanged))(d, d->onChangedData); +} + +void uiDateTimePickerOnChanged(uiDateTimePicker *d, void(*f)(uiDateTimePicker *, void *), void *data) +{ + d->onChanged = f; + d->onChangedData = data; +} + static uiDateTimePicker *finishNewDateTimePicker(DWORD style) { uiDateTimePicker *d; @@ -147,6 +216,8 @@ static uiDateTimePicker *finishNewDateTimePicker(DWORD style) // for the standard styles, this is in the date-time picker itself // for our date/time mode, we do it in a subclass assigned in uiNewDateTimePicker() uiWindowsRegisterReceiveWM_WININICHANGE(d->hwnd); + uiWindowsRegisterWM_NOTIFYHandler(d->hwnd, onWM_NOTIFY, uiControl(d)); + uiDateTimePickerOnChanged(d, defaultOnChanged, NULL); return d; }