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
51 enum
52 {
53 DATE_CHANGED,
54 TIME_CHANGED,
55 LAST_SIGNAL
56 };
57
58 enum
59 {
60 PROP_0,
61 PROP_TIME,
62 };
63
64 static QofLogModule log_module = GNC_MOD_GUI;
65 static guint date_edit_signals [LAST_SIGNAL] = { 0 };
66
67
68 static void gnc_date_edit_init (GNCDateEdit *gde);
69 static void gnc_date_edit_class_init (GNCDateEditClass *klass);
70 static void gnc_date_edit_dispose (GObject *object);
71 static void gnc_date_edit_finalize (GObject *object);
72 static void gnc_date_edit_forall (GtkContainer *container,
73 gboolean include_internals,
74 GtkCallback callback,
75 gpointer callbabck_data);
76 static struct tm gnc_date_edit_get_date_internal (GNCDateEdit *gde);
77 static int date_accel_key_press(GtkWidget *widget,
78 GdkEventKey *event,
79 gpointer data);
80
81
82 static GtkBoxClass *parent_class;
83
84 /**
85 * gnc_date_edit_get_type:
86 *
87 * Returns the GType for the GNCDateEdit widget
88 */
89 GType
gnc_date_edit_get_type(void)90 gnc_date_edit_get_type (void)
91 {
92 static GType date_edit_type = 0;
93
94 if (date_edit_type == 0)
95 {
96 static const GTypeInfo date_edit_info =
97 {
98 sizeof (GNCDateEditClass),
99 NULL,
100 NULL,
101 (GClassInitFunc) gnc_date_edit_class_init,
102 NULL,
103 NULL,
104 sizeof (GNCDateEdit),
105 0, /* n_preallocs */
106 (GInstanceInitFunc) gnc_date_edit_init,
107 NULL,
108 };
109
110 date_edit_type = g_type_register_static (GTK_TYPE_BOX,
111 "GNCDateEdit",
112 &date_edit_info, 0);
113 }
114
115 return date_edit_type;
116 }
117
118
119 static char *
gnc_strtok_r(char * s,const char * delim,char ** save_ptr)120 gnc_strtok_r (char *s, const char *delim, char **save_ptr)
121 {
122 char *token;
123
124 if (s == NULL)
125 s = *save_ptr;
126
127 /* Scan leading delimiters. */
128 s += strspn (s, delim);
129 if (!s || *s == '\0')
130 return NULL;
131
132 /* Find the end of the token. */
133 token = s;
134 s = strpbrk (token, delim);
135 if (s == NULL)
136 /* This token finishes the string. */
137 *save_ptr = strchr (token, '\0');
138 else
139 {
140 /* Terminate the token and make *SAVE_PTR point past it. */
141 *s = '\0';
142 *save_ptr = s + 1;
143 }
144 return token;
145 }
146
147 static void
gnc_date_edit_popdown(GNCDateEdit * gde)148 gnc_date_edit_popdown(GNCDateEdit *gde)
149 {
150 GdkSeat *seat;
151 GdkDevice *pointer;
152 GdkWindow *window;
153
154 g_return_if_fail (GNC_IS_DATE_EDIT (gde));
155
156 ENTER("gde %p", gde);
157
158 window = gtk_widget_get_window (GTK_WIDGET(gde));
159
160 seat = gdk_display_get_default_seat (gdk_window_get_display (window));
161 pointer = gdk_seat_get_pointer (seat);
162
163 gtk_grab_remove (gde->cal_popup);
164 gtk_widget_hide (gde->cal_popup);
165
166 if (pointer)
167 gdk_seat_ungrab (seat);
168
169 gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (gde->date_button),
170 FALSE);
171
172 LEAVE(" ");
173 }
174
175 static void
day_selected(GtkCalendar * calendar,GNCDateEdit * gde)176 day_selected (GtkCalendar *calendar, GNCDateEdit *gde)
177 {
178 time64 t;
179 guint year, month, day;
180 gde->in_selected_handler = TRUE;
181 gtk_calendar_get_date (calendar, &year, &month, &day);
182 /* GtkCalendar returns a 0-based month */
183 t = gnc_dmy2time64 (day, month + 1, year);
184 gnc_date_edit_set_time (gde, t);
185 gde->in_selected_handler = FALSE;
186 }
187
188 static void
day_selected_double_click(GtkCalendar * calendar,GNCDateEdit * gde)189 day_selected_double_click (GtkCalendar *calendar, GNCDateEdit *gde)
190 {
191 gnc_date_edit_popdown (gde);
192 }
193
194 static gint
delete_popup(GtkWidget * widget,gpointer data)195 delete_popup (GtkWidget *widget, gpointer data)
196 {
197 GNCDateEdit *gde;
198
199 gde = data;
200 gnc_date_edit_popdown (gde);
201
202 return TRUE;
203 }
204
205 static gint
key_press_popup(GtkWidget * widget,GdkEventKey * event,gpointer data)206 key_press_popup (GtkWidget *widget, GdkEventKey *event, gpointer data)
207 {
208 GNCDateEdit *gde = data;
209
210 if (event->keyval != GDK_KEY_Return &&
211 event->keyval != GDK_KEY_KP_Enter &&
212 event->keyval != GDK_KEY_Escape)
213 return date_accel_key_press(gde->date_entry, event, data);
214
215 gde = data;
216 g_signal_stop_emission_by_name (G_OBJECT (widget), "key-press-event");
217 gnc_date_edit_popdown (gde);
218
219 return TRUE;
220 }
221
222 static void
position_popup(GNCDateEdit * gde)223 position_popup (GNCDateEdit *gde)
224 {
225 gint x, y;
226 gint bwidth, bheight;
227 GtkRequisition req;
228 GtkAllocation alloc;
229
230 gtk_widget_get_preferred_size (gde->cal_popup, &req, NULL);
231
232 gdk_window_get_origin (gtk_widget_get_window (gde->date_button), &x, &y);
233
234 gtk_widget_get_allocation (gde->date_button, &alloc);
235 x += alloc.x;
236 y += alloc.y;
237 bwidth = alloc.width;
238 bheight = alloc.height;
239
240 x += bwidth - req.width;
241 y += bheight;
242
243 if (x < 0)
244 x = 0;
245
246 if (y < 0)
247 y = 0;
248
249 gtk_window_move (GTK_WINDOW (gde->cal_popup), x, y);
250 }
251
252 /* Pulled from gtkcombobox.c */
253 static gboolean
popup_grab_on_window(GdkWindow * window,GdkDevice * keyboard,GdkDevice * pointer,guint32 activate_time)254 popup_grab_on_window (GdkWindow *window,
255 GdkDevice *keyboard,
256 GdkDevice *pointer,
257 guint32 activate_time)
258 {
259 GdkDisplay *display = gdk_window_get_display (window);
260 GdkSeat *seat = gdk_display_get_default_seat (display);
261 GdkEvent *event = gtk_get_current_event ();
262
263 if (keyboard && gdk_seat_grab (seat, window, GDK_SEAT_CAPABILITY_KEYBOARD, TRUE, NULL,
264 event, NULL, NULL) != GDK_GRAB_SUCCESS)
265 return FALSE;
266
267 if (pointer && gdk_seat_grab (seat, window, GDK_SEAT_CAPABILITY_POINTER, TRUE, NULL,
268 event, NULL, NULL) != GDK_GRAB_SUCCESS)
269 {
270 if (keyboard)
271 gdk_seat_ungrab (seat);
272
273 return FALSE;
274 }
275 return TRUE;
276 }
277
278
279 static void
gnc_date_edit_popup(GNCDateEdit * gde)280 gnc_date_edit_popup (GNCDateEdit *gde)
281 {
282 GtkWidget *toplevel;
283 struct tm mtm;
284 gboolean date_was_valid;
285 GdkDevice *device, *keyboard, *pointer;
286
287 g_return_if_fail (GNC_IS_DATE_EDIT (gde));
288
289 ENTER("gde %p", gde);
290
291 device = gtk_get_current_event_device ();
292
293 /* This code is pretty much just copied from gtk_date_edit_get_date */
294 date_was_valid = qof_scan_date (gtk_entry_get_text (GTK_ENTRY (gde->date_entry)),
295 &mtm.tm_mday, &mtm.tm_mon, &mtm.tm_year);
296 if (!date_was_valid)
297 {
298 /* No valid date. Hacky workaround: Instead of crashing we randomly choose today's date. */
299 gnc_tm_get_today_start(&mtm);
300 }
301
302 mtm.tm_mon--;
303
304 /* Hope the user does not actually mean years early in the A.D. days...
305 * This date widget will obviously not work for a history program :-)
306 */
307 if (mtm.tm_year >= 1900)
308 mtm.tm_year -= 1900;
309
310 gnc_tm_set_day_start(&mtm);
311
312 /* Set the calendar. */
313 gtk_calendar_select_day (GTK_CALENDAR (gde->calendar), 1);
314 gtk_calendar_select_month (GTK_CALENDAR (gde->calendar), mtm.tm_mon,
315 1900 + mtm.tm_year);
316 gtk_calendar_select_day (GTK_CALENDAR (gde->calendar), mtm.tm_mday);
317
318 /* Make sure we'll get notified of clicks outside the popup
319 * window so we can properly pop down if that happens. */
320 toplevel = gtk_widget_get_toplevel (GTK_WIDGET (gde));
321 if (GTK_IS_WINDOW (toplevel))
322 {
323 gtk_window_group_add_window (
324 gtk_window_get_group (GTK_WINDOW (toplevel)),
325 GTK_WINDOW (gde->cal_popup));
326 gtk_window_set_transient_for (GTK_WINDOW (gde->cal_popup),
327 GTK_WINDOW (toplevel));
328 }
329
330 position_popup (gde);
331
332 gtk_widget_show (gde->cal_popup);
333
334 gtk_widget_grab_focus (gde->cal_popup);
335 gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (gde->date_button),
336 TRUE);
337
338 if (gdk_device_get_source (device) == GDK_SOURCE_KEYBOARD)
339 {
340 keyboard = device;
341 pointer = gdk_device_get_associated_device (device);
342 }
343 else
344 {
345 pointer = device;
346 keyboard = gdk_device_get_associated_device (device);
347 }
348
349 if (!gtk_widget_has_focus (gde->calendar))
350 gtk_widget_grab_focus (gde->calendar);
351
352 if (!popup_grab_on_window (gtk_widget_get_window ((GTK_WIDGET(gde->cal_popup))),
353 keyboard, pointer, GDK_CURRENT_TIME))
354 {
355 gtk_widget_hide (gde->cal_popup);
356 LEAVE("Failed to grab window");
357 return;
358 }
359
360 gtk_grab_add (gde->cal_popup);
361
362 LEAVE(" ");
363 }
364
365 /* This function is a customized gtk_combo_box_list_button_pressed(). */
366 static gboolean
gnc_date_edit_button_pressed(GtkWidget * widget,GdkEventButton * event,gpointer data)367 gnc_date_edit_button_pressed (GtkWidget *widget,
368 GdkEventButton *event,
369 gpointer data)
370 {
371 GNCDateEdit *gde = GNC_DATE_EDIT(data);
372 GtkWidget *ewidget = gtk_get_event_widget ((GdkEvent *)event);
373
374 ENTER("widget=%p, ewidget=%p, event=%p, gde=%p", widget, ewidget, event, gde);
375
376 /* While popped up, ignore presses outside the popup window. */
377 if (ewidget == gde->cal_popup)
378 {
379 LEAVE("Press on calendar. Ignoring.");
380 return TRUE;
381 }
382
383 /* If the press isn't to make the popup appear, just propagate it. */
384 if (ewidget != gde->date_button ||
385 gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (gde->date_button)))
386 {
387 LEAVE("Press, not on popup button, or while popup is raised.");
388 return FALSE;
389 }
390
391 if (!gtk_widget_has_focus (gde->date_button))
392 gtk_widget_grab_focus (gde->date_button);
393
394 gde->popup_in_progress = TRUE;
395
396 gnc_date_edit_popup (gde);
397
398 gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (gde->date_button), TRUE);
399
400 LEAVE("Popup in progress.");
401 return TRUE;
402 }
403
404 static gboolean
gnc_date_edit_button_released(GtkWidget * widget,GdkEventButton * event,gpointer data)405 gnc_date_edit_button_released (GtkWidget *widget,
406 GdkEventButton *event,
407 gpointer data)
408 {
409 GNCDateEdit *gde = GNC_DATE_EDIT(data);
410 GtkWidget *ewidget = gtk_get_event_widget ((GdkEvent *)event);
411 gboolean popup_in_progress = FALSE;
412
413 ENTER("widget=%p, ewidget=%p, event=%p, gde=%p", widget, ewidget, event, gde);
414
415 if (gde->popup_in_progress)
416 {
417 popup_in_progress = TRUE;
418 gde->popup_in_progress = FALSE;
419 }
420
421 /* Propagate releases on the calendar. */
422 if (ewidget == gde->calendar)
423 {
424 LEAVE("Button release on calendar.");
425 return FALSE;
426 }
427
428 if (ewidget == gde->date_button)
429 {
430 /* Pop down if we're up and it isn't due to the preceding press. */
431 if (!popup_in_progress &&
432 gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (gde->date_button)))
433 {
434 gnc_date_edit_popdown (gde);
435 LEAVE("Release on button, not in progress. Popped down.");
436 return TRUE;
437 }
438
439 LEAVE("Button release on button. Allowing.");
440 return FALSE;
441 }
442
443 /* Pop down on a release anywhere else. */
444 gnc_date_edit_popdown (gde);
445 LEAVE("Release not on button or calendar. Popping down.");
446 return TRUE;
447 }
448
449 static void
gnc_date_edit_button_toggled(GtkWidget * widget,GNCDateEdit * gde)450 gnc_date_edit_button_toggled (GtkWidget *widget, GNCDateEdit *gde)
451 {
452 ENTER("widget %p, gde %p", widget, gde);
453
454 if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (widget)))
455 {
456 if (!gde->popup_in_progress)
457 gnc_date_edit_popup (gde);
458 }
459 else
460 gnc_date_edit_popdown (gde);
461
462 LEAVE(" ");
463 }
464
465 static void
set_time(GtkWidget * widget,GNCDateEdit * gde)466 set_time (GtkWidget *widget, GNCDateEdit *gde)
467 {
468 gchar *text;
469 GtkTreeModel *model;
470 GtkTreeIter iter;
471
472 model = gtk_combo_box_get_model(GTK_COMBO_BOX(gde->time_combo));
473 gtk_combo_box_get_active_iter (GTK_COMBO_BOX(gde->time_combo), &iter);
474 gtk_tree_model_get( model, &iter, 0, &text, -1 );
475
476 gtk_entry_set_text (GTK_ENTRY (gde->time_entry), text);
477 if(text)
478 g_free(text);
479 g_signal_emit (G_OBJECT (gde), date_edit_signals [TIME_CHANGED], 0);
480 }
481
482 static void
fill_time_combo(GtkWidget * widget,GNCDateEdit * gde)483 fill_time_combo (GtkWidget *widget, GNCDateEdit *gde)
484 {
485 GtkTreeModel *model;
486 GtkTreeIter hour_iter, min_iter;
487 struct tm *tm_returned;
488 struct tm mtm;
489 time64 current_time;
490 int i, j;
491
492 if (gde->lower_hour > gde->upper_hour)
493 return;
494
495 model = gtk_combo_box_get_model (GTK_COMBO_BOX(gde->time_combo));
496
497 gnc_time (¤t_time);
498 tm_returned = gnc_localtime_r (¤t_time, &mtm);
499 g_return_if_fail(tm_returned != NULL);
500
501 for (i = gde->lower_hour; i <= gde->upper_hour; i++)
502 {
503 char buffer [40];
504 mtm.tm_hour = i;
505 mtm.tm_min = 0;
506
507 if (gde->flags & GNC_DATE_EDIT_24_HR)
508 qof_strftime (buffer, sizeof (buffer), "%H:00", &mtm);
509 else
510 qof_strftime (buffer, sizeof (buffer), "%I:00 %p ", &mtm);
511
512 gtk_tree_store_append (GTK_TREE_STORE(model), &hour_iter, NULL);
513 gtk_tree_store_set (GTK_TREE_STORE(model), &hour_iter, 0, buffer, -1);
514
515 for (j = 0; j < 60; j += 15)
516 {
517 mtm.tm_min = j;
518
519 if (gde->flags & GNC_DATE_EDIT_24_HR)
520 qof_strftime (buffer, sizeof (buffer), "%H:%M", &mtm);
521 else
522 qof_strftime (buffer, sizeof (buffer), "%I:%M %p", &mtm);
523
524 gtk_tree_store_append(GTK_TREE_STORE(model), &min_iter, &hour_iter );
525 gtk_tree_store_set (GTK_TREE_STORE(model), &min_iter, 0, buffer, -1);
526 }
527 }
528 }
529
530 static void
gnc_date_edit_set_time_internal(GNCDateEdit * gde,time64 the_time)531 gnc_date_edit_set_time_internal (GNCDateEdit *gde, time64 the_time)
532 {
533 char buffer [MAX_DATE_LENGTH + 1];
534 struct tm *mytm = gnc_localtime (&the_time);
535
536 g_return_if_fail(mytm != NULL);
537
538 /* Update the date text. */
539 qof_print_date_dmy_buff(buffer, MAX_DATE_LENGTH,
540 mytm->tm_mday,
541 mytm->tm_mon + 1,
542 1900 + mytm->tm_year);
543 gtk_entry_set_text(GTK_ENTRY(gde->date_entry), buffer);
544
545 /* Update the calendar. */
546 if (!gde->in_selected_handler)
547 {
548 gtk_calendar_select_day(GTK_CALENDAR (gde->calendar), 1);
549 gtk_calendar_select_month(GTK_CALENDAR (gde->calendar),
550 mytm->tm_mon, 1900 + mytm->tm_year);
551 gtk_calendar_select_day(GTK_CALENDAR (gde->calendar), mytm->tm_mday);
552 }
553
554 /* Set the time of day. */
555 if (gde->flags & GNC_DATE_EDIT_24_HR)
556 qof_strftime (buffer, sizeof (buffer), "%H:%M", mytm);
557 else
558 qof_strftime (buffer, sizeof (buffer), "%I:%M %p", mytm);
559 gtk_entry_set_text(GTK_ENTRY(gde->time_entry), buffer);
560
561 gnc_tm_free (mytm);
562
563 g_signal_emit (gde, date_edit_signals [DATE_CHANGED], 0);
564 g_signal_emit (gde, date_edit_signals [TIME_CHANGED], 0);
565 }
566
567
568 /** Retrieve a property specific to this GncPeriodSelect object. This is
569 * nothing more than a dispatch function for routines that can be
570 * called directly. It has the nice feature of allowing a single
571 * function call to retrieve multiple properties.
572 *
573 * @internal
574 */
575 static void
gnc_date_edit_get_property(GObject * object,guint prop_id,GValue * value,GParamSpec * pspec)576 gnc_date_edit_get_property (GObject *object,
577 guint prop_id,
578 GValue *value,
579 GParamSpec *pspec)
580 {
581 GNCDateEdit *date_edit = GNC_DATE_EDIT (object);
582
583 switch (prop_id)
584 {
585 case PROP_TIME:
586 g_value_set_int64 (value, gnc_date_edit_get_date (date_edit));
587 break;
588 default:
589 G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
590 break;
591 }
592 }
593
594
595 /** Set a property specific to this GncDateEdit object. This is
596 * nothing more than a dispatch function for routines that can be
597 * called directly. It has the nice feature of allowing a new widget
598 * to be created with a varargs list specifying the properties,
599 * instead of having to explicitly call each property function.
600 *
601 * @internal
602 */
603 static void
gnc_date_edit_set_property(GObject * object,guint prop_id,const GValue * value,GParamSpec * pspec)604 gnc_date_edit_set_property (GObject *object,
605 guint prop_id,
606 const GValue *value,
607 GParamSpec *pspec)
608 {
609 GNCDateEdit *date_edit = GNC_DATE_EDIT (object);
610
611 switch (prop_id)
612 {
613 case PROP_TIME:
614 gnc_date_edit_set_time_internal (date_edit, g_value_get_int64(value));
615 break;
616 default:
617 G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
618 break;
619 }
620 }
621
622 static void
gnc_date_edit_class_init(GNCDateEditClass * klass)623 gnc_date_edit_class_init (GNCDateEditClass *klass)
624 {
625 GtkContainerClass *container_class = (GtkContainerClass *) klass;
626 GObjectClass *object_class = (GObjectClass *) klass;
627
628 container_class->forall = gnc_date_edit_forall;
629 object_class->set_property = gnc_date_edit_set_property;
630 object_class->get_property = gnc_date_edit_get_property;
631 object_class->dispose = gnc_date_edit_dispose;
632 object_class->finalize = gnc_date_edit_finalize;
633
634 parent_class = g_type_class_ref(GTK_TYPE_BOX);
635
636 date_edit_signals [TIME_CHANGED] =
637 g_signal_new ("time_changed",
638 G_TYPE_FROM_CLASS (object_class),
639 G_SIGNAL_RUN_FIRST,
640 G_STRUCT_OFFSET (GNCDateEditClass, time_changed),
641 NULL, NULL,
642 g_cclosure_marshal_VOID__VOID,
643 G_TYPE_NONE, 0);
644
645 date_edit_signals [DATE_CHANGED] =
646 g_signal_new ("date_changed",
647 G_TYPE_FROM_CLASS (object_class),
648 G_SIGNAL_RUN_FIRST,
649 G_STRUCT_OFFSET (GNCDateEditClass, date_changed),
650 NULL, NULL,
651 g_cclosure_marshal_VOID__VOID,
652 G_TYPE_NONE, 0);
653
654 g_object_class_install_property(object_class,
655 PROP_TIME,
656 g_param_spec_int64("time",
657 "Date/time (seconds)",
658 "Date/time represented in seconds since Januari 31st, 1970",
659 G_MININT64,
660 G_MAXINT64,
661 0,
662 G_PARAM_READWRITE));
663
664 klass->date_changed = NULL;
665 klass->time_changed = NULL;
666 }
667
668 static void
gnc_date_edit_init(GNCDateEdit * gde)669 gnc_date_edit_init (GNCDateEdit *gde)
670 {
671 gtk_orientable_set_orientation (GTK_ORIENTABLE(gde), GTK_ORIENTATION_HORIZONTAL);
672
673 // Set the name for this widget so it can be easily manipulated with css
674 gtk_widget_set_name (GTK_WIDGET(gde), "gnc-id-date-edit");
675
676 gde->disposed = FALSE;
677 gde->popup_in_progress = FALSE;
678 gde->lower_hour = 7;
679 gde->upper_hour = 19;
680 gde->flags = GNC_DATE_EDIT_SHOW_TIME;
681 gde->in_selected_handler = FALSE;
682 }
683
684 static void
gnc_date_edit_finalize(GObject * object)685 gnc_date_edit_finalize (GObject *object)
686 {
687
688 g_return_if_fail (object != NULL);
689 g_return_if_fail (GNC_IS_DATE_EDIT (object));
690
691 if (G_OBJECT_CLASS (parent_class)->finalize)
692 (* G_OBJECT_CLASS (parent_class)->finalize) (object);
693 }
694
695 static void
gnc_date_edit_dispose(GObject * object)696 gnc_date_edit_dispose (GObject *object)
697 {
698 GNCDateEdit *gde;
699
700 g_return_if_fail (object != NULL);
701 g_return_if_fail (GNC_IS_DATE_EDIT (object));
702
703 gde = GNC_DATE_EDIT (object);
704
705 if (gde->disposed)
706 return;
707
708 gde->disposed = TRUE;
709
710 /* Only explicitly destroy the toplevel elements */
711
712 gtk_widget_destroy (GTK_WIDGET(gde->date_entry));
713 gde->date_entry = NULL;
714
715 gtk_widget_destroy (GTK_WIDGET(gde->date_button));
716 gde->date_button = NULL;
717
718 gtk_widget_destroy (GTK_WIDGET(gde->time_entry));
719 gde->time_entry = NULL;
720
721 gtk_widget_destroy (GTK_WIDGET(gde->time_combo));
722 gde->time_combo = NULL;
723
724 if (G_OBJECT_CLASS (parent_class)->dispose)
725 (* G_OBJECT_CLASS (parent_class)->dispose) (object);
726 }
727
728 static void
gnc_date_edit_forall(GtkContainer * container,gboolean include_internals,GtkCallback callback,gpointer callback_data)729 gnc_date_edit_forall (GtkContainer *container, gboolean include_internals,
730 GtkCallback callback, gpointer callback_data)
731 {
732 g_return_if_fail (container != NULL);
733 g_return_if_fail (GNC_IS_DATE_EDIT (container));
734 g_return_if_fail (callback != NULL);
735
736 /* Let GtkBox handle things only if the internal widgets need
737 * to be poked. */
738 if (!include_internals)
739 return;
740
741 if (!GTK_CONTAINER_CLASS (parent_class)->forall)
742 return;
743
744 GTK_CONTAINER_CLASS (parent_class)->forall (container,
745 include_internals,
746 callback,
747 callback_data);
748 }
749
750 /**
751 * gnc_date_edit_set_time:
752 * @gde: the GNCDateEdit widget
753 * @the_time: The time and date that should be set on the widget
754 *
755 * Changes the displayed date and time in the GNCDateEdit widget
756 * to be the one represented by @the_time.
757 */
758 void
gnc_date_edit_set_time(GNCDateEdit * gde,time64 the_time)759 gnc_date_edit_set_time (GNCDateEdit *gde, time64 the_time)
760 {
761 g_return_if_fail (gde != NULL);
762 g_return_if_fail (GNC_IS_DATE_EDIT (gde));
763
764 /* If the_time is invalid, use the last valid time
765 * seen (or as a last resort, the current date). */
766 gde->initial_time = the_time;
767
768 g_object_set (G_OBJECT (gde), "time", the_time, NULL);
769 }
770
771 void
gnc_date_edit_set_gdate(GNCDateEdit * gde,const GDate * date)772 gnc_date_edit_set_gdate (GNCDateEdit *gde, const GDate *date)
773 {
774 struct tm mytm;
775 time64 t;
776
777 g_return_if_fail(gde && GNC_IS_DATE_EDIT(gde) &&
778 date && g_date_valid(date));
779 g_date_to_struct_tm(date, &mytm);
780 t = gnc_mktime(&mytm);
781 gnc_date_edit_set_time(gde, t);
782 }
783
784 /**
785 * gnc_date_edit_set_popup_range:
786 * @gde: The GNCDateEdit widget
787 * @low_hour: low boundary for the time-range display popup.
788 * @up_hour: upper boundary for the time-range display popup.
789 *
790 * Sets the range of times that will be provide by the time popup
791 * selectors.
792 */
793 void
gnc_date_edit_set_popup_range(GNCDateEdit * gde,int low_hour,int up_hour)794 gnc_date_edit_set_popup_range (GNCDateEdit *gde, int low_hour, int up_hour)
795 {
796 g_return_if_fail (gde != NULL);
797 g_return_if_fail (GNC_IS_DATE_EDIT (gde));
798
799 gde->lower_hour = low_hour;
800 gde->upper_hour = up_hour;
801
802 fill_time_combo(NULL, gde);
803 }
804
805 /* This code should be kept in sync with src/register/datecell.c */
806 static int
date_accel_key_press(GtkWidget * widget,GdkEventKey * event,gpointer data)807 date_accel_key_press(GtkWidget *widget, GdkEventKey *event, gpointer data)
808 {
809 GNCDateEdit *gde = data;
810 const char *string;
811 struct tm tm;
812
813 string = gtk_entry_get_text (GTK_ENTRY (widget));
814
815 tm = gnc_date_edit_get_date_internal (gde);
816
817 if (!gnc_handle_date_accelerator (event, &tm, string))
818 return FALSE;
819
820 gnc_date_edit_set_time (gde, gnc_mktime (&tm));
821
822 g_signal_emit (G_OBJECT (gde), date_edit_signals [TIME_CHANGED], 0);
823 return TRUE;
824 }
825
826 static gint
key_press_entry(GtkWidget * widget,GdkEventKey * event,gpointer data)827 key_press_entry (GtkWidget *widget, GdkEventKey *event, gpointer data)
828 {
829 if (!date_accel_key_press(widget, event, data))
830 return FALSE;
831
832 g_signal_stop_emission_by_name (widget, "key-press-event");
833 return TRUE;
834 }
835
836 static int
date_focus_out_event(GtkWidget * widget,GdkEventKey * event,gpointer data)837 date_focus_out_event(GtkWidget *widget, GdkEventKey *event, gpointer data)
838 {
839 GNCDateEdit *gde = data;
840 struct tm tm;
841
842 /* Get the date entered and attempt to use it. */
843 tm = gnc_date_edit_get_date_internal (gde);
844 gnc_date_edit_set_time (gde, gnc_mktime (&tm));
845
846 /* Get the date again in case it was invalid the first time. */
847 tm = gnc_date_edit_get_date_internal (gde);
848
849 g_signal_emit (gde, date_edit_signals [DATE_CHANGED], 0);
850 g_signal_emit (gde, date_edit_signals [TIME_CHANGED], 0);
851
852 return FALSE;
853 }
854
855 static void
create_children(GNCDateEdit * gde)856 create_children (GNCDateEdit *gde)
857 {
858 GtkWidget *frame;
859 GtkWidget *hbox;
860 GtkWidget *arrow;
861 GtkTreeStore *store;
862 GtkCellRenderer *cell;
863
864 /* Create the text entry area. */
865 gde->date_entry = gtk_entry_new ();
866 gtk_entry_set_width_chars (GTK_ENTRY (gde->date_entry), 11);
867 gtk_box_pack_start (GTK_BOX (gde), gde->date_entry, TRUE, TRUE, 0);
868 gtk_widget_show (GTK_WIDGET(gde->date_entry));
869 g_signal_connect (G_OBJECT (gde->date_entry), "key-press-event",
870 G_CALLBACK (key_press_entry), gde);
871 g_signal_connect (G_OBJECT (gde->date_entry), "focus-out-event",
872 G_CALLBACK (date_focus_out_event), gde);
873
874 /* Create the popup button. */
875 gde->date_button = gtk_toggle_button_new ();
876 g_signal_connect (gde->date_button, "button-press-event",
877 G_CALLBACK(gnc_date_edit_button_pressed), gde);
878 g_signal_connect (G_OBJECT (gde->date_button), "toggled",
879 G_CALLBACK (gnc_date_edit_button_toggled), gde);
880 gtk_box_pack_start (GTK_BOX (gde), gde->date_button, FALSE, FALSE, 0);
881
882 hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 3);
883 gtk_box_set_homogeneous (GTK_BOX (hbox), FALSE);
884 gtk_container_add (GTK_CONTAINER (gde->date_button), hbox);
885 gtk_widget_show (GTK_WIDGET(hbox));
886
887 /* Calendar label, only shown if the date editor has a time field */
888 gde->cal_label = gtk_label_new (_("Calendar"));
889 gnc_label_set_alignment (gde->cal_label, 0.0, 0.5);
890 gtk_box_pack_start (GTK_BOX (hbox), gde->cal_label, TRUE, TRUE, 0);
891 if (gde->flags & GNC_DATE_EDIT_SHOW_TIME)
892 gtk_widget_show (GTK_WIDGET(gde->cal_label));
893
894 /* Graphic for the popup button. */
895 arrow = gtk_image_new_from_icon_name ("pan-down-symbolic", GTK_ICON_SIZE_BUTTON);
896
897 gtk_box_pack_start (GTK_BOX (hbox), arrow, TRUE, FALSE, 0);
898 gtk_widget_show (GTK_WIDGET(arrow));
899
900 gtk_widget_show (GTK_WIDGET(gde->date_button));
901
902 /* Time entry controls. */
903 gde->time_entry = gtk_entry_new ();
904 gtk_entry_set_max_length (GTK_ENTRY(gde->time_entry), 12);
905 gtk_widget_set_size_request (GTK_WIDGET(gde->time_entry), 88, -1);
906 gtk_box_pack_start (GTK_BOX (gde), gde->time_entry, TRUE, TRUE, 0);
907
908 store = gtk_tree_store_new(1, G_TYPE_STRING);
909 gde->time_combo = GTK_WIDGET(gtk_combo_box_new_with_model(GTK_TREE_MODEL(store)));
910 g_object_unref(store);
911 /* Create cell renderer. */
912 cell = gtk_cell_renderer_text_new();
913 /* Pack it to the combo box. */
914 gtk_cell_layout_pack_start( GTK_CELL_LAYOUT( gde->time_combo ), cell, TRUE );
915 /* Connect renderer to data source */
916 gtk_cell_layout_set_attributes( GTK_CELL_LAYOUT( gde->time_combo ), cell, "text", 0, NULL );
917
918 g_signal_connect (G_OBJECT (gde->time_combo), "changed",
919 G_CALLBACK (set_time), gde);
920
921 gtk_box_pack_start (GTK_BOX (gde), gde->time_combo, FALSE, FALSE, 0);
922
923 /* We do not create the popup menu with the hour range until we are
924 * realized, so that it uses the values that the user might supply in a
925 * future call to gnc_date_edit_set_popup_range
926 */
927 g_signal_connect (G_OBJECT (gde), "realize",
928 G_CALLBACK (fill_time_combo), gde);
929
930 if (gde->flags & GNC_DATE_EDIT_SHOW_TIME)
931 {
932 gtk_widget_show (GTK_WIDGET(gde->time_entry));
933 gtk_widget_show (GTK_WIDGET(gde->time_combo));
934 }
935
936 gde->cal_popup = gtk_window_new (GTK_WINDOW_POPUP);
937 gtk_widget_set_name (gde->cal_popup, "gnc-date-edit-popup-window");
938
939 gtk_window_set_type_hint (GTK_WINDOW (gde->cal_popup),
940 GDK_WINDOW_TYPE_HINT_COMBO);
941
942 gtk_widget_set_events (GTK_WIDGET(gde->cal_popup),
943 gtk_widget_get_events (GTK_WIDGET(gde->cal_popup)) |
944 GDK_KEY_PRESS_MASK);
945
946 g_signal_connect (gde->cal_popup, "delete-event",
947 G_CALLBACK(delete_popup), gde);
948 g_signal_connect (gde->cal_popup, "key-press-event",
949 G_CALLBACK(key_press_popup), gde);
950 g_signal_connect (gde->cal_popup, "button-press-event",
951 G_CALLBACK(gnc_date_edit_button_pressed), gde);
952 g_signal_connect (gde->cal_popup, "button-release-event",
953 G_CALLBACK(gnc_date_edit_button_released), gde);
954 gtk_window_set_resizable (GTK_WINDOW (gde->cal_popup), FALSE);
955 gtk_window_set_screen (GTK_WINDOW (gde->cal_popup),
956 gtk_widget_get_screen (GTK_WIDGET (gde)));
957
958 frame = gtk_frame_new (NULL);
959 gtk_frame_set_shadow_type (GTK_FRAME (frame), GTK_SHADOW_NONE);
960 gtk_container_add (GTK_CONTAINER (gde->cal_popup), frame);
961 gtk_widget_show (GTK_WIDGET(frame));
962
963 gde->calendar = gtk_calendar_new ();
964 gtk_calendar_set_display_options
965 (GTK_CALENDAR (gde->calendar),
966 (GTK_CALENDAR_SHOW_DAY_NAMES
967 | GTK_CALENDAR_SHOW_HEADING));
968 g_signal_connect (gde->calendar, "button-release-event",
969 G_CALLBACK(gnc_date_edit_button_released), gde);
970 g_signal_connect (G_OBJECT (gde->calendar), "day-selected",
971 G_CALLBACK (day_selected), gde);
972 g_signal_connect (G_OBJECT (gde->calendar),
973 "day-selected-double-click",
974 G_CALLBACK (day_selected_double_click), gde);
975 gtk_container_add (GTK_CONTAINER (frame), gde->calendar);
976 gtk_widget_show (GTK_WIDGET(gde->calendar));
977 }
978
979 /**
980 * gnc_date_edit_new:
981 * @the_time: date and time to be displayed on the widget
982 * @show_time: whether time should be displayed
983 * @use_24_format: whether 24-hour format is desired for the time display.
984 *
985 * Creates a new GNCDateEdit widget which can be used to provide
986 * an easy to use way for entering dates and times.
987 *
988 * Returns a GNCDateEdit widget.
989 */
990 GtkWidget *
gnc_date_edit_new(time64 the_time,int show_time,int use_24_format)991 gnc_date_edit_new (time64 the_time, int show_time, int use_24_format)
992 {
993 return gnc_date_edit_new_flags
994 (the_time,
995 ((show_time ? GNC_DATE_EDIT_SHOW_TIME : 0)
996 | (use_24_format ? GNC_DATE_EDIT_24_HR : 0)));
997 }
998
999 /*
1000 * Create a new GncDateEdit widget from a glade file. The widget
1001 * generated is set to today's date, and will not show a time as part
1002 * of the date. This function does not use any of the arguments
1003 * passed by glade.
1004 */
1005 GtkWidget *
gnc_date_edit_new_glade(gchar * widget_name,gchar * string1,gchar * string2,gint int1,gint int2)1006 gnc_date_edit_new_glade (gchar *widget_name,
1007 gchar *string1, gchar *string2,
1008 gint int1, gint int2)
1009 {
1010 GtkWidget *widget;
1011
1012 /* None of the standard glade arguments are used. */
1013 widget = gnc_date_edit_new(time(NULL), FALSE, FALSE);
1014 gtk_widget_show(widget);
1015 return widget;
1016 }
1017
1018
1019 /**
1020 * gnc_date_edit_new_flags:
1021 * @the_time: The initial time for the date editor.
1022 * @flags: A bitmask of GNCDateEditFlags values.
1023 *
1024 * Creates a new GNCDateEdit widget with the specified flags.
1025 *
1026 * Return value: the newly-created date editor widget.
1027 **/
1028 GtkWidget *
gnc_date_edit_new_flags(time64 the_time,GNCDateEditFlags flags)1029 gnc_date_edit_new_flags (time64 the_time, GNCDateEditFlags flags)
1030 {
1031 GNCDateEdit *gde;
1032
1033 gde = g_object_new (GNC_TYPE_DATE_EDIT, NULL, NULL);
1034
1035 gde->flags = flags;
1036 gde->initial_time = -1;
1037 create_children (gde);
1038 gnc_date_edit_set_time (gde, the_time);
1039
1040 return GTK_WIDGET (gde);
1041 }
1042
1043 static struct tm
gnc_date_edit_get_date_internal(GNCDateEdit * gde)1044 gnc_date_edit_get_date_internal (GNCDateEdit *gde)
1045 {
1046 struct tm tm = {0};
1047 char *str;
1048 gchar *flags = NULL;
1049 gboolean date_was_valid;
1050
1051 /* Assert, because we're just hosed if it's NULL */
1052 g_assert(gde != NULL);
1053 g_assert(GNC_IS_DATE_EDIT(gde));
1054
1055 date_was_valid = qof_scan_date (gtk_entry_get_text (GTK_ENTRY (gde->date_entry)),
1056 &tm.tm_mday, &tm.tm_mon, &tm.tm_year);
1057
1058 if (!date_was_valid)
1059 {
1060 /* Hm... no valid date. What should we do not? As a hacky workaround we
1061 revert to today's date. Alternatively we can return some value that
1062 signals that we don't get a valid date, but all callers of this
1063 function will have to check this. Alas, I'm too lazy to do this here. */
1064 gnc_tm_get_today_start(&tm);
1065 }
1066
1067 tm.tm_mon--;
1068
1069 tm.tm_year -= 1900;
1070
1071 if (gde->flags & GNC_DATE_EDIT_SHOW_TIME)
1072 {
1073 char *tokp = NULL;
1074 gchar *temp;
1075
1076 str = g_strdup (gtk_entry_get_text
1077 (GTK_ENTRY (gde->time_entry)));
1078 temp = gnc_strtok_r (str, ": ", &tokp);
1079 if (temp)
1080 {
1081 tm.tm_hour = atoi (temp);
1082 temp = gnc_strtok_r (NULL, ": ", &tokp);
1083 if (temp)
1084 {
1085 if (isdigit (*temp))
1086 {
1087 tm.tm_min = atoi (temp);
1088 flags = gnc_strtok_r (NULL, ": ",
1089 &tokp);
1090 if (flags && isdigit (*flags))
1091 {
1092 tm.tm_sec = atoi (flags);
1093 flags = gnc_strtok_r (NULL,
1094 ": ",
1095 &tokp);
1096 }
1097 }
1098 else
1099 flags = temp;
1100 }
1101 }
1102
1103 if (flags && (strcasecmp (flags, "PM") == 0))
1104 {
1105 if (tm.tm_hour < 12)
1106 tm.tm_hour += 12;
1107 }
1108 g_free (str);
1109 }
1110 else
1111 {
1112 gnc_tm_set_day_start(&tm);
1113 }
1114
1115 tm.tm_isdst = -1;
1116
1117 return tm;
1118 }
1119
1120 /**
1121 * gnc_date_edit_get_date:
1122 * @gde: The GNCDateEdit widget
1123 *
1124 * Returns the time entered in the GNCDateEdit widget
1125 */
1126 time64
gnc_date_edit_get_date(GNCDateEdit * gde)1127 gnc_date_edit_get_date (GNCDateEdit *gde)
1128 {
1129 struct tm tm;
1130
1131 g_return_val_if_fail (gde != NULL, 0);
1132 g_return_val_if_fail (GNC_IS_DATE_EDIT (gde), 0);
1133
1134 tm = gnc_date_edit_get_date_internal (gde);
1135
1136 return gnc_mktime (&tm);
1137 }
1138
1139 void
gnc_date_edit_get_gdate(GNCDateEdit * gde,GDate * date)1140 gnc_date_edit_get_gdate (GNCDateEdit *gde, GDate *date)
1141 {
1142 time64 t;
1143
1144 g_return_if_fail (gde && date);
1145 g_return_if_fail (GNC_IS_DATE_EDIT (gde));
1146
1147 t = gnc_date_edit_get_date(gde);
1148 g_date_clear (date, 1);
1149 gnc_gdate_set_time64 (date, t);
1150 }
1151
1152 /**
1153 * gnc_date_edit_get_date_end:
1154 * @gde: The GNCDateEdit widget
1155 *
1156 * Returns the date entered in the GNCDateEdit widget,
1157 * but with the time adjusted to the end of the day.
1158 */
1159 time64
gnc_date_edit_get_date_end(GNCDateEdit * gde)1160 gnc_date_edit_get_date_end (GNCDateEdit *gde)
1161 {
1162 struct tm tm;
1163
1164 g_return_val_if_fail (gde != NULL, 0);
1165 g_return_val_if_fail (GNC_IS_DATE_EDIT (gde), 0);
1166
1167 tm = gnc_date_edit_get_date_internal (gde);
1168 gnc_tm_set_day_end(&tm);
1169
1170 return gnc_mktime (&tm);
1171 }
1172
1173 /**
1174 * gnc_date_edit_set_flags:
1175 * @gde: The date editor widget whose flags should be changed.
1176 * @flags: The new bitmask of GNCDateEditFlags values.
1177 *
1178 * Changes the display flags on an existing date editor widget.
1179 **/
1180 void
gnc_date_edit_set_flags(GNCDateEdit * gde,GNCDateEditFlags flags)1181 gnc_date_edit_set_flags (GNCDateEdit *gde, GNCDateEditFlags flags)
1182 {
1183 GNCDateEditFlags old_flags;
1184
1185 g_return_if_fail (gde != NULL);
1186 g_return_if_fail (GNC_IS_DATE_EDIT (gde));
1187
1188 old_flags = gde->flags;
1189 gde->flags = flags;
1190
1191 if ((flags & GNC_DATE_EDIT_SHOW_TIME) !=
1192 (old_flags & GNC_DATE_EDIT_SHOW_TIME))
1193 {
1194 if (flags & GNC_DATE_EDIT_SHOW_TIME)
1195 {
1196 gtk_widget_show (gde->cal_label);
1197 gtk_widget_show (gde->time_entry);
1198 gtk_widget_show (gde->time_combo);
1199 }
1200 else
1201 {
1202 gtk_widget_hide (gde->cal_label);
1203 gtk_widget_hide (gde->time_entry);
1204 gtk_widget_hide (gde->time_combo);
1205 }
1206 }
1207
1208 if ((flags & GNC_DATE_EDIT_24_HR) != (old_flags & GNC_DATE_EDIT_24_HR))
1209 /* This will destroy the old menu properly */
1210 fill_time_combo (NULL, gde);
1211
1212 }
1213
1214 /**
1215 * gnc_date_edit_get_flags:
1216 * @gde: The date editor whose flags should be queried.
1217 *
1218 * Queries the display flags on a date editor widget.
1219 *
1220 * Return value: The current display flags for the given date editor widget.
1221 **/
1222 int
gnc_date_edit_get_flags(GNCDateEdit * gde)1223 gnc_date_edit_get_flags (GNCDateEdit *gde)
1224 {
1225 g_return_val_if_fail (gde != NULL, 0);
1226 g_return_val_if_fail (GNC_IS_DATE_EDIT (gde), 0);
1227
1228 return gde->flags;
1229 }
1230
1231 /**
1232 * gnc_date_set_activates_default:
1233 * @gde: The date editor to modify
1234 * @state: The new state for this widget.
1235 *
1236 * Extracts the editable field from a GNCDateEdit widget, and sets it
1237 * up so that pressing the Enter key in this field as the same as
1238 * clicking the button that has the default.
1239 **/
1240 void
gnc_date_activates_default(GNCDateEdit * gde,gboolean state)1241 gnc_date_activates_default (GNCDateEdit *gde, gboolean state)
1242 {
1243 if (!gde)
1244 return;
1245
1246 gtk_entry_set_activates_default(GTK_ENTRY(gde->date_entry), state);
1247 }
1248
1249 /**
1250 * gnc_date_grab_focus:
1251 * @gde: The date editor to modify
1252 * @state: The new state for this widget.
1253 *
1254 * Sets the focus to the Editable field.
1255 **/
1256 void
gnc_date_grab_focus(GNCDateEdit * gde)1257 gnc_date_grab_focus (GNCDateEdit *gde)
1258 {
1259 if (!gde)
1260 return;
1261
1262 gtk_widget_grab_focus (gde->date_entry);
1263 }
1264 /** Sets the editable field from a GNCDateEdit widget as the target
1265 * for the specified label's access key.
1266 *
1267 * @param gde The date editor to set as the target.
1268 *
1269 * @param label The label whose access key should set focus to this
1270 * widget. */
1271 void
gnc_date_make_mnemonic_target(GNCDateEdit * gde,GtkWidget * label)1272 gnc_date_make_mnemonic_target (GNCDateEdit *gde, GtkWidget *label)
1273 {
1274 if (!gde)
1275 return;
1276
1277 gtk_label_set_mnemonic_widget (GTK_LABEL(label), gde->date_entry);
1278 }
1279
1280
1281