1 /********************************************************************
2  * gnc-date-format.c -- Date formator widget                        *
3  *                       (GnuCash)                                  *
4  * Copyright (C) 2003 Derek Atkins  <derek@ihtfp.com>               *
5  *                                                                  *
6  * This program is free software; you can redistribute it and/or    *
7  * modify it under the terms of the GNU General Public License as   *
8  * published by the Free Software Foundation; either version 2 of   *
9  * the License, or (at your option) any later version.              *
10  *                                                                  *
11  * This program is distributed in the hope that it will be useful,  *
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of   *
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the    *
14  * GNU General Public License for more details.                     *
15  *                                                                  *
16  * You should have received a copy of the GNU General Public License*
17  * along with this program; if not, contact:                        *
18  *                                                                  *
19  * Free Software Foundation           Voice:  +1-617-542-5942       *
20  * 51 Franklin Street, Fifth Floor    Fax:    +1-617-542-2652       *
21  * Boston, MA  02110-1301,  USA       gnu@gnu.org                   *
22  ********************************************************************/
23 
24 /*
25   @NOTATION@
26 */
27 
28 /*
29  * Date format widget
30  *
31  * Authors: Derek Atkins <derek@ihtfp.com>
32  */
33 
34 #include <config.h>
35 
36 #include <gtk/gtk.h>
37 #include <string.h>
38 #include <stdio.h>
39 
40 #include "gnc-date-format.h"
41 #include "dialog-utils.h"
42 #include "gnc-engine.h"
43 
44 /* Perhaps it's better just to use MAX_DATE_LENGTH defined in gnc-date.h */
45 #define MAX_DATE_LEN 80
46 
47 /* This static indicates the debugging module that this .o belongs to.  */
48 G_GNUC_UNUSED static QofLogModule log_module = GNC_MOD_GUI;
49 
50 enum
51 {
52     FORMAT_CHANGED,
53     LAST_SIGNAL
54 };
55 
56 typedef struct _GNCDateFormatPrivate GNCDateFormatPrivate;
57 
58 struct _GNCDateFormatPrivate
59 {
60     GtkWidget*	format_combobox;
61 
62     GtkWidget*  label;
63     GtkWidget*  table;
64 
65     GtkWidget*	months_label;
66     GtkWidget*	months_number;
67     GtkWidget*	months_abbrev;
68     GtkWidget*	months_name;
69 
70     GtkWidget*	years_label;
71     GtkWidget*	years_button;
72 
73     GtkWidget*	custom_label;
74     GtkWidget*	custom_entry;
75 
76     GtkWidget*	sample_label;
77 };
78 
79 #define GNC_DATE_FORMAT_GET_PRIVATE(o)  \
80    ((GNCDateFormatPrivate*)g_type_instance_get_private((GTypeInstance*)o, GNC_TYPE_DATE_FORMAT))
81 
82 static guint date_format_signals [LAST_SIGNAL] = { 0 };
83 
84 static void gnc_date_format_init         (GNCDateFormat      *gdf);
85 static void gnc_date_format_class_init   (GNCDateFormatClass *klass);
86 static void gnc_date_format_finalize     (GObject            *object);
87 static void gnc_date_format_compute_format(GNCDateFormat *gdf);
88 
89 void gnc_ui_date_format_changed_cb(GtkWidget *unused, gpointer user_data);
90 
91 static GtkBoxClass *parent_class;
92 
G_DEFINE_TYPE_WITH_PRIVATE(GNCDateFormat,gnc_date_format,GTK_TYPE_BOX)93 G_DEFINE_TYPE_WITH_PRIVATE(GNCDateFormat, gnc_date_format, GTK_TYPE_BOX)
94 
95 static void
96 gnc_date_format_class_init (GNCDateFormatClass *klass)
97 {
98     GObjectClass   *gobject_class = (GObjectClass *) klass;
99 
100     parent_class = g_type_class_peek_parent(klass);
101 
102     gobject_class->finalize = gnc_date_format_finalize;
103 
104     date_format_signals [FORMAT_CHANGED] =
105         g_signal_new ("format_changed",
106                       G_OBJECT_CLASS_TYPE (gobject_class),
107                       G_SIGNAL_RUN_FIRST,
108                       G_STRUCT_OFFSET (GNCDateFormatClass, format_changed),
109                       NULL,
110                       NULL,
111                       g_cclosure_marshal_VOID__VOID,
112                       G_TYPE_NONE,
113                       0);
114 }
115 
116 
117 static void
gnc_date_format_init(GNCDateFormat * gdf)118 gnc_date_format_init (GNCDateFormat *gdf)
119 {
120     GNCDateFormatPrivate *priv;
121     GtkBuilder *builder;
122     GtkWidget *dialog;
123 
124     g_return_if_fail(gdf);
125     g_return_if_fail(GNC_IS_DATE_FORMAT(gdf));
126 
127     gtk_orientable_set_orientation (GTK_ORIENTABLE(gdf), GTK_ORIENTATION_HORIZONTAL);
128 
129     // Set the name for this widget so it can be easily manipulated with css
130     gtk_widget_set_name (GTK_WIDGET(gdf), "gnc-id-date-format");
131 
132     /* Open up the Glade and set the signals */
133     builder = gtk_builder_new();
134     gnc_builder_add_from_file (builder, "gnc-date-format.glade", "format-liststore");
135     gnc_builder_add_from_file (builder, "gnc-date-format.glade", "gnc_date_format_window");
136 
137     gtk_builder_connect_signals_full (builder, gnc_builder_connect_full_func, gdf);
138 
139     /* pull in all the child widgets */
140     priv =  GNC_DATE_FORMAT_GET_PRIVATE(gdf);
141     priv->label = GTK_WIDGET(gtk_builder_get_object (builder, "widget_label"));
142     priv->format_combobox = GTK_WIDGET(gtk_builder_get_object (builder, "format_combobox"));
143 
144     priv->months_label = GTK_WIDGET(gtk_builder_get_object (builder, "months_label"));
145     priv->months_number = GTK_WIDGET(gtk_builder_get_object (builder, "month_number_button"));
146     priv->months_abbrev = GTK_WIDGET(gtk_builder_get_object (builder, "month_abbrev_button"));
147     priv->months_name = GTK_WIDGET(gtk_builder_get_object (builder, "month_name_button"));
148 
149     priv->years_label = GTK_WIDGET(gtk_builder_get_object (builder, "years_label"));
150     priv->years_button = GTK_WIDGET(gtk_builder_get_object (builder, "years_button"));
151 
152     priv->custom_label = GTK_WIDGET(gtk_builder_get_object (builder, "format_label"));
153     priv->custom_entry = GTK_WIDGET(gtk_builder_get_object (builder, "format_entry"));
154 
155     priv->sample_label = GTK_WIDGET(gtk_builder_get_object (builder, "sample_label"));
156 
157     /* Set initial format to gnucash default */
158     gnc_date_format_set_format(gdf, QOF_DATE_FORMAT_UNSET);
159 
160     /* pull in the dialog and table widgets and play the reconnect game */
161     dialog = GTK_WIDGET(gtk_builder_get_object (builder, "gnc_date_format_window"));
162 
163     priv->table = GTK_WIDGET(gtk_builder_get_object (builder, "date_format_table"));
164     g_object_ref (G_OBJECT(priv->table));
165     gtk_container_remove (GTK_CONTAINER(dialog), priv->table);
166     gtk_container_add (GTK_CONTAINER(gdf), priv->table);
167     g_object_unref (G_OBJECT(priv->table));
168 
169     g_object_unref(G_OBJECT(builder));
170 
171     /* Destroy the now empty window */
172     gtk_widget_destroy(dialog);
173 }
174 
175 
176 static void
gnc_date_format_finalize(GObject * object)177 gnc_date_format_finalize (GObject *object)
178 {
179     g_return_if_fail(object != NULL);
180     g_return_if_fail(GNC_IS_DATE_FORMAT(object));
181 
182     if (G_OBJECT_CLASS(parent_class)->finalize)
183         (* G_OBJECT_CLASS(parent_class)->finalize) (object);
184 }
185 
186 
187 /**
188  * gnc_date_format_new:
189  *
190  * Creates a new GNCDateFormat widget which can be used to provide
191  * an easy to use way for entering date formats and seeing the sample.
192  *
193  * Returns a GNCDateFormat widget.
194  */
195 GtkWidget *
gnc_date_format_new(void)196 gnc_date_format_new (void)
197 {
198     return gnc_date_format_new_with_label (NULL);
199 }
200 
201 
202 GtkWidget *
gnc_date_format_new_without_label(void)203 gnc_date_format_new_without_label (void)
204 {
205     GtkWidget *widget = gnc_date_format_new_with_label(NULL);
206     GNCDateFormat *gdf = GNC_DATE_FORMAT(widget);
207     GNCDateFormatPrivate *priv = GNC_DATE_FORMAT_GET_PRIVATE(gdf);
208 
209     // remove the first column which has the label
210     gtk_grid_remove_column (GTK_GRID(priv->table), 0);
211     priv->label = NULL;
212     return widget;
213 }
214 
215 
216 /**
217  * gnc_date_format_new_with_label:
218  * @label: the label to use to define the widget.
219  *
220  * Creates a new GNCDateFormat widget which can be used to provide
221  * an easy to use way for entering date formats and seeing the sample.
222  *
223  * Returns a GNCDateFormat widget.
224  */
225 GtkWidget *
gnc_date_format_new_with_label(const char * label)226 gnc_date_format_new_with_label (const char *label)
227 {
228     GNCDateFormat *gdf;
229     GNCDateFormatPrivate *priv;
230 
231     gdf = g_object_new(GNC_TYPE_DATE_FORMAT, NULL);
232     priv = GNC_DATE_FORMAT_GET_PRIVATE(gdf);
233 
234     if (label)
235         gtk_label_set_text(GTK_LABEL(priv->label), label);
236 
237     gnc_date_format_compute_format(gdf);
238     return GTK_WIDGET(gdf);
239 }
240 
241 
242 void
gnc_date_format_set_format(GNCDateFormat * gdf,QofDateFormat format)243 gnc_date_format_set_format (GNCDateFormat *gdf, QofDateFormat format)
244 {
245     GNCDateFormatPrivate *priv;
246 
247     g_return_if_fail(gdf);
248     g_return_if_fail(GNC_IS_DATE_FORMAT(gdf));
249 
250     priv = GNC_DATE_FORMAT_GET_PRIVATE(gdf);
251     gtk_combo_box_set_active(GTK_COMBO_BOX(priv->format_combobox), format);
252     gnc_date_format_compute_format(gdf);
253 }
254 
255 
256 QofDateFormat
gnc_date_format_get_format(GNCDateFormat * gdf)257 gnc_date_format_get_format (GNCDateFormat *gdf)
258 {
259     GNCDateFormatPrivate *priv;
260 
261     g_return_val_if_fail (gdf, QOF_DATE_FORMAT_LOCALE);
262     g_return_val_if_fail (GNC_IS_DATE_FORMAT(gdf), QOF_DATE_FORMAT_LOCALE);
263 
264     priv = GNC_DATE_FORMAT_GET_PRIVATE(gdf);
265     return gtk_combo_box_get_active(GTK_COMBO_BOX(priv->format_combobox));
266 }
267 
268 
269 void
gnc_date_format_set_months(GNCDateFormat * gdf,GNCDateMonthFormat months)270 gnc_date_format_set_months (GNCDateFormat *gdf, GNCDateMonthFormat months)
271 {
272     GNCDateFormatPrivate *priv;
273     GtkWidget *button = NULL;
274 
275     g_return_if_fail(gdf);
276     g_return_if_fail(GNC_IS_DATE_FORMAT(gdf));
277 
278     priv = GNC_DATE_FORMAT_GET_PRIVATE(gdf);
279     switch (months)
280     {
281     case GNCDATE_MONTH_NUMBER:
282         button = priv->months_number;
283         break;
284     case GNCDATE_MONTH_ABBREV:
285         button = priv->months_abbrev;
286         break;
287     case GNCDATE_MONTH_NAME:
288         button = priv->months_name;
289         break;
290     default:
291         break;
292     }
293 
294     g_return_if_fail(button);
295 
296     gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(button), TRUE);
297     gnc_date_format_compute_format(gdf);
298 }
299 
300 
301 GNCDateMonthFormat
gnc_date_format_get_months(GNCDateFormat * gdf)302 gnc_date_format_get_months (GNCDateFormat *gdf)
303 {
304     GNCDateFormatPrivate *priv;
305 
306     g_return_val_if_fail(gdf, GNCDATE_MONTH_NUMBER);
307     g_return_val_if_fail(GNC_IS_DATE_FORMAT(gdf), GNCDATE_MONTH_NUMBER);
308 
309     priv = GNC_DATE_FORMAT_GET_PRIVATE(gdf);
310     if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(priv->months_number)))
311         return GNCDATE_MONTH_NUMBER;
312     if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(priv->months_abbrev)))
313         return GNCDATE_MONTH_ABBREV;
314     if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(priv->months_name)))
315         return GNCDATE_MONTH_NAME;
316 
317     /* We should never reach this point */
318     g_assert(FALSE);
319     return GNCDATE_MONTH_NUMBER;
320 }
321 
322 
323 void
gnc_date_format_set_years(GNCDateFormat * gdf,gboolean include_century)324 gnc_date_format_set_years (GNCDateFormat *gdf, gboolean include_century)
325 {
326     GNCDateFormatPrivate *priv;
327 
328     g_return_if_fail(gdf);
329     g_return_if_fail(GNC_IS_DATE_FORMAT(gdf));
330 
331     priv = GNC_DATE_FORMAT_GET_PRIVATE(gdf);
332     gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(priv->years_button),
333                                  include_century);
334     gnc_date_format_compute_format(gdf);
335 }
336 
337 
338 gboolean
gnc_date_format_get_years(GNCDateFormat * gdf)339 gnc_date_format_get_years (GNCDateFormat *gdf)
340 {
341     GNCDateFormatPrivate *priv;
342 
343     g_return_val_if_fail(gdf, FALSE);
344     g_return_val_if_fail(GNC_IS_DATE_FORMAT(gdf), FALSE);
345 
346     priv = GNC_DATE_FORMAT_GET_PRIVATE(gdf);
347     return gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(priv->years_button));
348 }
349 
350 
351 void
gnc_date_format_set_custom(GNCDateFormat * gdf,const char * format)352 gnc_date_format_set_custom (GNCDateFormat *gdf, const char *format)
353 {
354     GNCDateFormatPrivate *priv;
355 
356     g_return_if_fail(gdf);
357     g_return_if_fail(GNC_IS_DATE_FORMAT(gdf));
358 
359     if (format == NULL || *format == '\0')
360         return;
361 
362     priv = GNC_DATE_FORMAT_GET_PRIVATE(gdf);
363     gtk_entry_set_text(GTK_ENTRY(priv->custom_entry), format);
364     gnc_date_format_compute_format(gdf);
365 }
366 
367 
368 const char *
gnc_date_format_get_custom(GNCDateFormat * gdf)369 gnc_date_format_get_custom (GNCDateFormat *gdf)
370 {
371     GNCDateFormatPrivate *priv;
372 
373     g_return_val_if_fail(gdf, "");
374     g_return_val_if_fail(GNC_IS_DATE_FORMAT(gdf), "");
375 
376     priv = GNC_DATE_FORMAT_GET_PRIVATE(gdf);
377     return gtk_entry_get_text(GTK_ENTRY(priv->custom_entry));
378 }
379 
380 
381 void
gnc_ui_date_format_changed_cb(GtkWidget * unused,gpointer user_data)382 gnc_ui_date_format_changed_cb(GtkWidget *unused, gpointer user_data)
383 {
384     GNCDateFormat * gdf = user_data;
385 
386     gnc_date_format_compute_format(gdf);
387 }
388 
389 
390 static void
gnc_date_format_enable_month(GNCDateFormat * gdf,gboolean sensitive)391 gnc_date_format_enable_month (GNCDateFormat *gdf, gboolean sensitive)
392 {
393     GNCDateFormatPrivate *priv;
394 
395     priv = GNC_DATE_FORMAT_GET_PRIVATE(gdf);
396     gtk_widget_set_sensitive(priv->months_label, sensitive);
397     gtk_widget_set_sensitive(priv->months_number, sensitive);
398     gtk_widget_set_sensitive(priv->months_abbrev, sensitive);
399     gtk_widget_set_sensitive(priv->months_name, sensitive);
400 }
401 
402 
403 static void
gnc_date_format_enable_year(GNCDateFormat * gdf,gboolean sensitive)404 gnc_date_format_enable_year (GNCDateFormat *gdf, gboolean sensitive)
405 {
406     GNCDateFormatPrivate *priv;
407 
408     priv = GNC_DATE_FORMAT_GET_PRIVATE(gdf);
409     gtk_widget_set_sensitive(priv->years_label, sensitive);
410     gtk_widget_set_sensitive(priv->years_button, sensitive);
411 }
412 
413 
414 static void
gnc_date_format_enable_format(GNCDateFormat * gdf,gboolean sensitive)415 gnc_date_format_enable_format (GNCDateFormat *gdf, gboolean sensitive)
416 {
417     GNCDateFormatPrivate *priv;
418 
419     priv = GNC_DATE_FORMAT_GET_PRIVATE(gdf);
420     gtk_widget_set_sensitive(priv->custom_label, sensitive);
421     gtk_widget_set_sensitive(priv->custom_entry, sensitive);
422 }
423 
424 
425 void
gnc_date_format_refresh(GNCDateFormat * gdf)426 gnc_date_format_refresh (GNCDateFormat *gdf)
427 {
428     GNCDateFormatPrivate *priv;
429     int sel_option;
430     gboolean enable_year, enable_month, enable_custom, check_modifiers;
431     static gchar *format, *c;
432     gchar date_string[MAX_DATE_LEN];
433     time64 secs_now;
434     struct tm today;
435 
436     g_return_if_fail(gdf);
437     g_return_if_fail(GNC_IS_DATE_FORMAT(gdf));
438 
439     priv = GNC_DATE_FORMAT_GET_PRIVATE(gdf);
440     sel_option =
441         gtk_combo_box_get_active(GTK_COMBO_BOX(priv->format_combobox));
442 
443     switch (sel_option)
444     {
445     case QOF_DATE_FORMAT_CUSTOM:
446         format = g_strdup(gtk_entry_get_text(GTK_ENTRY(priv->custom_entry)));
447         enable_year = enable_month = check_modifiers = FALSE;
448         enable_custom = TRUE;
449         break;
450 
451     case QOF_DATE_FORMAT_UNSET:
452     case QOF_DATE_FORMAT_LOCALE:
453     case QOF_DATE_FORMAT_UTC:
454         format = g_strdup(qof_date_format_get_string(sel_option));
455         enable_year = enable_month = check_modifiers = enable_custom = FALSE;
456         break;
457 
458     case QOF_DATE_FORMAT_ISO:
459         gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(priv->months_number), TRUE);
460         enable_year = check_modifiers = TRUE;
461         enable_month = enable_custom = FALSE;
462         break;
463 
464     default:
465         enable_year = enable_month = check_modifiers = TRUE;
466         enable_custom = FALSE;
467         break;
468     }
469 
470     /* Tweak widget sensitivities, as appropriate. */
471     gnc_date_format_enable_year(gdf, enable_year);
472     gnc_date_format_enable_month(gdf, enable_month);
473     gnc_date_format_enable_format(gdf, enable_custom);
474 
475     /* Update the format string based upon the user's preferences */
476     if (check_modifiers)
477     {
478         if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(priv->months_number)))
479         {
480             format = g_strdup(qof_date_format_get_string(sel_option));
481         }
482         else
483         {
484             format = g_strdup(qof_date_text_format_get_string(sel_option));
485             if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(priv->months_name)))
486             {
487                 c = strchr(format, 'b');
488                 if (c)
489                     *c = 'B';
490             }
491         }
492         if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(priv->years_button)))
493         {
494             c = strchr(format, 'y');
495             if (c)
496                 *c = 'Y';
497         }
498     }
499 
500     /*
501      * Give feedback on the format string so users can see how it works
502      * without having to read the strftime man page. Prevent recursive
503      * signals.
504      */
505     g_signal_handlers_block_matched(priv->custom_entry, G_SIGNAL_MATCH_DATA,
506                                     0, 0, NULL, NULL, gdf);
507     gtk_entry_set_text(GTK_ENTRY(priv->custom_entry), format);
508     g_signal_handlers_unblock_matched(priv->custom_entry, G_SIGNAL_MATCH_DATA,
509                                       0, 0, NULL, NULL, gdf);
510 
511     /* Visual feedback on what the date will look like. */
512     secs_now = gnc_time (NULL);
513     gnc_localtime_r (&secs_now, &today);
514     qof_strftime(date_string, MAX_DATE_LEN, format, &today);
515     gtk_label_set_text(GTK_LABEL(priv->sample_label), date_string);
516     g_free(format);
517 }
518 
519 
520 static void
gnc_date_format_compute_format(GNCDateFormat * gdf)521 gnc_date_format_compute_format(GNCDateFormat *gdf)
522 {
523     g_return_if_fail(gdf);
524     g_return_if_fail(GNC_IS_DATE_FORMAT(gdf));
525 
526     /* refresh the widget */
527     gnc_date_format_refresh(gdf);
528 
529     /* Emit a signal that we've changed */
530     g_signal_emit(G_OBJECT(gdf), date_format_signals[FORMAT_CHANGED], 0);
531 }
532