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