1 /* HomeBank -- Free, easy, personal accounting for everyone.
2 * Copyright (C) 1995-2019 Maxime DOYEN
4 * This file is part of HomeBank.
6 * HomeBank is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2 of the License, or
9 * (at your option) any later version.
11 * HomeBank 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.
16 * You should have received a copy of the GNU General Public License
17 * along with this program. If not, see <http://www.gnu.org/licenses/>.
22 #include <stdlib.h> /* atoi, atof, atol */
25 #include <gdk/gdkkeysyms.h>
27 #include "gtk-dateentry.h"
49 static guint dateentry_signals
[LAST_SIGNAL
] = {0,};
52 G_DEFINE_TYPE(GtkDateEntry
, gtk_date_entry
, GTK_TYPE_BOX
)
56 // this is to be able to seizure d or d/m or m/d in the gtkdateentry
58 /* order of these in the current locale */
59 static GDateDMY dmy_order
[3] =
61 G_DATE_DAY
, G_DATE_MONTH
, G_DATE_YEAR
64 struct _GDateParseTokens
{
70 typedef struct _GDateParseTokens GDateParseTokens
;
75 hb_date_fill_parse_tokens (const gchar
*str
, GDateParseTokens
*pt
)
77 gchar num
[4][NUM_LEN
+1];
81 //DB( g_print("\n[dateentry] fill parse token\n") );
83 /* We count 4, but store 3; so we can give an error
86 num
[0][0] = num
[1][0] = num
[2][0] = num
[3][0] = '\0';
88 s
= (const guchar
*) str
;
90 while (*s
&& pt
->num_ints
< 4)
94 while (*s
&& g_ascii_isdigit (*s
) && i
< NUM_LEN
)
96 num
[pt
->num_ints
][i
] = *s
;
103 num
[pt
->num_ints
][i
] = '\0';
107 if (*s
== '\0') break;
112 pt
->n
[0] = pt
->num_ints
> 0 ? atoi (num
[0]) : 0;
113 pt
->n
[1] = pt
->num_ints
> 1 ? atoi (num
[1]) : 0;
114 pt
->n
[2] = pt
->num_ints
> 2 ? atoi (num
[2]) : 0;
119 static void hb_date_parse_tokens(GDate
*date
, const gchar
*str
)
123 hb_date_fill_parse_tokens(str
, &pt
);
124 DB( g_print(" -> parsetoken return %d values: %d %d %d\n", pt
.num_ints
, pt
.n
[0], pt
.n
[1], pt
.n
[2]) );
126 // initialize with today's date
127 g_date_set_time_t(date
, time(NULL
));
129 switch( pt
.num_ints
)
132 DB( g_print(" -> seizured 1 number\n") );
133 if(g_date_valid_day(pt
.n
[0]))
134 g_date_set_day(date
, pt
.n
[0]);
137 DB( g_print(" -> seizured 2 numbers\n") );
138 if( dmy_order
[0] != G_DATE_YEAR
)
140 if( dmy_order
[0] == G_DATE_DAY
)
142 if(g_date_valid_day(pt
.n
[0]))
143 g_date_set_day(date
, pt
.n
[0]);
144 if(g_date_valid_month(pt
.n
[1]))
145 g_date_set_month(date
, pt
.n
[1]);
149 if(g_date_valid_day(pt
.n
[1]))
150 g_date_set_day(date
, pt
.n
[1]);
151 if(g_date_valid_month(pt
.n
[0]))
152 g_date_set_month(date
, pt
.n
[0]);
161 update_text(GtkDateEntry
*self
)
163 GtkDateEntryPrivate
*priv
= self
->priv
;
166 DB( g_print("\n[dateentry] update text\n") );
168 //%x : The preferred date representation for the current locale without the time.
169 g_date_strftime (label
, 256 - 1, "%x", priv
->date
);
170 gtk_entry_set_text (GTK_ENTRY (priv
->entry
), label
);
171 DB( g_print(" = %s\n", label
) );
176 eval_date(GtkDateEntry
*self
)
178 GtkDateEntryPrivate
*priv
= self
->priv
;
180 g_date_clamp(priv
->date
, &priv
->mindate
, &priv
->maxdate
);
184 if(priv
->lastdate
!= g_date_get_julian(priv
->date
))
186 DB( g_print(" **emit 'changed' signal**\n") );
187 g_signal_emit_by_name (self
, "changed", NULL
, NULL
);
190 priv
->lastdate
= g_date_get_julian(priv
->date
);
195 parse_date(GtkDateEntry
*self
)
197 GtkDateEntryPrivate
*priv
= self
->priv
;
200 DB( g_print("\n[dateentry] parse date\n") );
202 str
= gtk_entry_get_text (GTK_ENTRY (priv
->entry
));
204 //1) we parse the string according to the locale
205 g_date_set_parse (priv
->date
, str
);
206 if(!g_date_valid(priv
->date
) || g_date_get_julian (priv
->date
) <= HB_MINDATE
)
208 //2) give a try to tokens: day, day/month, month/day
209 hb_date_parse_tokens(priv
->date
, str
);
212 //3) at last if date still invalid, put today's dateentry_signals
213 // we should consider just warn the user here
214 if(!g_date_valid(priv
->date
))
216 g_date_set_time_t(priv
->date
, time(NULL
));
223 gtk_date_entry_cb_calendar_day_selected(GtkWidget
* calendar
, GtkDateEntry
* dateentry
)
225 GtkDateEntryPrivate
*priv
= dateentry
->priv
;
226 guint year
, month
, day
;
228 DB( g_print("\n[dateentry] calendar_day_selected\n") );
230 gtk_calendar_get_date (GTK_CALENDAR (priv
->calendar
), &year
, &month
, &day
);
231 g_date_set_dmy (priv
->date
, day
, month
+ 1, year
);
232 eval_date(dateentry
);
237 gtk_date_entry_cb_calendar_day_select_double_click(GtkWidget
* calendar
, gpointer user_data
)
239 GtkDateEntry
*dateentry
= user_data
;
240 GtkDateEntryPrivate
*priv
= dateentry
->priv
;
242 DB( g_print("\n[dateentry] calendar_day_select_double_click\n") );
244 gtk_widget_hide (priv
->popover
);
251 gtk_date_entry_cb_calendar_monthyear(GtkWidget
*calendar
, GtkDateEntry
*dateentry
)
253 GtkDateEntryPrivate
*priv
= dateentry
->priv
;
254 guint year
, month
, day
;
256 DB( g_print("\n[dateentry] cb_calendar_monthyear\n") );
258 gtk_calendar_get_date (GTK_CALENDAR (priv
->calendar
), &year
, &month
, &day
);
260 g_object_set(calendar
, "year", 1900, NULL
);
263 g_object_set(calendar
, "year", 2200, NULL
);
269 gtk_date_entry_cb_entry_key_pressed (GtkWidget
*widget
, GdkEventKey
*event
, gpointer user_data
)
271 GtkDateEntry
*dateentry
= user_data
;
272 GtkDateEntryPrivate
*priv
= dateentry
->priv
;
274 DB( g_print("\n[dateentry] entry key pressed: state=%04x, keyval=%04x\n", event
->state
, event
->keyval
) );
276 if( event
->keyval
== GDK_KEY_Up
)
278 if( !(event
->state
& (GDK_SHIFT_MASK
| GDK_CONTROL_MASK
)) )
280 g_date_add_days (priv
->date
, 1);
281 eval_date(dateentry
);
284 if( event
->state
& GDK_SHIFT_MASK
)
286 g_date_add_months (priv
->date
, 1);
287 eval_date(dateentry
);
290 if( event
->state
& GDK_CONTROL_MASK
)
292 g_date_add_years (priv
->date
, 1);
293 eval_date(dateentry
);
298 if( event
->keyval
== GDK_KEY_Down
)
300 if( !(event
->state
& (GDK_SHIFT_MASK
| GDK_CONTROL_MASK
)) )
302 g_date_subtract_days (priv
->date
, 1);
303 eval_date(dateentry
);
306 if( event
->state
& GDK_SHIFT_MASK
)
308 g_date_subtract_months (priv
->date
, 1);
309 eval_date(dateentry
);
312 if( event
->state
& GDK_CONTROL_MASK
)
314 g_date_subtract_years (priv
->date
, 1);
315 eval_date(dateentry
);
325 gtk_date_entry_cb_entry_activate(GtkWidget
*gtkentry
, gpointer user_data
)
327 GtkDateEntry
*dateentry
= user_data
;
329 DB( g_print("\n[dateentry] entry_activate\n") );
331 parse_date(dateentry
);
332 eval_date(dateentry
);
337 gtk_date_entry_cb_entry_focus_out(GtkWidget
*widget
, GdkEventFocus
*event
, gpointer user_data
)
339 GtkDateEntry
*dateentry
= user_data
;
341 DB( g_print("\n[dateentry] entry focus-out-event %d\n", gtk_widget_is_focus(GTK_WIDGET(dateentry
))) );
343 parse_date(dateentry
);
344 eval_date(dateentry
);
350 gtk_date_entry_cb_button_clicked (GtkWidget
* widget
, GtkDateEntry
* dateentry
)
352 GtkDateEntryPrivate
*priv
= dateentry
->priv
;
356 DB( g_print("\n[dateentry] button_clicked\n") );
358 /* GtkCalendar expects month to be in 0-11 range (inclusive) */
359 month
= g_date_get_month (priv
->date
) - 1;
361 g_signal_handler_block(priv
->calendar
, priv
->hid_dayselect
);
363 gtk_calendar_select_month (GTK_CALENDAR (priv
->calendar
),
364 CLAMP (month
, 0, 11),
365 g_date_get_year (priv
->date
));
366 gtk_calendar_select_day (GTK_CALENDAR (priv
->calendar
),
367 g_date_get_day (priv
->date
));
369 g_signal_handler_unblock(priv
->calendar
, priv
->hid_dayselect
);
371 gtk_popover_set_relative_to (GTK_POPOVER (priv
->popover
), GTK_WIDGET (priv
->entry
));
372 //gtk_widget_get_clip(priv->arrow, &rect);
373 //gtk_popover_set_pointing_to (GTK_POPOVER (priv->popover), &rect);
375 gtk_widget_show_all (priv
->popover
);
380 gtk_date_entry_destroy (GtkWidget
*object
)
382 GtkDateEntry
*dateentry
= GTK_DATE_ENTRY (object
);
383 GtkDateEntryPrivate
*priv
= dateentry
->priv
;
385 g_return_if_fail(object
!= NULL
);
386 g_return_if_fail(GTK_IS_DATE_ENTRY(object
));
388 DB( g_print("\n[dateentry] destroy\n") );
390 DB( g_print(" free gtkentry: %p\n", priv
->entry
) );
391 DB( g_print(" free arrow: %p\n", priv
->button
) );
393 DB( g_print(" free dateentry: %p\n", dateentry
) );
396 g_date_free(priv
->date
);
399 GTK_WIDGET_CLASS (gtk_date_entry_parent_class
)->destroy (object
);
405 gtk_date_entry_dispose (GObject
*gobject
)
407 //GtkDateEntry *self = GTK_DATE_ENTRY (gobject);
409 DB( g_print("\n[dateentry] dispose\n") );
412 //g_clear_object (&self->priv->an_object);
414 G_OBJECT_CLASS (gtk_date_entry_parent_class
)->dispose (gobject
);
421 gtk_date_entry_finalize (GObject
*gobject
)
423 //GtkDateEntry *self = GTK_DATE_ENTRY (gobject);
425 DB( g_print("\n[dateentry] finalize\n") );
428 //g_date_free(self->date);
429 //g_free (self->priv->a_string);
431 /* Always chain up to the parent class; as with dispose(), finalize()
432 * is guaranteed to exist on the parent's class virtual function table
434 G_OBJECT_CLASS(gtk_date_entry_parent_class
)->finalize (gobject
);
440 gtk_date_entry_class_init (GtkDateEntryClass
*class)
442 GObjectClass
*object_class
;
443 GtkWidgetClass
*widget_class
;
445 object_class
= G_OBJECT_CLASS (class);
446 widget_class
= GTK_WIDGET_CLASS (class);
448 DB( g_print("\n[dateentry] class_init\n") );
450 //object_class->constructor = gtk_date_entry_constructor;
451 //object_class->set_property = gtk_date_entry_set_property;
452 //object_class->get_property = gtk_date_entry_get_property;
453 object_class
->dispose
= gtk_date_entry_dispose
;
454 object_class
->finalize
= gtk_date_entry_finalize
;
456 widget_class
->destroy
= gtk_date_entry_destroy
;
458 dateentry_signals
[CHANGED
] =
459 g_signal_new ("changed",
460 G_TYPE_FROM_CLASS (class),
462 G_STRUCT_OFFSET (GtkDateEntryClass
, changed
),
464 g_cclosure_marshal_VOID__VOID
,
467 g_type_class_add_private (object_class
, sizeof (GtkDateEntryPrivate
));
472 gtk_date_entry_init (GtkDateEntry
*dateentry
)
474 GtkDateEntryPrivate
*priv
;
476 DB( g_print("\n[dateentry] init\n") );
478 /* yes, also priv, need to keep the code readable */
479 dateentry
->priv
= G_TYPE_INSTANCE_GET_PRIVATE (dateentry
,
481 GtkDateEntryPrivate
);
482 priv
= dateentry
->priv
;
484 gtk_style_context_add_class (gtk_widget_get_style_context (GTK_WIDGET(dateentry
)), GTK_STYLE_CLASS_LINKED
);
486 priv
->entry
= gtk_entry_new ();
487 //todo: see if really useful
488 gtk_entry_set_width_chars(GTK_ENTRY(priv
->entry
), 16);
489 gtk_entry_set_max_width_chars(GTK_ENTRY(priv
->entry
), 16);
490 gtk_box_pack_start (GTK_BOX (dateentry
), priv
->entry
, TRUE
, TRUE
, 0);
492 priv
->button
= gtk_button_new ();
493 priv
->arrow
= gtk_image_new_from_icon_name ("pan-down-symbolic", GTK_ICON_SIZE_BUTTON
);
494 gtk_container_add (GTK_CONTAINER (priv
->button
), priv
->arrow
);
495 gtk_box_pack_end (GTK_BOX (dateentry
), priv
->button
, FALSE
, FALSE
, 0);
497 priv
->popover
= gtk_popover_new (priv
->button
);
498 gtk_popover_set_position(GTK_POPOVER(priv
->popover
), GTK_POS_BOTTOM
);
499 priv
->calendar
= gtk_calendar_new ();
500 gtk_container_add (GTK_CONTAINER (priv
->popover
), priv
->calendar
);
502 gtk_widget_set_margin_start (priv
->calendar
, 10);
503 gtk_widget_set_margin_end (priv
->calendar
, 10);
504 gtk_widget_set_margin_top (priv
->calendar
, 10);
505 gtk_widget_set_margin_bottom (priv
->calendar
, 10);
507 gtk_widget_show_all (GTK_WIDGET(dateentry
));
509 /* initialize datas */
510 priv
->date
= g_date_new();
511 g_date_set_time_t(priv
->date
, time(NULL
));
512 g_date_set_dmy(&priv
->mindate
, 1, 1, 1900); //693596
513 g_date_set_dmy(&priv
->maxdate
, 31, 12, 2200); //803533
514 update_text(dateentry
);
517 g_signal_connect (priv
->entry
, "key-press-event",
518 G_CALLBACK (gtk_date_entry_cb_entry_key_pressed
), dateentry
);
520 g_signal_connect_after (priv
->entry
, "focus-out-event",
521 G_CALLBACK (gtk_date_entry_cb_entry_focus_out
), dateentry
);
523 g_signal_connect (priv
->entry
, "activate",
524 G_CALLBACK (gtk_date_entry_cb_entry_activate
), dateentry
);
527 g_signal_connect (priv
->button
, "clicked",
528 G_CALLBACK (gtk_date_entry_cb_button_clicked
), dateentry
);
531 g_signal_connect (priv
->calendar
, "prev-year",
532 G_CALLBACK (gtk_date_entry_cb_calendar_monthyear
), dateentry
);
533 g_signal_connect (priv
->calendar
, "next-year",
534 G_CALLBACK (gtk_date_entry_cb_calendar_monthyear
), dateentry
);
535 g_signal_connect (priv
->calendar
, "prev-month",
536 G_CALLBACK (gtk_date_entry_cb_calendar_monthyear
), dateentry
);
537 g_signal_connect (priv
->calendar
, "next-month",
538 G_CALLBACK (gtk_date_entry_cb_calendar_monthyear
), dateentry
);
540 priv
->hid_dayselect
= g_signal_connect (priv
->calendar
, "day-selected",
541 G_CALLBACK (gtk_date_entry_cb_calendar_day_selected
), dateentry
);
543 g_signal_connect (priv
->calendar
, "day-selected-double-click",
544 G_CALLBACK (gtk_date_entry_cb_calendar_day_select_double_click
), dateentry
);
550 gtk_date_entry_new ()
552 GtkDateEntry
*dateentry
;
554 DB( g_print("\n[dateentry] new\n") );
556 dateentry
= g_object_new (GTK_TYPE_DATE_ENTRY
, NULL
);
558 return GTK_WIDGET(dateentry
);
566 gtk_date_entry_set_mindate(GtkDateEntry
*dateentry
, guint32 julian_days
)
568 GtkDateEntryPrivate
*priv
= dateentry
->priv
;
570 DB( g_print("\n[dateentry] set mindate\n") );
572 g_return_if_fail (GTK_IS_DATE_ENTRY (dateentry
));
574 if(g_date_valid_julian(julian_days
))
576 g_date_set_julian (&priv
->mindate
, julian_days
);
585 gtk_date_entry_set_maxdate(GtkDateEntry
*dateentry
, guint32 julian_days
)
587 GtkDateEntryPrivate
*priv
= dateentry
->priv
;
589 DB( g_print("\n[dateentry] set maxdate\n") );
591 g_return_if_fail (GTK_IS_DATE_ENTRY (dateentry
));
593 if(g_date_valid_julian(julian_days
))
595 g_date_set_julian (&priv
->maxdate
, julian_days
);
604 gtk_date_entry_set_date(GtkDateEntry
*dateentry
, guint32 julian_days
)
606 GtkDateEntryPrivate
*priv
= dateentry
->priv
;
608 DB( g_print("\n[dateentry] set date\n") );
610 g_return_if_fail (GTK_IS_DATE_ENTRY (dateentry
));
612 if(g_date_valid_julian(julian_days
))
614 g_date_set_julian (priv
->date
, julian_days
);
618 g_date_set_time_t(priv
->date
, time(NULL
));
620 eval_date(dateentry
);
628 gtk_date_entry_get_date(GtkDateEntry
*dateentry
)
630 GtkDateEntryPrivate
*priv
= dateentry
->priv
;
632 DB( g_print("\n[dateentry] get date\n") );
634 g_return_val_if_fail (GTK_IS_DATE_ENTRY (dateentry
), 0);
636 return(g_date_get_julian(priv
->date
));