GnuCash  5.6-150-g038405b370+
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 
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 static void gnc_date_edit_dispose (GObject *object);
68 static void gnc_date_edit_finalize (GObject *object);
69 static void gnc_date_edit_forall (GtkContainer *container,
70  gboolean include_internals,
71  GtkCallback callback,
72  gpointer callbabck_data);
73 static struct tm gnc_date_edit_get_date_internal (GNCDateEdit *gde);
74 static int date_accel_key_press(GtkWidget *widget,
75  GdkEventKey *event,
76  gpointer data);
77 
78 G_DEFINE_TYPE (GNCDateEdit, gnc_date_edit, GTK_TYPE_BOX)
79 
80 static char *
81 gnc_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 
108 static void
109 gnc_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 
136 static void
137 day_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 
149 static void
150 day_selected_double_click (GtkCalendar *calendar, GNCDateEdit *gde)
151 {
152  gnc_date_edit_popdown (gde);
153 }
154 
155 static gint
156 delete_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 
166 static gint
167 key_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 
183 static void
184 position_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 */
214 static gboolean
215 popup_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 
240 static void
241 gnc_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(). */
327 static gboolean
328 gnc_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 
365 static gboolean
366 gnc_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 
410 static void
411 gnc_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 
426 static void
427 set_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 
443 static void
444 fill_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  gnc_time (&current_time);
459  tm_returned = gnc_localtime_r (&current_time, &mtm);
460  g_return_if_fail(tm_returned != NULL);
461 
462  for (i = gde->lower_hour; i <= gde->upper_hour; i++)
463  {
464  char buffer [40];
465  mtm.tm_hour = i;
466  mtm.tm_min = 0;
467 
468  if (gde->flags & GNC_DATE_EDIT_24_HR)
469  qof_strftime (buffer, sizeof (buffer), "%H:00", &mtm);
470  else
471  qof_strftime (buffer, sizeof (buffer), "%I:00 %p ", &mtm);
472 
473  gtk_tree_store_append (GTK_TREE_STORE(model), &hour_iter, NULL);
474  gtk_tree_store_set (GTK_TREE_STORE(model), &hour_iter, 0, buffer, -1);
475 
476  for (j = 0; j < 60; j += 15)
477  {
478  mtm.tm_min = j;
479 
480  if (gde->flags & GNC_DATE_EDIT_24_HR)
481  qof_strftime (buffer, sizeof (buffer), "%H:%M", &mtm);
482  else
483  qof_strftime (buffer, sizeof (buffer), "%I:%M %p", &mtm);
484 
485  gtk_tree_store_append(GTK_TREE_STORE(model), &min_iter, &hour_iter );
486  gtk_tree_store_set (GTK_TREE_STORE(model), &min_iter, 0, buffer, -1);
487  }
488  }
489 }
490 
491 static void
492 gnc_date_edit_set_time_internal (GNCDateEdit *gde, time64 the_time)
493 {
494  char buffer [MAX_DATE_LENGTH + 1];
495  struct tm *mytm = gnc_localtime (&the_time);
496 
497  g_return_if_fail(mytm != NULL);
498 
499  /* Update the date text. */
501  mytm->tm_mday,
502  mytm->tm_mon + 1,
503  1900 + mytm->tm_year);
504  gtk_entry_set_text(GTK_ENTRY(gde->date_entry), buffer);
505 
506  /* Update the calendar. */
507  if (!gde->in_selected_handler)
508  {
509  gtk_calendar_select_day(GTK_CALENDAR (gde->calendar), 1);
510  gtk_calendar_select_month(GTK_CALENDAR (gde->calendar),
511  mytm->tm_mon, 1900 + mytm->tm_year);
512  gtk_calendar_select_day(GTK_CALENDAR (gde->calendar), mytm->tm_mday);
513  }
514 
515  /* Set the time of day. */
516  if (gde->flags & GNC_DATE_EDIT_24_HR)
517  qof_strftime (buffer, sizeof (buffer), "%H:%M", mytm);
518  else
519  qof_strftime (buffer, sizeof (buffer), "%I:%M %p", mytm);
520  gtk_entry_set_text(GTK_ENTRY(gde->time_entry), buffer);
521 
522  gnc_tm_free (mytm);
523 
524  g_signal_emit (gde, date_edit_signals [DATE_CHANGED], 0);
525  g_signal_emit (gde, date_edit_signals [TIME_CHANGED], 0);
526 }
527 
528 
536 static void
537 gnc_date_edit_get_property (GObject *object,
538  guint prop_id,
539  GValue *value,
540  GParamSpec *pspec)
541 {
542  GNCDateEdit *date_edit = GNC_DATE_EDIT (object);
543 
544  switch (prop_id)
545  {
546  case PROP_TIME:
547  g_value_set_int64 (value, gnc_date_edit_get_date (date_edit));
548  break;
549  default:
550  G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
551  break;
552  }
553 }
554 
555 
564 static void
565 gnc_date_edit_set_property (GObject *object,
566  guint prop_id,
567  const GValue *value,
568  GParamSpec *pspec)
569 {
570  GNCDateEdit *date_edit = GNC_DATE_EDIT (object);
571 
572  switch (prop_id)
573  {
574  case PROP_TIME:
575  gnc_date_edit_set_time_internal (date_edit, g_value_get_int64(value));
576  break;
577  default:
578  G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
579  break;
580  }
581 }
582 
583 static void
584 gnc_date_edit_class_init (GNCDateEditClass *klass)
585 {
586  GtkContainerClass *container_class = (GtkContainerClass *) klass;
587  GObjectClass *object_class = (GObjectClass *) klass;
588 
589  container_class->forall = gnc_date_edit_forall;
590  object_class->set_property = gnc_date_edit_set_property;
591  object_class->get_property = gnc_date_edit_get_property;
592  object_class->dispose = gnc_date_edit_dispose;
593  object_class->finalize = gnc_date_edit_finalize;
594 
595  date_edit_signals [TIME_CHANGED] =
596  g_signal_new ("time_changed",
597  G_TYPE_FROM_CLASS (object_class),
598  G_SIGNAL_RUN_FIRST,
599  G_STRUCT_OFFSET (GNCDateEditClass, time_changed),
600  NULL, NULL,
601  g_cclosure_marshal_VOID__VOID,
602  G_TYPE_NONE, 0);
603 
604  date_edit_signals [DATE_CHANGED] =
605  g_signal_new ("date_changed",
606  G_TYPE_FROM_CLASS (object_class),
607  G_SIGNAL_RUN_FIRST,
608  G_STRUCT_OFFSET (GNCDateEditClass, date_changed),
609  NULL, NULL,
610  g_cclosure_marshal_VOID__VOID,
611  G_TYPE_NONE, 0);
612 
613  g_object_class_install_property(object_class,
614  PROP_TIME,
615  g_param_spec_int64("time",
616  "Date/time (seconds)",
617  "Date/time represented in seconds since midnight UTC, 1 January 1970",
618  G_MININT64,
619  G_MAXINT64,
620  0,
621  G_PARAM_READWRITE));
622 
623  klass->date_changed = NULL;
624  klass->time_changed = NULL;
625 }
626 
627 static void
628 gnc_date_edit_init (GNCDateEdit *gde)
629 {
630  gtk_orientable_set_orientation (GTK_ORIENTABLE(gde), GTK_ORIENTATION_HORIZONTAL);
631 
632  // Set the name for this widget so it can be easily manipulated with css
633  gtk_widget_set_name (GTK_WIDGET(gde), "gnc-id-date-edit");
634 
635  gde->disposed = FALSE;
636  gde->popup_in_progress = FALSE;
637  gde->lower_hour = 7;
638  gde->upper_hour = 19;
639  gde->flags = GNC_DATE_EDIT_SHOW_TIME;
640  gde->in_selected_handler = FALSE;
641 }
642 
643 static void
644 gnc_date_edit_finalize (GObject *object)
645 {
646 
647  g_return_if_fail (object != NULL);
648  g_return_if_fail (GNC_IS_DATE_EDIT (object));
649 
650  G_OBJECT_CLASS (gnc_date_edit_parent_class)->finalize (object);
651 }
652 
653 static void
654 gnc_date_edit_dispose (GObject *object)
655 {
656  GNCDateEdit *gde;
657 
658  g_return_if_fail (object != NULL);
659  g_return_if_fail (GNC_IS_DATE_EDIT (object));
660 
661  gde = GNC_DATE_EDIT (object);
662 
663  if (gde->disposed)
664  return;
665 
666  gde->disposed = TRUE;
667 
668  /* Only explicitly destroy the toplevel elements */
669 
670  gtk_widget_destroy (GTK_WIDGET(gde->date_entry));
671  gde->date_entry = NULL;
672 
673  gtk_widget_destroy (GTK_WIDGET(gde->date_button));
674  gde->date_button = NULL;
675 
676  gtk_widget_destroy (GTK_WIDGET(gde->time_entry));
677  gde->time_entry = NULL;
678 
679  gtk_widget_destroy (GTK_WIDGET(gde->time_combo));
680  gde->time_combo = NULL;
681 
682  G_OBJECT_CLASS (gnc_date_edit_parent_class)->dispose (object);
683 }
684 
685 static void
686 gnc_date_edit_forall (GtkContainer *container, gboolean include_internals,
687  GtkCallback callback, gpointer callback_data)
688 {
689  g_return_if_fail (container != NULL);
690  g_return_if_fail (GNC_IS_DATE_EDIT (container));
691  g_return_if_fail (callback != NULL);
692 
693  /* Let GtkBox handle things only if the internal widgets need
694  * to be poked. */
695  if (!include_internals)
696  return;
697 
698  if (!GTK_CONTAINER_CLASS (gnc_date_edit_parent_class)->forall)
699  return;
700 
701  GTK_CONTAINER_CLASS (gnc_date_edit_parent_class)->forall (container,
702  include_internals,
703  callback,
704  callback_data);
705 }
706 
715 void
716 gnc_date_edit_set_time (GNCDateEdit *gde, time64 the_time)
717 {
718  g_return_if_fail (gde != NULL);
719  g_return_if_fail (GNC_IS_DATE_EDIT (gde));
720 
721  /* If the_time is invalid, use the last valid time
722  * seen (or as a last resort, the current date). */
723  gde->initial_time = the_time;
724 
725  g_object_set (G_OBJECT (gde), "time", the_time, NULL);
726 }
727 
728 void
729 gnc_date_edit_set_gdate (GNCDateEdit *gde, const GDate *date)
730 {
731  struct tm mytm;
732  time64 t;
733 
734  g_return_if_fail(gde && GNC_IS_DATE_EDIT(gde) &&
735  date && g_date_valid(date));
736  g_date_to_struct_tm(date, &mytm);
737  t = gnc_mktime(&mytm);
738  gnc_date_edit_set_time(gde, t);
739 }
740 
750 void
751 gnc_date_edit_set_popup_range (GNCDateEdit *gde, int low_hour, int up_hour)
752 {
753  g_return_if_fail (gde != NULL);
754  g_return_if_fail (GNC_IS_DATE_EDIT (gde));
755 
756  gde->lower_hour = low_hour;
757  gde->upper_hour = up_hour;
758 
759  fill_time_combo(NULL, gde);
760 }
761 
762 /* This code should be kept in sync with src/register/datecell.c */
763 static int
764 date_accel_key_press(GtkWidget *widget, GdkEventKey *event, gpointer data)
765 {
766  GNCDateEdit *gde = data;
767  const char *string;
768  struct tm tm;
769 
770  string = gtk_entry_get_text (GTK_ENTRY (widget));
771 
772  tm = gnc_date_edit_get_date_internal (gde);
773 
774  if (!gnc_handle_date_accelerator (event, &tm, string))
775  return FALSE;
776 
777  gnc_date_edit_set_time (gde, gnc_mktime (&tm));
778 
779  g_signal_emit (G_OBJECT (gde), date_edit_signals [TIME_CHANGED], 0);
780  return TRUE;
781 }
782 
783 static gint
784 key_press_entry (GtkWidget *widget, GdkEventKey *event, gpointer data)
785 {
786  if (!date_accel_key_press(widget, event, data))
787  return FALSE;
788 
789  g_signal_stop_emission_by_name (widget, "key-press-event");
790  return TRUE;
791 }
792 
793 static int
794 date_focus_out_event(GtkWidget *widget, GdkEventKey *event, gpointer data)
795 {
796  GNCDateEdit *gde = data;
797  struct tm tm;
798 
799  /* Get the date entered and attempt to use it. */
800  tm = gnc_date_edit_get_date_internal (gde);
801  gnc_date_edit_set_time (gde, gnc_mktime (&tm));
802 
803  g_signal_emit (gde, date_edit_signals [DATE_CHANGED], 0);
804  g_signal_emit (gde, date_edit_signals [TIME_CHANGED], 0);
805 
806  return FALSE;
807 }
808 
809 static void
810 create_children (GNCDateEdit *gde)
811 {
812  GtkWidget *frame;
813  GtkWidget *hbox;
814  GtkWidget *arrow;
815  GtkTreeStore *store;
816  GtkCellRenderer *cell;
817 
818  /* Create the text entry area. */
819  gde->date_entry = gtk_entry_new ();
820  gtk_entry_set_width_chars (GTK_ENTRY (gde->date_entry), 11);
821  gtk_box_pack_start (GTK_BOX (gde), gde->date_entry, TRUE, TRUE, 0);
822  gtk_widget_show (GTK_WIDGET(gde->date_entry));
823  g_signal_connect (G_OBJECT (gde->date_entry), "key-press-event",
824  G_CALLBACK (key_press_entry), gde);
825  g_signal_connect (G_OBJECT (gde->date_entry), "focus-out-event",
826  G_CALLBACK (date_focus_out_event), gde);
827 
828  /* Create the popup button. */
829  gde->date_button = gtk_toggle_button_new ();
830  g_signal_connect (gde->date_button, "button-press-event",
831  G_CALLBACK(gnc_date_edit_button_pressed), gde);
832  g_signal_connect (G_OBJECT (gde->date_button), "toggled",
833  G_CALLBACK (gnc_date_edit_button_toggled), gde);
834  gtk_box_pack_start (GTK_BOX (gde), gde->date_button, FALSE, FALSE, 0);
835 
836  hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 3);
837  gtk_box_set_homogeneous (GTK_BOX (hbox), FALSE);
838  gtk_container_add (GTK_CONTAINER (gde->date_button), hbox);
839  gtk_widget_show (GTK_WIDGET(hbox));
840 
841  /* Calendar label, only shown if the date editor has a time field */
842  gde->cal_label = gtk_label_new (_("Calendar"));
843  gnc_label_set_alignment (gde->cal_label, 0.0, 0.5);
844  gtk_box_pack_start (GTK_BOX (hbox), gde->cal_label, TRUE, TRUE, 0);
845  if (gde->flags & GNC_DATE_EDIT_SHOW_TIME)
846  gtk_widget_show (GTK_WIDGET(gde->cal_label));
847 
848  /* Graphic for the popup button. */
849  arrow = gtk_image_new_from_icon_name ("pan-down-symbolic", GTK_ICON_SIZE_BUTTON);
850 
851  gtk_box_pack_start (GTK_BOX (hbox), arrow, TRUE, FALSE, 0);
852  gtk_widget_show (GTK_WIDGET(arrow));
853 
854  gtk_widget_show (GTK_WIDGET(gde->date_button));
855 
856  /* Time entry controls. */
857  gde->time_entry = gtk_entry_new ();
858  gtk_entry_set_max_length (GTK_ENTRY(gde->time_entry), 12);
859  gtk_widget_set_size_request (GTK_WIDGET(gde->time_entry), 88, -1);
860  gtk_box_pack_start (GTK_BOX (gde), gde->time_entry, TRUE, TRUE, 0);
861 
862  store = gtk_tree_store_new(1, G_TYPE_STRING);
863  gde->time_combo = GTK_WIDGET(gtk_combo_box_new_with_model(GTK_TREE_MODEL(store)));
864  g_object_unref(store);
865  /* Create cell renderer. */
866  cell = gtk_cell_renderer_text_new();
867  /* Pack it to the combo box. */
868  gtk_cell_layout_pack_start( GTK_CELL_LAYOUT( gde->time_combo ), cell, TRUE );
869  /* Connect renderer to data source */
870  gtk_cell_layout_set_attributes( GTK_CELL_LAYOUT( gde->time_combo ), cell, "text", 0, NULL );
871 
872  g_signal_connect (G_OBJECT (gde->time_combo), "changed",
873  G_CALLBACK (set_time), gde);
874 
875  gtk_box_pack_start (GTK_BOX (gde), gde->time_combo, FALSE, FALSE, 0);
876 
877  /* We do not create the popup menu with the hour range until we are
878  * realized, so that it uses the values that the user might supply in a
879  * future call to gnc_date_edit_set_popup_range
880  */
881  g_signal_connect (G_OBJECT (gde), "realize",
882  G_CALLBACK (fill_time_combo), gde);
883 
884  if (gde->flags & GNC_DATE_EDIT_SHOW_TIME)
885  {
886  gtk_widget_show (GTK_WIDGET(gde->time_entry));
887  gtk_widget_show (GTK_WIDGET(gde->time_combo));
888  }
889 
890  gde->cal_popup = gtk_window_new (GTK_WINDOW_POPUP);
891  gtk_widget_set_name (gde->cal_popup, "gnc-date-edit-popup-window");
892 
893  gtk_window_set_type_hint (GTK_WINDOW (gde->cal_popup),
894  GDK_WINDOW_TYPE_HINT_COMBO);
895 
896  gtk_widget_set_events (GTK_WIDGET(gde->cal_popup),
897  gtk_widget_get_events (GTK_WIDGET(gde->cal_popup)) |
898  GDK_KEY_PRESS_MASK);
899 
900  g_signal_connect (gde->cal_popup, "delete-event",
901  G_CALLBACK(delete_popup), gde);
902  g_signal_connect (gde->cal_popup, "key-press-event",
903  G_CALLBACK(key_press_popup), gde);
904  g_signal_connect (gde->cal_popup, "button-press-event",
905  G_CALLBACK(gnc_date_edit_button_pressed), gde);
906  g_signal_connect (gde->cal_popup, "button-release-event",
907  G_CALLBACK(gnc_date_edit_button_released), gde);
908  gtk_window_set_resizable (GTK_WINDOW (gde->cal_popup), FALSE);
909  gtk_window_set_screen (GTK_WINDOW (gde->cal_popup),
910  gtk_widget_get_screen (GTK_WIDGET (gde)));
911 
912  frame = gtk_frame_new (NULL);
913  gtk_frame_set_shadow_type (GTK_FRAME (frame), GTK_SHADOW_NONE);
914  gtk_container_add (GTK_CONTAINER (gde->cal_popup), frame);
915  gtk_widget_show (GTK_WIDGET(frame));
916 
917  gde->calendar = gtk_calendar_new ();
918  gtk_calendar_set_display_options
919  (GTK_CALENDAR (gde->calendar),
920  (GTK_CALENDAR_SHOW_DAY_NAMES
921  | GTK_CALENDAR_SHOW_HEADING));
922  g_signal_connect (gde->calendar, "button-release-event",
923  G_CALLBACK(gnc_date_edit_button_released), gde);
924  g_signal_connect (G_OBJECT (gde->calendar), "day-selected",
925  G_CALLBACK (day_selected), gde);
926  g_signal_connect (G_OBJECT (gde->calendar),
927  "day-selected-double-click",
928  G_CALLBACK (day_selected_double_click), gde);
929  gtk_container_add (GTK_CONTAINER (frame), gde->calendar);
930  gtk_widget_show (GTK_WIDGET(gde->calendar));
931 }
932 
944 GtkWidget *
945 gnc_date_edit_new (time64 the_time, int show_time, int use_24_format)
946 {
947  return gnc_date_edit_new_flags
948  (the_time,
949  ((show_time ? GNC_DATE_EDIT_SHOW_TIME : 0)
950  | (use_24_format ? GNC_DATE_EDIT_24_HR : 0)));
951 }
952 
953 /*
954  * Create a new GncDateEdit widget from a glade file. The widget
955  * generated is set to today's date, and will not show a time as part
956  * of the date. This function does not use any of the arguments
957  * passed by glade.
958  */
959 GtkWidget *
960 gnc_date_edit_new_glade (gchar *widget_name,
961  gchar *string1, gchar *string2,
962  gint int1, gint int2)
963 {
964  GtkWidget *widget;
965 
966  /* None of the standard glade arguments are used. */
967  widget = gnc_date_edit_new(time(NULL), FALSE, FALSE);
968  gtk_widget_show(widget);
969  return widget;
970 }
971 
972 
982 GtkWidget *
983 gnc_date_edit_new_flags (time64 the_time, GNCDateEditFlags flags)
984 {
985  GNCDateEdit *gde;
986 
987  gde = g_object_new (GNC_TYPE_DATE_EDIT, NULL, NULL);
988 
989  gde->flags = flags;
990  gde->initial_time = -1;
991  create_children (gde);
992  gnc_date_edit_set_time (gde, the_time);
993 
994  return GTK_WIDGET (gde);
995 }
996 
997 static struct tm
998 gnc_date_edit_get_date_internal (GNCDateEdit *gde)
999 {
1000  struct tm tm = {0};
1001  char *str;
1002  gchar *flags = NULL;
1003  gboolean date_was_valid;
1004 
1005  /* Assert, because we're just hosed if it's NULL */
1006  g_assert(gde != NULL);
1007  g_assert(GNC_IS_DATE_EDIT(gde));
1008 
1009  date_was_valid = qof_scan_date (gtk_entry_get_text (GTK_ENTRY (gde->date_entry)),
1010  &tm.tm_mday, &tm.tm_mon, &tm.tm_year);
1011 
1012  if (!date_was_valid)
1013  {
1014  /* Hm... no valid date. What should we do now? As a hacky workaround we
1015  revert to today's date. Alternatively we can return some value that
1016  signals that we don't get a valid date, but all callers of this
1017  function will have to check this. Alas, I'm too lazy to do this here. */
1019  }
1020  else
1021  {
1022  tm.tm_mon--;
1023  tm.tm_year -= 1900;
1024  }
1025 
1026  if (gde->flags & GNC_DATE_EDIT_SHOW_TIME)
1027  {
1028  char *tokp = NULL;
1029  gchar *temp;
1030 
1031  str = g_strdup (gtk_entry_get_text
1032  (GTK_ENTRY (gde->time_entry)));
1033  temp = gnc_strtok_r (str, ": ", &tokp);
1034  if (temp)
1035  {
1036  tm.tm_hour = atoi (temp);
1037  temp = gnc_strtok_r (NULL, ": ", &tokp);
1038  if (temp)
1039  {
1040  if (isdigit (*temp))
1041  {
1042  tm.tm_min = atoi (temp);
1043  flags = gnc_strtok_r (NULL, ": ",
1044  &tokp);
1045  if (flags && isdigit (*flags))
1046  {
1047  tm.tm_sec = atoi (flags);
1048  flags = gnc_strtok_r (NULL,
1049  ": ",
1050  &tokp);
1051  }
1052  }
1053  else
1054  flags = temp;
1055  }
1056  }
1057 
1058  if (flags && (strcasecmp (flags, "PM") == 0))
1059  {
1060  if (tm.tm_hour < 12)
1061  tm.tm_hour += 12;
1062  }
1063  g_free (str);
1064  }
1065  else
1066  {
1068  }
1069 
1070  tm.tm_isdst = -1;
1071 
1072  return tm;
1073 }
1074 
1081 time64
1082 gnc_date_edit_get_date (GNCDateEdit *gde)
1083 {
1084  struct tm tm;
1085 
1086  g_return_val_if_fail (gde != NULL, 0);
1087  g_return_val_if_fail (GNC_IS_DATE_EDIT (gde), 0);
1088 
1089  tm = gnc_date_edit_get_date_internal (gde);
1090 
1091  return gnc_mktime (&tm);
1092 }
1093 
1094 void
1095 gnc_date_edit_get_gdate (GNCDateEdit *gde, GDate *date)
1096 {
1097  time64 t;
1098 
1099  g_return_if_fail (gde && date);
1100  g_return_if_fail (GNC_IS_DATE_EDIT (gde));
1101 
1102  t = gnc_date_edit_get_date(gde);
1103  g_date_clear (date, 1);
1104  gnc_gdate_set_time64 (date, t);
1105 }
1106 
1114 time64
1115 gnc_date_edit_get_date_end (GNCDateEdit *gde)
1116 {
1117  struct tm tm;
1118 
1119  g_return_val_if_fail (gde != NULL, 0);
1120  g_return_val_if_fail (GNC_IS_DATE_EDIT (gde), 0);
1121 
1122  tm = gnc_date_edit_get_date_internal (gde);
1123  gnc_tm_set_day_end(&tm);
1124 
1125  return gnc_mktime (&tm);
1126 }
1127 
1135 void
1136 gnc_date_edit_set_flags (GNCDateEdit *gde, GNCDateEditFlags flags)
1137 {
1138  GNCDateEditFlags old_flags;
1139 
1140  g_return_if_fail (gde != NULL);
1141  g_return_if_fail (GNC_IS_DATE_EDIT (gde));
1142 
1143  old_flags = gde->flags;
1144  gde->flags = flags;
1145 
1146  if ((flags & GNC_DATE_EDIT_SHOW_TIME) !=
1147  (old_flags & GNC_DATE_EDIT_SHOW_TIME))
1148  {
1149  if (flags & GNC_DATE_EDIT_SHOW_TIME)
1150  {
1151  gtk_widget_show (gde->cal_label);
1152  gtk_widget_show (gde->time_entry);
1153  gtk_widget_show (gde->time_combo);
1154  }
1155  else
1156  {
1157  gtk_widget_hide (gde->cal_label);
1158  gtk_widget_hide (gde->time_entry);
1159  gtk_widget_hide (gde->time_combo);
1160  }
1161  }
1162 
1163  if ((flags & GNC_DATE_EDIT_24_HR) != (old_flags & GNC_DATE_EDIT_24_HR))
1164  /* This will destroy the old menu properly */
1165  fill_time_combo (NULL, gde);
1166 
1167 }
1168 
1177 int
1178 gnc_date_edit_get_flags (GNCDateEdit *gde)
1179 {
1180  g_return_val_if_fail (gde != NULL, 0);
1181  g_return_val_if_fail (GNC_IS_DATE_EDIT (gde), 0);
1182 
1183  return gde->flags;
1184 }
1185 
1195 void
1196 gnc_date_activates_default (GNCDateEdit *gde, gboolean state)
1197 {
1198  if (!gde)
1199  return;
1200 
1201  gtk_entry_set_activates_default(GTK_ENTRY(gde->date_entry), state);
1202 }
1203 
1211 void
1212 gnc_date_grab_focus (GNCDateEdit *gde)
1213 {
1214  if (!gde)
1215  return;
1216 
1217  gtk_widget_grab_focus (gde->date_entry);
1218 }
1226 void
1227 gnc_date_make_mnemonic_target (GNCDateEdit *gde, GtkWidget *label)
1228 {
1229  if (!gde)
1230  return;
1231 
1232  gtk_label_set_mnemonic_widget (GTK_LABEL(label), gde->date_entry);
1233 }
1234 
1235 
size_t qof_print_date_dmy_buff(gchar *buff, size_t buflen, int day, int month, int year)
qof_print_date_dmy_buff Convert a date as day / month / year integers into a localized string represe...
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 ...
Definition: gnc-date.cpp:1056
Date and Time handling routines.
time64 gnc_dmy2time64(gint day, gint month, gint year)
Convert a day, month, and year to a time64, returning the first second of the day.
#define ENTER(format, args...)
Print a function entry debugging message.
Definition: qoflog.h:272
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:114
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...
Definition: gnc-date.cpp:1335
void gnc_tm_free(struct tm *time)
free a struct tm* created with gnc_localtime() or gnc_gmtime()
Definition: gnc-date.cpp:96
time64 gnc_mktime(struct tm *time)
calculate seconds from the epoch given a time struct
Definition: gnc-date.cpp:218
#define MAX_DATE_LENGTH
The maximum length of a string created by the date printers.
Definition: gnc-date.h:108
struct tm * gnc_localtime(const time64 *secs)
fill out a time struct from a 64-bit time value.
Definition: gnc-date.cpp:102
All type declarations for the whole Gnucash engine.
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:918
void gnc_gdate_set_time64(GDate *gd, time64 time)
Set a GDate to a time64.
Definition: gnc-date.cpp:1244
#define LEAVE(format, args...)
Print a function exit debugging message.
Definition: qoflog.h:282
time64 gnc_time(time64 *tbuf)
get the current time
Definition: gnc-date.cpp:261
gint64 time64
Most systems that are currently maintained, including Microsoft Windows, BSD-derived Unixes and Linux...
Definition: gnc-date.h:87
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...
Definition: gnc-date.cpp:1270