GnuCash c935c2f+
Loading...
Searching...
No Matches
gnc-date-edit.c
1/*
2 * gnc-date-edit.c -- Date editor widget
3 *
4 * Copyright (C) 1997, 1998, 1999, 2000 Free Software Foundation
5 * All rights reserved.
6 *
7 * Gnucash is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU Library General Public License
9 * as published by the Free Software Foundation; either version 2 of the
10 * License, or (at your option) any later version.
11 *
12 * Gnucash is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 * Library General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with this program; if not, contact:
19 *
20 * Free Software Foundation Voice: +1-617-542-5942
21 * 51 Franklin Street, Fifth Floor Fax: +1-617-542-2652
22 * Boston, MA 02110-1301, USA gnu@gnu.org
23 *
24 */
25/*
26 @NOTATION@
27 */
28
29/*
30 * Date editor widget
31 *
32 * Authors: Miguel de Icaza
33 * Dave Peticolas <dave@krondo.com>
34 */
35
36#include <config.h>
37
38#include <gtk/gtk.h>
39#include <glib/gi18n.h>
40#include <gdk/gdkkeysyms.h>
41#include <string.h>
42#include <stdlib.h> /* atoi */
43#include <ctype.h> /* isdigit */
44#include <stdio.h>
45
46#include "gnc-date.h"
47#include "gnc-engine.h"
48#include "dialog-utils.h"
49#include "gnc-date-edit.h"
50
51enum
52{
53 DATE_CHANGED,
54 TIME_CHANGED,
55 LAST_SIGNAL
56};
57
58enum
59{
60 PROP_0,
61 PROP_TIME,
62};
63
64static QofLogModule log_module = GNC_MOD_GUI;
65static guint date_edit_signals [LAST_SIGNAL] = { 0 };
66
67static void gnc_date_edit_dispose (GObject *object);
68static void gnc_date_edit_finalize (GObject *object);
69static void gnc_date_edit_forall (GtkContainer *container,
70 gboolean include_internals,
71 GtkCallback callback,
72 gpointer callbabck_data);
73static struct tm gnc_date_edit_get_date_internal (GNCDateEdit *gde);
74static int date_accel_key_press(GtkWidget *widget,
75 GdkEventKey *event,
76 gpointer data);
77
78G_DEFINE_TYPE (GNCDateEdit, gnc_date_edit, GTK_TYPE_BOX)
79
80static char *
81gnc_strtok_r (char *s, const char *delim, char **save_ptr)
82{
83 char *token;
84
85 if (s == NULL)
86 s = *save_ptr;
87
88 /* Scan leading delimiters. */
89 s += strspn (s, delim);
90 if (!s || *s == '\0')
91 return NULL;
92
93 /* Find the end of the token. */
94 token = s;
95 s = strpbrk (token, delim);
96 if (s == NULL)
97 /* This token finishes the string. */
98 *save_ptr = strchr (token, '\0');
99 else
100 {
101 /* Terminate the token and make *SAVE_PTR point past it. */
102 *s = '\0';
103 *save_ptr = s + 1;
104 }
105 return token;
106}
107
108static void
109gnc_date_edit_popdown(GNCDateEdit *gde)
110{
111 GdkSeat *seat;
112 GdkDevice *pointer;
113 GdkWindow *window;
114
115 g_return_if_fail (GNC_IS_DATE_EDIT (gde));
116
117 ENTER("gde %p", gde);
118
119 window = gtk_widget_get_window (GTK_WIDGET(gde));
120
121 seat = gdk_display_get_default_seat (gdk_window_get_display (window));
122 pointer = gdk_seat_get_pointer (seat);
123
124 gtk_grab_remove (gde->cal_popup);
125 gtk_widget_hide (gde->cal_popup);
126
127 if (pointer)
128 gdk_seat_ungrab (seat);
129
130 gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (gde->date_button),
131 FALSE);
132
133 LEAVE(" ");
134}
135
136static void
137day_selected (GtkCalendar *calendar, GNCDateEdit *gde)
138{
139 time64 t;
140 guint year, month, day;
141 gde->in_selected_handler = TRUE;
142 gtk_calendar_get_date (calendar, &year, &month, &day);
143 /* GtkCalendar returns a 0-based month */
144 t = gnc_dmy2time64 (day, month + 1, year);
145 gnc_date_edit_set_time (gde, t);
146 gde->in_selected_handler = FALSE;
147}
148
149static void
150day_selected_double_click (GtkCalendar *calendar, GNCDateEdit *gde)
151{
152 gnc_date_edit_popdown (gde);
153}
154
155static gint
156delete_popup (GtkWidget *widget, GdkEvent *event, gpointer data)
157{
158 GNCDateEdit *gde;
159
160 gde = data;
161 gnc_date_edit_popdown (gde);
162
163 return TRUE;
164}
165
166static gboolean
167key_press_popup (GtkWidget *widget, GdkEventKey *event, gpointer data)
168{
169 GNCDateEdit *gde = data;
170
171 if (event->keyval != GDK_KEY_Return &&
172 event->keyval != GDK_KEY_KP_Enter &&
173 event->keyval != GDK_KEY_Escape)
174 return date_accel_key_press(gde->date_entry, event, data);
175
176 gde = data;
177 g_signal_stop_emission_by_name (G_OBJECT (widget), "key-press-event");
178 gnc_date_edit_popdown (gde);
179
180 return TRUE;
181}
182
183static void
184position_popup (GNCDateEdit *gde)
185{
186 gint x, y;
187 gint bwidth, bheight;
188 GtkRequisition req;
189 GtkAllocation alloc;
190
191 gtk_widget_get_preferred_size (gde->cal_popup, &req, NULL);
192
193 gdk_window_get_origin (gtk_widget_get_window (gde->date_button), &x, &y);
194
195 gtk_widget_get_allocation (gde->date_button, &alloc);
196 x += alloc.x;
197 y += alloc.y;
198 bwidth = alloc.width;
199 bheight = alloc.height;
200
201 x += bwidth - req.width;
202 y += bheight;
203
204 if (x < 0)
205 x = 0;
206
207 if (y < 0)
208 y = 0;
209
210 gtk_window_move (GTK_WINDOW (gde->cal_popup), x, y);
211}
212
213/* Pulled from gtkcombobox.c */
214static gboolean
215popup_grab_on_window (GdkWindow *window,
216 GdkDevice *keyboard,
217 GdkDevice *pointer,
218 guint32 activate_time)
219{
220 GdkDisplay *display = gdk_window_get_display (window);
221 GdkSeat *seat = gdk_display_get_default_seat (display);
222 GdkEvent *event = gtk_get_current_event ();
223
224 if (keyboard && gdk_seat_grab (seat, window, GDK_SEAT_CAPABILITY_KEYBOARD, TRUE, NULL,
225 event, NULL, NULL) != GDK_GRAB_SUCCESS)
226 return FALSE;
227
228 if (pointer && gdk_seat_grab (seat, window, GDK_SEAT_CAPABILITY_POINTER, TRUE, NULL,
229 event, NULL, NULL) != GDK_GRAB_SUCCESS)
230 {
231 if (keyboard)
232 gdk_seat_ungrab (seat);
233
234 return FALSE;
235 }
236 return TRUE;
237}
238
239
240static void
241gnc_date_edit_popup (GNCDateEdit *gde)
242{
243 GtkWidget *toplevel;
244 struct tm mtm;
245 gboolean date_was_valid;
246 GdkDevice *device, *keyboard, *pointer;
247
248 g_return_if_fail (GNC_IS_DATE_EDIT (gde));
249
250 ENTER("gde %p", gde);
251
252 device = gtk_get_current_event_device ();
253
254 /* This code is pretty much just copied from gtk_date_edit_get_date */
255 date_was_valid = qof_scan_date (gtk_entry_get_text (GTK_ENTRY (gde->date_entry)),
256 &mtm.tm_mday, &mtm.tm_mon, &mtm.tm_year);
257 if (!date_was_valid)
258 {
259 /* No valid date. Hacky workaround: Instead of crashing we randomly choose today's date. */
261 }
262
263 mtm.tm_mon--;
264
265 /* Hope the user does not actually mean years early in the A.D. days...
266 * This date widget will obviously not work for a history program :-)
267 */
268 if (mtm.tm_year >= 1900)
269 mtm.tm_year -= 1900;
270
272
273 /* Set the calendar. */
274 gtk_calendar_select_day (GTK_CALENDAR (gde->calendar), 1);
275 gtk_calendar_select_month (GTK_CALENDAR (gde->calendar), mtm.tm_mon,
276 1900 + mtm.tm_year);
277 gtk_calendar_select_day (GTK_CALENDAR (gde->calendar), mtm.tm_mday);
278
279 /* Make sure we'll get notified of clicks outside the popup
280 * window so we can properly pop down if that happens. */
281 toplevel = gtk_widget_get_toplevel (GTK_WIDGET (gde));
282 if (GTK_IS_WINDOW (toplevel))
283 {
284 gtk_window_group_add_window (
285 gtk_window_get_group (GTK_WINDOW (toplevel)),
286 GTK_WINDOW (gde->cal_popup));
287 gtk_window_set_transient_for (GTK_WINDOW (gde->cal_popup),
288 GTK_WINDOW (toplevel));
289 }
290
291 position_popup (gde);
292
293 gtk_widget_show (gde->cal_popup);
294
295 gtk_widget_grab_focus (gde->cal_popup);
296 gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (gde->date_button),
297 TRUE);
298
299 if (gdk_device_get_source (device) == GDK_SOURCE_KEYBOARD)
300 {
301 keyboard = device;
302 pointer = gdk_device_get_associated_device (device);
303 }
304 else
305 {
306 pointer = device;
307 keyboard = gdk_device_get_associated_device (device);
308 }
309
310 if (!gtk_widget_has_focus (gde->calendar))
311 gtk_widget_grab_focus (gde->calendar);
312
313 if (!popup_grab_on_window (gtk_widget_get_window ((GTK_WIDGET(gde->cal_popup))),
314 keyboard, pointer, GDK_CURRENT_TIME))
315 {
316 gtk_widget_hide (gde->cal_popup);
317 LEAVE("Failed to grab window");
318 return;
319 }
320
321 gtk_grab_add (gde->cal_popup);
322
323 LEAVE(" ");
324}
325
326/* This function is a customized gtk_combo_box_list_button_pressed(). */
327static gboolean
328gnc_date_edit_button_pressed (GtkWidget *widget,
329 GdkEventButton *event,
330 gpointer data)
331{
332 GNCDateEdit *gde = GNC_DATE_EDIT(data);
333 GtkWidget *ewidget = gtk_get_event_widget ((GdkEvent *)event);
334
335 ENTER("widget=%p, ewidget=%p, event=%p, gde=%p", widget, ewidget, event, gde);
336
337 /* While popped up, ignore presses outside the popup window. */
338 if (ewidget == gde->cal_popup)
339 {
340 LEAVE("Press on calendar. Ignoring.");
341 return TRUE;
342 }
343
344 /* If the press isn't to make the popup appear, just propagate it. */
345 if (ewidget != gde->date_button ||
346 gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (gde->date_button)))
347 {
348 LEAVE("Press, not on popup button, or while popup is raised.");
349 return FALSE;
350 }
351
352 if (!gtk_widget_has_focus (gde->date_button))
353 gtk_widget_grab_focus (gde->date_button);
354
355 gde->popup_in_progress = TRUE;
356
357 gnc_date_edit_popup (gde);
358
359 gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (gde->date_button), TRUE);
360
361 LEAVE("Popup in progress.");
362 return TRUE;
363}
364
365static gboolean
366gnc_date_edit_button_released (GtkWidget *widget,
367 GdkEventButton *event,
368 gpointer data)
369{
370 GNCDateEdit *gde = GNC_DATE_EDIT(data);
371 GtkWidget *ewidget = gtk_get_event_widget ((GdkEvent *)event);
372 gboolean popup_in_progress = FALSE;
373
374 ENTER("widget=%p, ewidget=%p, event=%p, gde=%p", widget, ewidget, event, gde);
375
376 if (gde->popup_in_progress)
377 {
378 popup_in_progress = TRUE;
379 gde->popup_in_progress = FALSE;
380 }
381
382 /* Propagate releases on the calendar. */
383 if (ewidget == gde->calendar)
384 {
385 LEAVE("Button release on calendar.");
386 return FALSE;
387 }
388
389 if (ewidget == gde->date_button)
390 {
391 /* Pop down if we're up and it isn't due to the preceding press. */
392 if (!popup_in_progress &&
393 gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (gde->date_button)))
394 {
395 gnc_date_edit_popdown (gde);
396 LEAVE("Release on button, not in progress. Popped down.");
397 return TRUE;
398 }
399
400 LEAVE("Button release on button. Allowing.");
401 return FALSE;
402 }
403
404 /* Pop down on a release anywhere else. */
405 gnc_date_edit_popdown (gde);
406 LEAVE("Release not on button or calendar. Popping down.");
407 return TRUE;
408}
409
410static void
411gnc_date_edit_button_toggled (GtkWidget *widget, GNCDateEdit *gde)
412{
413 ENTER("widget %p, gde %p", widget, gde);
414
415 if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (widget)))
416 {
417 if (!gde->popup_in_progress)
418 gnc_date_edit_popup (gde);
419 }
420 else
421 gnc_date_edit_popdown (gde);
422
423 LEAVE(" ");
424}
425
426static void
427set_time (GtkWidget *widget, GNCDateEdit *gde)
428{
429 gchar *text;
430 GtkTreeModel *model;
431 GtkTreeIter iter;
432
433 model = gtk_combo_box_get_model(GTK_COMBO_BOX(gde->time_combo));
434 gtk_combo_box_get_active_iter (GTK_COMBO_BOX(gde->time_combo), &iter);
435 gtk_tree_model_get( model, &iter, 0, &text, -1 );
436
437 gtk_entry_set_text (GTK_ENTRY (gde->time_entry), text);
438 if(text)
439 g_free(text);
440 g_signal_emit (G_OBJECT (gde), date_edit_signals [TIME_CHANGED], 0);
441}
442
443static void
444fill_time_combo (GtkWidget *widget, GNCDateEdit *gde)
445{
446 GtkTreeModel *model;
447 GtkTreeIter hour_iter, min_iter;
448 struct tm *tm_returned;
449 struct tm mtm;
450 time64 current_time;
451 int i, j;
452
453 if (gde->lower_hour > gde->upper_hour)
454 return;
455
456 model = gtk_combo_box_get_model (GTK_COMBO_BOX(gde->time_combo));
457
458 gtk_tree_store_clear (GTK_TREE_STORE(model));
459
460 gnc_time (&current_time);
461 tm_returned = gnc_localtime_r (&current_time, &mtm);
462 g_return_if_fail(tm_returned != NULL);
463
464 for (i = gde->lower_hour; i <= gde->upper_hour; i++)
465 {
466 char buffer [40];
467 mtm.tm_hour = i;
468 mtm.tm_min = 0;
469
470 if (gde->flags & GNC_DATE_EDIT_24_HR)
471 qof_strftime (buffer, sizeof (buffer), "%H:00", &mtm);
472 else
473 qof_strftime (buffer, sizeof (buffer), "%I:00 %p ", &mtm);
474
475 gtk_tree_store_append (GTK_TREE_STORE(model), &hour_iter, NULL);
476 gtk_tree_store_set (GTK_TREE_STORE(model), &hour_iter, 0, buffer, -1);
477
478 for (j = 0; j < 60; j += 15)
479 {
480 mtm.tm_min = j;
481
482 if (gde->flags & GNC_DATE_EDIT_24_HR)
483 qof_strftime (buffer, sizeof (buffer), "%H:%M", &mtm);
484 else
485 qof_strftime (buffer, sizeof (buffer), "%I:%M %p", &mtm);
486
487 gtk_tree_store_append(GTK_TREE_STORE(model), &min_iter, &hour_iter );
488 gtk_tree_store_set (GTK_TREE_STORE(model), &min_iter, 0, buffer, -1);
489 }
490 }
491}
492
493static void
494gnc_date_edit_set_time_internal (GNCDateEdit *gde, time64 the_time)
495{
496 char buffer [MAX_DATE_LENGTH + 1];
497 struct tm *mytm = gnc_localtime (&the_time);
498
499 g_return_if_fail(mytm != NULL);
500
501 /* Update the date text. */
502 qof_print_date_dmy_buff(buffer, MAX_DATE_LENGTH,
503 mytm->tm_mday,
504 mytm->tm_mon + 1,
505 1900 + mytm->tm_year);
506 gtk_entry_set_text(GTK_ENTRY(gde->date_entry), buffer);
507
508 /* Update the calendar. */
509 if (!gde->in_selected_handler)
510 {
511 gtk_calendar_select_day(GTK_CALENDAR (gde->calendar), 1);
512 gtk_calendar_select_month(GTK_CALENDAR (gde->calendar),
513 mytm->tm_mon, 1900 + mytm->tm_year);
514 gtk_calendar_select_day(GTK_CALENDAR (gde->calendar), mytm->tm_mday);
515 }
516
517 /* Set the time of day. */
518 if (gde->flags & GNC_DATE_EDIT_24_HR)
519 qof_strftime (buffer, sizeof (buffer), "%H:%M", mytm);
520 else
521 qof_strftime (buffer, sizeof (buffer), "%I:%M %p", mytm);
522 gtk_entry_set_text(GTK_ENTRY(gde->time_entry), buffer);
523
524 gnc_tm_free (mytm);
525
526 g_signal_emit (gde, date_edit_signals [DATE_CHANGED], 0);
527 g_signal_emit (gde, date_edit_signals [TIME_CHANGED], 0);
528}
529
530
538static void
539gnc_date_edit_get_property (GObject *object,
540 guint prop_id,
541 GValue *value,
542 GParamSpec *pspec)
543{
544 GNCDateEdit *date_edit = GNC_DATE_EDIT (object);
545
546 switch (prop_id)
547 {
548 case PROP_TIME:
549 g_value_set_int64 (value, gnc_date_edit_get_date (date_edit));
550 break;
551 default:
552 G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
553 break;
554 }
555}
556
557
566static void
567gnc_date_edit_set_property (GObject *object,
568 guint prop_id,
569 const GValue *value,
570 GParamSpec *pspec)
571{
572 GNCDateEdit *date_edit = GNC_DATE_EDIT (object);
573
574 switch (prop_id)
575 {
576 case PROP_TIME:
577 gnc_date_edit_set_time_internal (date_edit, g_value_get_int64(value));
578 break;
579 default:
580 G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
581 break;
582 }
583}
584
585static void
586gnc_date_edit_class_init (GNCDateEditClass *klass)
587{
588 GtkContainerClass *container_class = (GtkContainerClass *) klass;
589 GObjectClass *object_class = (GObjectClass *) klass;
590
591 container_class->forall = gnc_date_edit_forall;
592 object_class->set_property = gnc_date_edit_set_property;
593 object_class->get_property = gnc_date_edit_get_property;
594 object_class->dispose = gnc_date_edit_dispose;
595 object_class->finalize = gnc_date_edit_finalize;
596
597 date_edit_signals [TIME_CHANGED] =
598 g_signal_new ("time_changed",
599 G_TYPE_FROM_CLASS (object_class),
600 G_SIGNAL_RUN_FIRST,
601 G_STRUCT_OFFSET (GNCDateEditClass, time_changed),
602 NULL, NULL,
603 g_cclosure_marshal_VOID__VOID,
604 G_TYPE_NONE, 0);
605
606 date_edit_signals [DATE_CHANGED] =
607 g_signal_new ("date_changed",
608 G_TYPE_FROM_CLASS (object_class),
609 G_SIGNAL_RUN_FIRST,
610 G_STRUCT_OFFSET (GNCDateEditClass, date_changed),
611 NULL, NULL,
612 g_cclosure_marshal_VOID__VOID,
613 G_TYPE_NONE, 0);
614
615 g_object_class_install_property(object_class,
616 PROP_TIME,
617 g_param_spec_int64("time",
618 "Date/time (seconds)",
619 "Date/time represented in seconds since midnight UTC, 1 January 1970",
620 G_MININT64,
621 G_MAXINT64,
622 0,
623 G_PARAM_READWRITE));
624
625 klass->date_changed = NULL;
626 klass->time_changed = NULL;
627}
628
629static void
630gnc_date_edit_init (GNCDateEdit *gde)
631{
632 gtk_orientable_set_orientation (GTK_ORIENTABLE(gde), GTK_ORIENTATION_HORIZONTAL);
633
634 // Set the name for this widget so it can be easily manipulated with css
635 gtk_widget_set_name (GTK_WIDGET(gde), "gnc-id-date-edit");
636
637 gde->disposed = FALSE;
638 gde->popup_in_progress = FALSE;
639 gde->lower_hour = 7;
640 gde->upper_hour = 19;
641 gde->flags = GNC_DATE_EDIT_SHOW_TIME;
642 gde->in_selected_handler = FALSE;
643}
644
645static void
646gnc_date_edit_finalize (GObject *object)
647{
648
649 g_return_if_fail (object != NULL);
650 g_return_if_fail (GNC_IS_DATE_EDIT (object));
651
652 G_OBJECT_CLASS (gnc_date_edit_parent_class)->finalize (object);
653}
654
655static void
656gnc_date_edit_dispose (GObject *object)
657{
658 GNCDateEdit *gde;
659
660 g_return_if_fail (object != NULL);
661 g_return_if_fail (GNC_IS_DATE_EDIT (object));
662
663 gde = GNC_DATE_EDIT (object);
664
665 if (gde->disposed)
666 return;
667
668 gde->disposed = TRUE;
669
670 /* Only explicitly destroy the toplevel elements */
671
672 gtk_widget_destroy (GTK_WIDGET(gde->date_entry));
673 gde->date_entry = NULL;
674
675 gtk_widget_destroy (GTK_WIDGET(gde->date_button));
676 gde->date_button = NULL;
677
678 gtk_widget_destroy (GTK_WIDGET(gde->time_entry));
679 gde->time_entry = NULL;
680
681 gtk_widget_destroy (GTK_WIDGET(gde->time_combo));
682 gde->time_combo = NULL;
683
684 G_OBJECT_CLASS (gnc_date_edit_parent_class)->dispose (object);
685}
686
687static void
688gnc_date_edit_forall (GtkContainer *container, gboolean include_internals,
689 GtkCallback callback, gpointer callback_data)
690{
691 g_return_if_fail (container != NULL);
692 g_return_if_fail (GNC_IS_DATE_EDIT (container));
693 g_return_if_fail (callback != NULL);
694
695 /* Let GtkBox handle things only if the internal widgets need
696 * to be poked. */
697 if (!include_internals)
698 return;
699
700 if (!GTK_CONTAINER_CLASS (gnc_date_edit_parent_class)->forall)
701 return;
702
703 GTK_CONTAINER_CLASS (gnc_date_edit_parent_class)->forall (container,
704 include_internals,
705 callback,
706 callback_data);
707}
708
717void
718gnc_date_edit_set_time (GNCDateEdit *gde, time64 the_time)
719{
720 g_return_if_fail (gde != NULL);
721 g_return_if_fail (GNC_IS_DATE_EDIT (gde));
722
723 /* If the_time is invalid, use the last valid time
724 * seen (or as a last resort, the current date). */
725 gde->initial_time = the_time;
726
727 g_object_set (G_OBJECT (gde), "time", the_time, NULL);
728}
729
730void
731gnc_date_edit_set_gdate (GNCDateEdit *gde, const GDate *date)
732{
733 struct tm mytm;
734 time64 t;
735
736 g_return_if_fail(gde && GNC_IS_DATE_EDIT(gde) &&
737 date && g_date_valid(date));
738 g_date_to_struct_tm(date, &mytm);
739 t = gnc_mktime(&mytm);
740 gnc_date_edit_set_time(gde, t);
741}
742
752void
753gnc_date_edit_set_popup_range (GNCDateEdit *gde, int low_hour, int up_hour)
754{
755 g_return_if_fail (gde != NULL);
756 g_return_if_fail (GNC_IS_DATE_EDIT (gde));
757
758 gde->lower_hour = low_hour;
759 gde->upper_hour = up_hour;
760
761 fill_time_combo(NULL, gde);
762}
763
764/* This code should be kept in sync with src/register/datecell.c */
765static int
766date_accel_key_press(GtkWidget *widget, GdkEventKey *event, gpointer data)
767{
768 GNCDateEdit *gde = data;
769 const char *string;
770 struct tm tm;
771
772 string = gtk_entry_get_text (GTK_ENTRY (widget));
773
774 tm = gnc_date_edit_get_date_internal (gde);
775
776 if (!gnc_handle_date_accelerator (event, &tm, string))
777 return FALSE;
778
779 gnc_date_edit_set_time (gde, gnc_mktime (&tm));
780
781 g_signal_emit (G_OBJECT (gde), date_edit_signals [TIME_CHANGED], 0);
782 return TRUE;
783}
784
785static gboolean
786key_press_entry (GtkWidget *widget, GdkEventKey *event, gpointer data)
787{
788 if (!date_accel_key_press(widget, event, data))
789 return FALSE;
790
791 g_signal_stop_emission_by_name (widget, "key-press-event");
792 return TRUE;
793}
794
795static int
796date_focus_out_event(GtkWidget *widget, GdkEventKey *event, gpointer data)
797{
798 GNCDateEdit *gde = data;
799 struct tm tm;
800
801 /* Get the date entered and attempt to use it. */
802 tm = gnc_date_edit_get_date_internal (gde);
803 gnc_date_edit_set_time (gde, gnc_mktime (&tm));
804
805 g_signal_emit (gde, date_edit_signals [DATE_CHANGED], 0);
806 g_signal_emit (gde, date_edit_signals [TIME_CHANGED], 0);
807
808 return FALSE;
809}
810
811static void
812create_children (GNCDateEdit *gde)
813{
814 GtkWidget *frame;
815 GtkWidget *hbox;
816 GtkWidget *arrow;
817 GtkTreeStore *store;
818 GtkCellRenderer *cell;
819
820 /* Create the text entry area. */
821 gde->date_entry = gtk_entry_new ();
822 gtk_entry_set_width_chars (GTK_ENTRY (gde->date_entry), 11);
823 gtk_box_pack_start (GTK_BOX (gde), gde->date_entry, TRUE, TRUE, 0);
824 gtk_widget_show (GTK_WIDGET(gde->date_entry));
825 g_signal_connect (G_OBJECT (gde->date_entry), "key-press-event",
826 G_CALLBACK (key_press_entry), gde);
827 g_signal_connect (G_OBJECT (gde->date_entry), "focus-out-event",
828 G_CALLBACK (date_focus_out_event), gde);
829
830 /* Create the popup button. */
831 gde->date_button = gtk_toggle_button_new ();
832 g_signal_connect (gde->date_button, "button-press-event",
833 G_CALLBACK(gnc_date_edit_button_pressed), gde);
834 g_signal_connect (G_OBJECT (gde->date_button), "toggled",
835 G_CALLBACK (gnc_date_edit_button_toggled), gde);
836 gtk_box_pack_start (GTK_BOX (gde), gde->date_button, FALSE, FALSE, 0);
837
838 hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 3);
839 gtk_box_set_homogeneous (GTK_BOX (hbox), FALSE);
840 gtk_container_add (GTK_CONTAINER (gde->date_button), hbox);
841 gtk_widget_show (GTK_WIDGET(hbox));
842
843 /* Calendar label, only shown if the date editor has a time field */
844 gde->cal_label = gtk_label_new (_("Calendar"));
845 gnc_label_set_alignment (gde->cal_label, 0.0, 0.5);
846 gtk_box_pack_start (GTK_BOX (hbox), gde->cal_label, TRUE, TRUE, 0);
847 if (gde->flags & GNC_DATE_EDIT_SHOW_TIME)
848 gtk_widget_show (GTK_WIDGET(gde->cal_label));
849
850 /* Graphic for the popup button. */
851 arrow = gtk_image_new_from_icon_name ("pan-down-symbolic", GTK_ICON_SIZE_BUTTON);
852
853 gtk_box_pack_start (GTK_BOX (hbox), arrow, TRUE, FALSE, 0);
854 gtk_widget_show (GTK_WIDGET(arrow));
855
856 gtk_widget_show (GTK_WIDGET(gde->date_button));
857
858 /* Time entry controls. */
859 gde->time_entry = gtk_entry_new ();
860 gtk_entry_set_max_length (GTK_ENTRY(gde->time_entry), 12);
861 gtk_widget_set_size_request (GTK_WIDGET(gde->time_entry), 88, -1);
862 gtk_box_pack_start (GTK_BOX (gde), gde->time_entry, TRUE, TRUE, 0);
863
864 store = gtk_tree_store_new(1, G_TYPE_STRING);
865 gde->time_combo = GTK_WIDGET(gtk_combo_box_new_with_model(GTK_TREE_MODEL(store)));
866 g_object_unref(store);
867 /* Create cell renderer. */
868 cell = gtk_cell_renderer_text_new();
869 /* Pack it to the combo box. */
870 gtk_cell_layout_pack_start( GTK_CELL_LAYOUT( gde->time_combo ), cell, TRUE );
871 /* Connect renderer to data source */
872 gtk_cell_layout_set_attributes( GTK_CELL_LAYOUT( gde->time_combo ), cell, "text", 0, NULL );
873
874 g_signal_connect (G_OBJECT (gde->time_combo), "changed",
875 G_CALLBACK (set_time), gde);
876
877 gtk_box_pack_start (GTK_BOX (gde), gde->time_combo, FALSE, FALSE, 0);
878
879 /* We do not create the popup menu with the hour range until we are
880 * realized, so that it uses the values that the user might supply in a
881 * future call to gnc_date_edit_set_popup_range
882 */
883 g_signal_connect (G_OBJECT (gde), "realize",
884 G_CALLBACK (fill_time_combo), gde);
885
886 if (gde->flags & GNC_DATE_EDIT_SHOW_TIME)
887 {
888 gtk_widget_show (GTK_WIDGET(gde->time_entry));
889 gtk_widget_show (GTK_WIDGET(gde->time_combo));
890 }
891
892 gde->cal_popup = gtk_window_new (GTK_WINDOW_POPUP);
893 gtk_widget_set_name (gde->cal_popup, "gnc-date-edit-popup-window");
894
895 gtk_window_set_type_hint (GTK_WINDOW (gde->cal_popup),
896 GDK_WINDOW_TYPE_HINT_COMBO);
897
898 gtk_widget_set_events (GTK_WIDGET(gde->cal_popup),
899 gtk_widget_get_events (GTK_WIDGET(gde->cal_popup)) |
900 GDK_KEY_PRESS_MASK);
901
902 g_signal_connect (gde->cal_popup, "delete-event",
903 G_CALLBACK(delete_popup), gde);
904 g_signal_connect (gde->cal_popup, "key-press-event",
905 G_CALLBACK(key_press_popup), gde);
906 g_signal_connect (gde->cal_popup, "button-press-event",
907 G_CALLBACK(gnc_date_edit_button_pressed), gde);
908 g_signal_connect (gde->cal_popup, "button-release-event",
909 G_CALLBACK(gnc_date_edit_button_released), gde);
910 gtk_window_set_resizable (GTK_WINDOW (gde->cal_popup), FALSE);
911 gtk_window_set_screen (GTK_WINDOW (gde->cal_popup),
912 gtk_widget_get_screen (GTK_WIDGET (gde)));
913
914 frame = gtk_frame_new (NULL);
915 gtk_frame_set_shadow_type (GTK_FRAME (frame), GTK_SHADOW_NONE);
916 gtk_container_add (GTK_CONTAINER (gde->cal_popup), frame);
917 gtk_widget_show (GTK_WIDGET(frame));
918
919 gde->calendar = gtk_calendar_new ();
920 gtk_calendar_set_display_options
921 (GTK_CALENDAR (gde->calendar),
922 (GTK_CALENDAR_SHOW_DAY_NAMES
923 | GTK_CALENDAR_SHOW_HEADING));
924 g_signal_connect (gde->calendar, "button-release-event",
925 G_CALLBACK(gnc_date_edit_button_released), gde);
926 g_signal_connect (G_OBJECT (gde->calendar), "day-selected",
927 G_CALLBACK (day_selected), gde);
928 g_signal_connect (G_OBJECT (gde->calendar),
929 "day-selected-double-click",
930 G_CALLBACK (day_selected_double_click), gde);
931 gtk_container_add (GTK_CONTAINER (frame), gde->calendar);
932 gtk_widget_show (GTK_WIDGET(gde->calendar));
933}
934
946GtkWidget *
947gnc_date_edit_new (time64 the_time, int show_time, int use_24_format)
948{
949 return gnc_date_edit_new_flags
950 (the_time,
951 ((show_time ? GNC_DATE_EDIT_SHOW_TIME : 0)
952 | (use_24_format ? GNC_DATE_EDIT_24_HR : 0)));
953}
954
955/*
956 * Create a new GncDateEdit widget from a glade file. The widget
957 * generated is set to today's date, and will not show a time as part
958 * of the date. This function does not use any of the arguments
959 * passed by glade.
960 */
961GtkWidget *
962gnc_date_edit_new_glade (gchar *widget_name,
963 gchar *string1, gchar *string2,
964 gint int1, gint int2)
965{
966 GtkWidget *widget;
967
968 /* None of the standard glade arguments are used. */
969 widget = gnc_date_edit_new(time(NULL), FALSE, FALSE);
970 gtk_widget_show(widget);
971 return widget;
972}
973
974
984GtkWidget *
985gnc_date_edit_new_flags (time64 the_time, GNCDateEditFlags flags)
986{
987 GNCDateEdit *gde;
988
989 gde = g_object_new (GNC_TYPE_DATE_EDIT, NULL, NULL);
990
991 gde->flags = flags;
992 gde->initial_time = -1;
993 create_children (gde);
994 gnc_date_edit_set_time (gde, the_time);
995
996 return GTK_WIDGET (gde);
997}
998
999static struct tm
1000gnc_date_edit_get_date_internal (GNCDateEdit *gde)
1001{
1002 struct tm tm = {0};
1003 char *str;
1004 gchar *flags = NULL;
1005 gboolean date_was_valid;
1006
1007 /* Assert, because we're just hosed if it's NULL */
1008 g_assert(gde != NULL);
1009 g_assert(GNC_IS_DATE_EDIT(gde));
1010
1011 date_was_valid = qof_scan_date (gtk_entry_get_text (GTK_ENTRY (gde->date_entry)),
1012 &tm.tm_mday, &tm.tm_mon, &tm.tm_year);
1013
1014 if (!date_was_valid)
1015 {
1016 /* Hm... no valid date. What should we do now? As a hacky workaround we
1017 revert to today's date. Alternatively we can return some value that
1018 signals that we don't get a valid date, but all callers of this
1019 function will have to check this. Alas, I'm too lazy to do this here. */
1021 }
1022 else
1023 {
1024 tm.tm_mon--;
1025 tm.tm_year -= 1900;
1026 }
1027
1028 if (gde->flags & GNC_DATE_EDIT_SHOW_TIME)
1029 {
1030 char *tokp = NULL;
1031 gchar *temp;
1032
1033 str = g_strdup (gtk_entry_get_text
1034 (GTK_ENTRY (gde->time_entry)));
1035 temp = gnc_strtok_r (str, ": ", &tokp);
1036 if (temp)
1037 {
1038 tm.tm_hour = atoi (temp);
1039 temp = gnc_strtok_r (NULL, ": ", &tokp);
1040 if (temp)
1041 {
1042 if (isdigit (*temp))
1043 {
1044 tm.tm_min = atoi (temp);
1045 flags = gnc_strtok_r (NULL, ": ",
1046 &tokp);
1047 if (flags && isdigit (*flags))
1048 {
1049 tm.tm_sec = atoi (flags);
1050 flags = gnc_strtok_r (NULL,
1051 ": ",
1052 &tokp);
1053 }
1054 }
1055 else
1056 flags = temp;
1057 }
1058 }
1059
1060 if (flags && (strcasecmp (flags, "PM") == 0))
1061 {
1062 if (tm.tm_hour < 12)
1063 tm.tm_hour += 12;
1064 }
1065 g_free (str);
1066 }
1067 else
1068 {
1070 }
1071
1072 tm.tm_isdst = -1;
1073
1074 return tm;
1075}
1076
1083time64
1084gnc_date_edit_get_date (GNCDateEdit *gde)
1085{
1086 struct tm tm;
1087
1088 g_return_val_if_fail (gde != NULL, 0);
1089 g_return_val_if_fail (GNC_IS_DATE_EDIT (gde), 0);
1090
1091 tm = gnc_date_edit_get_date_internal (gde);
1092
1093 return gnc_mktime (&tm);
1094}
1095
1096void
1097gnc_date_edit_get_gdate (GNCDateEdit *gde, GDate *date)
1098{
1099 time64 t;
1100
1101 g_return_if_fail (gde && date);
1102 g_return_if_fail (GNC_IS_DATE_EDIT (gde));
1103
1104 t = gnc_date_edit_get_date(gde);
1105 g_date_clear (date, 1);
1106 gnc_gdate_set_time64 (date, t);
1107}
1108
1116time64
1117gnc_date_edit_get_date_end (GNCDateEdit *gde)
1118{
1119 struct tm tm;
1120
1121 g_return_val_if_fail (gde != NULL, 0);
1122 g_return_val_if_fail (GNC_IS_DATE_EDIT (gde), 0);
1123
1124 tm = gnc_date_edit_get_date_internal (gde);
1125 gnc_tm_set_day_end(&tm);
1126
1127 return gnc_mktime (&tm);
1128}
1129
1137void
1138gnc_date_edit_set_flags (GNCDateEdit *gde, GNCDateEditFlags flags)
1139{
1140 GNCDateEditFlags old_flags;
1141
1142 g_return_if_fail (gde != NULL);
1143 g_return_if_fail (GNC_IS_DATE_EDIT (gde));
1144
1145 old_flags = gde->flags;
1146 gde->flags = flags;
1147
1148 if ((flags & GNC_DATE_EDIT_SHOW_TIME) !=
1149 (old_flags & GNC_DATE_EDIT_SHOW_TIME))
1150 {
1151 if (flags & GNC_DATE_EDIT_SHOW_TIME)
1152 {
1153 gtk_widget_show (gde->cal_label);
1154 gtk_widget_show (gde->time_entry);
1155 gtk_widget_show (gde->time_combo);
1156 }
1157 else
1158 {
1159 gtk_widget_hide (gde->cal_label);
1160 gtk_widget_hide (gde->time_entry);
1161 gtk_widget_hide (gde->time_combo);
1162 }
1163 }
1164
1165 if ((flags & GNC_DATE_EDIT_24_HR) != (old_flags & GNC_DATE_EDIT_24_HR))
1166 /* This will destroy the old menu properly */
1167 fill_time_combo (NULL, gde);
1168
1169}
1170
1179int
1180gnc_date_edit_get_flags (GNCDateEdit *gde)
1181{
1182 g_return_val_if_fail (gde != NULL, 0);
1183 g_return_val_if_fail (GNC_IS_DATE_EDIT (gde), 0);
1184
1185 return gde->flags;
1186}
1187
1197void
1198gnc_date_activates_default (GNCDateEdit *gde, gboolean state)
1199{
1200 if (!gde)
1201 return;
1202
1203 gtk_entry_set_activates_default(GTK_ENTRY(gde->date_entry), state);
1204}
1205
1213void
1214gnc_date_grab_focus (GNCDateEdit *gde)
1215{
1216 if (!gde)
1217 return;
1218
1219 gtk_widget_grab_focus (gde->date_entry);
1220}
1228void
1229gnc_date_make_mnemonic_target (GNCDateEdit *gde, GtkWidget *label)
1230{
1231 if (!gde)
1232 return;
1233
1234 gtk_label_set_mnemonic_widget (GTK_LABEL(label), gde->date_entry);
1235}
1236
1237
Date and Time handling routines.
All type declarations for the whole Gnucash engine.
time64 gnc_mktime(struct tm *time)
calculate seconds from the epoch given a time struct
Definition gnc-date.cpp:219
void gnc_tm_set_day_neutral(struct tm *tm)
The gnc_tm_set_day_neutral() inline routine will set the appropriate fields in the struct tm to indic...
#define MAX_DATE_LENGTH
The maximum length of a string created by the date printers.
Definition gnc-date.h:108
struct tm * gnc_localtime_r(const time64 *secs, struct tm *time)
fill out a time struct from a 64-bit time value adjusted for the current time zone.
Definition gnc-date.cpp:115
void gnc_gdate_set_time64(GDate *gd, time64 time)
Set a GDate to a time64.
void gnc_tm_get_today_neutral(struct tm *tm)
The gnc_tm_get_today_start() routine takes a pointer to a struct tm and fills it in with the timezone...
gboolean qof_scan_date(const char *buff, int *day, int *month, int *year)
qof_scan_date Convert a string into day / month / year integers according to the current dateFormat v...
Definition gnc-date.cpp:991
gint64 time64
Most systems that are currently maintained, including Microsoft Windows, BSD-derived Unixes and Linux...
Definition gnc-date.h:87
void gnc_tm_free(struct tm *time)
free a struct tm* created with gnc_localtime() or gnc_gmtime()
Definition gnc-date.cpp:97
time64 gnc_time(time64 *tbuf)
get the current time
Definition gnc-date.cpp:262
struct tm * gnc_localtime(const time64 *secs)
fill out a time struct from a 64-bit time value.
Definition gnc-date.cpp:103
gsize qof_strftime(gchar *buf, gsize max, const gchar *format, const struct tm *tm)
qof_strftime calls qof_format_time to print a given time and afterwards tries to put the result into ...
#define LEAVE(format, args...)
Print a function exit debugging message.
Definition qoflog.h:282
#define ENTER(format, args...)
Print a function entry debugging message.
Definition qoflog.h:272