]> Dogcows Code - chaz/homebank/blob - src/gtk-dateentry.c
Merge branch 'upstream'
[chaz/homebank] / src / gtk-dateentry.c
1 /* HomeBank -- Free, easy, personal accounting for everyone.
2 * Copyright (C) 1995-2019 Maxime DOYEN
3 *
4 * This file is part of HomeBank.
5 *
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.
10 *
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.
15 *
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/>.
18 */
19
20
21 //#include <time.h>
22 #include <stdlib.h> /* atoi, atof, atol */
23
24 #include <gtk/gtk.h>
25 #include <gdk/gdkkeysyms.h>
26
27 #include "gtk-dateentry.h"
28
29 #define MYDEBUG 0
30
31 #if MYDEBUG
32 #define DB(x) (x);
33 #else
34 #define DB(x);
35 #endif
36
37
38 enum {
39 CHANGED,
40 LAST_SIGNAL
41 };
42
43
44 enum {
45 PROPERTY_DATE = 5,
46 };
47
48
49 static guint dateentry_signals[LAST_SIGNAL] = {0,};
50
51
52 G_DEFINE_TYPE(GtkDateEntry, gtk_date_entry, GTK_TYPE_BOX)
53
54
55 // todo:finish this
56 // this is to be able to seizure d or d/m or m/d in the gtkdateentry
57
58 /* order of these in the current locale */
59 static GDateDMY dmy_order[3] =
60 {
61 G_DATE_DAY, G_DATE_MONTH, G_DATE_YEAR
62 };
63
64 struct _GDateParseTokens {
65 gint num_ints;
66 gint n[3];
67 guint month;
68 };
69
70 typedef struct _GDateParseTokens GDateParseTokens;
71
72 #define NUM_LEN 10
73
74 static void
75 hb_date_fill_parse_tokens (const gchar *str, GDateParseTokens *pt)
76 {
77 gchar num[4][NUM_LEN+1];
78 gint i;
79 const guchar *s;
80
81 //DB( g_print("\n[dateentry] fill parse token\n") );
82
83 /* We count 4, but store 3; so we can give an error
84 * if there are 4.
85 */
86 num[0][0] = num[1][0] = num[2][0] = num[3][0] = '\0';
87
88 s = (const guchar *) str;
89 pt->num_ints = 0;
90 while (*s && pt->num_ints < 4)
91 {
92
93 i = 0;
94 while (*s && g_ascii_isdigit (*s) && i < NUM_LEN)
95 {
96 num[pt->num_ints][i] = *s;
97 ++s;
98 ++i;
99 }
100
101 if (i > 0)
102 {
103 num[pt->num_ints][i] = '\0';
104 ++(pt->num_ints);
105 }
106
107 if (*s == '\0') break;
108
109 ++s;
110 }
111
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;
115
116 }
117
118
119 static void hb_date_parse_tokens(GDate *date, const gchar *str)
120 {
121 GDateParseTokens pt;
122
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]) );
125
126 // initialize with today's date
127 g_date_set_time_t(date, time(NULL));
128
129 switch( pt.num_ints )
130 {
131 case 1:
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]);
135 break;
136 case 2:
137 DB( g_print(" -> seizured 2 numbers\n") );
138 if( dmy_order[0] != G_DATE_YEAR )
139 {
140 if( dmy_order[0] == G_DATE_DAY )
141 {
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]);
146 }
147 else
148 {
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]);
153 }
154 }
155 break;
156 }
157 }
158
159
160 static void
161 update_text(GtkDateEntry *self)
162 {
163 GtkDateEntryPrivate *priv = self->priv;
164 gchar label[256];
165
166 DB( g_print("\n[dateentry] update text\n") );
167
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) );
172 }
173
174
175 static void
176 eval_date(GtkDateEntry *self)
177 {
178 GtkDateEntryPrivate *priv = self->priv;
179
180 g_date_clamp(priv->date, &priv->mindate, &priv->maxdate);
181
182 update_text(self);
183
184 if(priv->lastdate != g_date_get_julian(priv->date))
185 {
186 DB( g_print(" **emit 'changed' signal**\n") );
187 g_signal_emit_by_name (self, "changed", NULL, NULL);
188 }
189
190 priv->lastdate = g_date_get_julian(priv->date);
191 }
192
193
194 static void
195 parse_date(GtkDateEntry *self)
196 {
197 GtkDateEntryPrivate *priv = self->priv;
198 const gchar *str;
199
200 DB( g_print("\n[dateentry] parse date\n") );
201
202 str = gtk_entry_get_text (GTK_ENTRY (priv->entry));
203
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)
207 {
208 //2) give a try to tokens: day, day/month, month/day
209 hb_date_parse_tokens(priv->date, str);
210 }
211
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))
215 {
216 g_date_set_time_t(priv->date, time(NULL));
217 }
218 eval_date(self);
219 }
220
221
222 static void
223 gtk_date_entry_cb_calendar_day_selected(GtkWidget * calendar, GtkDateEntry * dateentry)
224 {
225 GtkDateEntryPrivate *priv = dateentry->priv;
226 guint year, month, day;
227
228 DB( g_print("\n[dateentry] calendar_day_selected\n") );
229
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);
233 }
234
235
236 static gint
237 gtk_date_entry_cb_calendar_day_select_double_click(GtkWidget * calendar, gpointer user_data)
238 {
239 GtkDateEntry *dateentry = user_data;
240 GtkDateEntryPrivate *priv = dateentry->priv;
241
242 DB( g_print("\n[dateentry] calendar_day_select_double_click\n") );
243
244 gtk_widget_hide (priv->popover);
245
246 return FALSE;
247 }
248
249
250 static void
251 gtk_date_entry_cb_calendar_monthyear(GtkWidget *calendar, GtkDateEntry *dateentry)
252 {
253 GtkDateEntryPrivate *priv = dateentry->priv;
254 guint year, month, day;
255
256 DB( g_print("\n[dateentry] cb_calendar_monthyear\n") );
257
258 gtk_calendar_get_date (GTK_CALENDAR (priv->calendar), &year, &month, &day);
259 if( year < 1900)
260 g_object_set(calendar, "year", 1900, NULL);
261
262 if( year > 2200)
263 g_object_set(calendar, "year", 2200, NULL);
264
265 }
266
267
268 static gint
269 gtk_date_entry_cb_entry_key_pressed (GtkWidget *widget, GdkEventKey *event, gpointer user_data)
270 {
271 GtkDateEntry *dateentry = user_data;
272 GtkDateEntryPrivate *priv = dateentry->priv;
273
274 DB( g_print("\n[dateentry] entry key pressed: state=%04x, keyval=%04x\n", event->state, event->keyval) );
275
276 if( event->keyval == GDK_KEY_Up )
277 {
278 if( !(event->state & (GDK_SHIFT_MASK | GDK_CONTROL_MASK)) )
279 {
280 g_date_add_days (priv->date, 1);
281 eval_date(dateentry);
282 }
283 else
284 if( event->state & GDK_SHIFT_MASK )
285 {
286 g_date_add_months (priv->date, 1);
287 eval_date(dateentry);
288 }
289 else
290 if( event->state & GDK_CONTROL_MASK )
291 {
292 g_date_add_years (priv->date, 1);
293 eval_date(dateentry);
294 }
295 return TRUE;
296 }
297 else
298 if( event->keyval == GDK_KEY_Down )
299 {
300 if( !(event->state & (GDK_SHIFT_MASK | GDK_CONTROL_MASK)) )
301 {
302 g_date_subtract_days (priv->date, 1);
303 eval_date(dateentry);
304 }
305 else
306 if( event->state & GDK_SHIFT_MASK )
307 {
308 g_date_subtract_months (priv->date, 1);
309 eval_date(dateentry);
310 }
311 else
312 if( event->state & GDK_CONTROL_MASK )
313 {
314 g_date_subtract_years (priv->date, 1);
315 eval_date(dateentry);
316 }
317 return TRUE;
318 }
319
320 return FALSE;
321 }
322
323
324 static void
325 gtk_date_entry_cb_entry_activate(GtkWidget *gtkentry, gpointer user_data)
326 {
327 GtkDateEntry *dateentry = user_data;
328
329 DB( g_print("\n[dateentry] entry_activate\n") );
330
331 parse_date(dateentry);
332 eval_date(dateentry);
333 }
334
335
336 static gboolean
337 gtk_date_entry_cb_entry_focus_out(GtkWidget *widget, GdkEventFocus *event, gpointer user_data)
338 {
339 GtkDateEntry *dateentry = user_data;
340
341 DB( g_print("\n[dateentry] entry focus-out-event %d\n", gtk_widget_is_focus(GTK_WIDGET(dateentry))) );
342
343 parse_date(dateentry);
344 eval_date(dateentry);
345 return FALSE;
346 }
347
348
349 static void
350 gtk_date_entry_cb_button_clicked (GtkWidget * widget, GtkDateEntry * dateentry)
351 {
352 GtkDateEntryPrivate *priv = dateentry->priv;
353 //GdkRectangle rect;
354 int month;
355
356 DB( g_print("\n[dateentry] button_clicked\n") );
357
358 /* GtkCalendar expects month to be in 0-11 range (inclusive) */
359 month = g_date_get_month (priv->date) - 1;
360
361 g_signal_handler_block(priv->calendar, priv->hid_dayselect);
362
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));
368
369 g_signal_handler_unblock(priv->calendar, priv->hid_dayselect);
370
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);
374
375 gtk_widget_show_all (priv->popover);
376 }
377
378
379 static void
380 gtk_date_entry_destroy (GtkWidget *object)
381 {
382 GtkDateEntry *dateentry = GTK_DATE_ENTRY (object);
383 GtkDateEntryPrivate *priv = dateentry->priv;
384
385 g_return_if_fail(object != NULL);
386 g_return_if_fail(GTK_IS_DATE_ENTRY(object));
387
388 DB( g_print("\n[dateentry] destroy\n") );
389
390 DB( g_print(" free gtkentry: %p\n", priv->entry) );
391 DB( g_print(" free arrow: %p\n", priv->button) );
392
393 DB( g_print(" free dateentry: %p\n", dateentry) );
394
395 if(priv->date)
396 g_date_free(priv->date);
397 priv->date = NULL;
398
399 GTK_WIDGET_CLASS (gtk_date_entry_parent_class)->destroy (object);
400 }
401
402
403
404 static void
405 gtk_date_entry_dispose (GObject *gobject)
406 {
407 //GtkDateEntry *self = GTK_DATE_ENTRY (gobject);
408
409 DB( g_print("\n[dateentry] dispose\n") );
410
411
412 //g_clear_object (&self->priv->an_object);
413
414 G_OBJECT_CLASS (gtk_date_entry_parent_class)->dispose (gobject);
415 }
416
417
418
419
420 static void
421 gtk_date_entry_finalize (GObject *gobject)
422 {
423 //GtkDateEntry *self = GTK_DATE_ENTRY (gobject);
424
425 DB( g_print("\n[dateentry] finalize\n") );
426
427
428 //g_date_free(self->date);
429 //g_free (self->priv->a_string);
430
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
433 */
434 G_OBJECT_CLASS(gtk_date_entry_parent_class)->finalize (gobject);
435 }
436
437
438
439 static void
440 gtk_date_entry_class_init (GtkDateEntryClass *class)
441 {
442 GObjectClass *object_class;
443 GtkWidgetClass *widget_class;
444
445 object_class = G_OBJECT_CLASS (class);
446 widget_class = GTK_WIDGET_CLASS (class);
447
448 DB( g_print("\n[dateentry] class_init\n") );
449
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;
455
456 widget_class->destroy = gtk_date_entry_destroy;
457
458 dateentry_signals[CHANGED] =
459 g_signal_new ("changed",
460 G_TYPE_FROM_CLASS (class),
461 G_SIGNAL_RUN_LAST,
462 G_STRUCT_OFFSET (GtkDateEntryClass, changed),
463 NULL, NULL,
464 g_cclosure_marshal_VOID__VOID,
465 G_TYPE_NONE, 0);
466
467 g_type_class_add_private (object_class, sizeof (GtkDateEntryPrivate));
468
469 }
470
471 static void
472 gtk_date_entry_init (GtkDateEntry *dateentry)
473 {
474 GtkDateEntryPrivate *priv;
475
476 DB( g_print("\n[dateentry] init\n") );
477
478 /* yes, also priv, need to keep the code readable */
479 dateentry->priv = G_TYPE_INSTANCE_GET_PRIVATE (dateentry,
480 GTK_TYPE_DATE_ENTRY,
481 GtkDateEntryPrivate);
482 priv = dateentry->priv;
483
484 gtk_style_context_add_class (gtk_widget_get_style_context (GTK_WIDGET(dateentry)), GTK_STYLE_CLASS_LINKED);
485
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);
491
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);
496
497 priv->popover = gtk_popover_new (priv->button);
498 gtk_popover_set_position(GTK_POPOVER(priv->popover), GTK_POS_BOTTOM);
499 gtk_container_set_border_width (GTK_CONTAINER (priv->popover), 6);
500 priv->calendar = gtk_calendar_new ();
501 gtk_container_add (GTK_CONTAINER (priv->popover), priv->calendar);
502
503 gtk_widget_show_all (GTK_WIDGET(dateentry));
504
505 /* initialize datas */
506 priv->date = g_date_new();
507 g_date_set_time_t(priv->date, time(NULL));
508 g_date_set_dmy(&priv->mindate, 1, 1, 1900); //693596
509 g_date_set_dmy(&priv->maxdate, 31, 12, 2200); //803533
510 update_text(dateentry);
511
512
513 g_signal_connect (priv->entry, "key-press-event",
514 G_CALLBACK (gtk_date_entry_cb_entry_key_pressed), dateentry);
515
516 g_signal_connect_after (priv->entry, "focus-out-event",
517 G_CALLBACK (gtk_date_entry_cb_entry_focus_out), dateentry);
518
519 g_signal_connect (priv->entry, "activate",
520 G_CALLBACK (gtk_date_entry_cb_entry_activate), dateentry);
521
522
523 g_signal_connect (priv->button, "clicked",
524 G_CALLBACK (gtk_date_entry_cb_button_clicked), dateentry);
525
526
527 g_signal_connect (priv->calendar, "prev-year",
528 G_CALLBACK (gtk_date_entry_cb_calendar_monthyear), dateentry);
529 g_signal_connect (priv->calendar, "next-year",
530 G_CALLBACK (gtk_date_entry_cb_calendar_monthyear), dateentry);
531 g_signal_connect (priv->calendar, "prev-month",
532 G_CALLBACK (gtk_date_entry_cb_calendar_monthyear), dateentry);
533 g_signal_connect (priv->calendar, "next-month",
534 G_CALLBACK (gtk_date_entry_cb_calendar_monthyear), dateentry);
535
536 priv->hid_dayselect = g_signal_connect (priv->calendar, "day-selected",
537 G_CALLBACK (gtk_date_entry_cb_calendar_day_selected), dateentry);
538
539 g_signal_connect (priv->calendar, "day-selected-double-click",
540 G_CALLBACK (gtk_date_entry_cb_calendar_day_select_double_click), dateentry);
541
542 }
543
544
545 GtkWidget *
546 gtk_date_entry_new ()
547 {
548 GtkDateEntry *dateentry;
549
550 DB( g_print("\n[dateentry] new\n") );
551
552 dateentry = g_object_new (GTK_TYPE_DATE_ENTRY, NULL);
553
554 return GTK_WIDGET(dateentry);
555 }
556
557
558 /*
559 **
560 */
561 void
562 gtk_date_entry_set_mindate(GtkDateEntry *dateentry, guint32 julian_days)
563 {
564 GtkDateEntryPrivate *priv = dateentry->priv;
565
566 DB( g_print("\n[dateentry] set mindate\n") );
567
568 g_return_if_fail (GTK_IS_DATE_ENTRY (dateentry));
569
570 if(g_date_valid_julian(julian_days))
571 {
572 g_date_set_julian (&priv->mindate, julian_days);
573 }
574 }
575
576
577 /*
578 **
579 */
580 void
581 gtk_date_entry_set_maxdate(GtkDateEntry *dateentry, guint32 julian_days)
582 {
583 GtkDateEntryPrivate *priv = dateentry->priv;
584
585 DB( g_print("\n[dateentry] set maxdate\n") );
586
587 g_return_if_fail (GTK_IS_DATE_ENTRY (dateentry));
588
589 if(g_date_valid_julian(julian_days))
590 {
591 g_date_set_julian (&priv->maxdate, julian_days);
592 }
593 }
594
595
596 /*
597 **
598 */
599 void
600 gtk_date_entry_set_date(GtkDateEntry *dateentry, guint32 julian_days)
601 {
602 GtkDateEntryPrivate *priv = dateentry->priv;
603
604 DB( g_print("\n[dateentry] set date\n") );
605
606 g_return_if_fail (GTK_IS_DATE_ENTRY (dateentry));
607
608 if(g_date_valid_julian(julian_days))
609 {
610 g_date_set_julian (priv->date, julian_days);
611 }
612 else
613 {
614 g_date_set_time_t(priv->date, time(NULL));
615 }
616 eval_date(dateentry);
617 }
618
619
620 /*
621 **
622 */
623 guint32
624 gtk_date_entry_get_date(GtkDateEntry *dateentry)
625 {
626 GtkDateEntryPrivate *priv = dateentry->priv;
627
628 DB( g_print("\n[dateentry] get date\n") );
629
630 g_return_val_if_fail (GTK_IS_DATE_ENTRY (dateentry), 0);
631
632 return(g_date_get_julian(priv->date));
633 }
634
This page took 0.062743 seconds and 4 git commands to generate.