1 /* ************************************************************************** */
2 /* */
3 /* Copyright (C) 2000-2008 Cédric Auger (cedric@grisbi.org) */
4 /* 2003-2008 Benjamin Drieu (bdrieu@april.org) */
5 /* https://www.grisbi.org/ */
6 /* */
7 /* This program is free software; you can redistribute it and/or modify */
8 /* it under the terms of the GNU General Public License as published by */
9 /* the Free Software Foundation; either version 2 of the License, or */
10 /* (at your option) any later version. */
11 /* */
12 /* This program 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 */
15 /* GNU 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, write to the Free Software */
19 /* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */
20 /* */
21 /* ************************************************************************** */
22
23 /**
24 * \file gsb_calendar_entry.c
25 * this is a new widget, an entry for containing a date
26 * that entry controls the calendar, the check of the date...
27 *
28 * */
29
30 #ifdef HAVE_CONFIG_H
31 #include "config.h"
32 #endif
33
34 #include "include.h"
35 #include <ctype.h>
36 #include <gdk/gdkkeysyms.h>
37 #include <glib/gi18n.h>
38
39 /*START_INCLUDE*/
40 #include "gsb_calendar_entry.h"
41 #include "grisbi_app.h"
42 #include "gsb_form_widget.h"
43 #include "structures.h"
44 #include "utils_dates.h"
45 /*END_INCLUDE*/
46
47 /*START_STATIC*/
48 static gboolean gsb_calendar_entry_button_press ( GtkWidget *entry,
49 GdkEventButton *event,
50 gpointer null );
51 static gboolean gsb_calendar_entry_calendar_key_press ( GtkCalendar *pCalendar,
52 GdkEventKey *ev,
53 GtkWidget *entry );
54 static gboolean gsb_calendar_entry_changed ( GtkWidget *entry,
55 gpointer null );
56 static gboolean gsb_calendar_entry_focus_out ( GtkWidget *entry,
57 GdkEventFocus *event,
58 gint *set_today );
59 static gboolean gsb_calendar_entry_key_press ( GtkWidget *entry,
60 GdkEventKey *ev,
61 gpointer null );
62 static GtkWidget *gsb_calendar_entry_popup ( GtkWidget *entry );
63 static gboolean gsb_calendar_entry_select_date ( GtkCalendar *pCalendar,
64 GtkWidget *entry );
65 static void gsb_calendar_entry_step_date ( GtkWidget *entry,
66 gint movement );
67 /*END_STATIC*/
68
69
70
71 /*START_EXTERN*/
72 /*END_EXTERN*/
73
74 #define ENTRY_NORMAL 0
75 #define ENTRY_RED 1
76
77
78 /**
79 * create a new entry for contain a date
80 * if double-click on that entry, popup a calendar
81 * when leave the entry, check the date and complete it
82 *
83 * \param set_today TRUE if we want to set the current day if the entry is left empty
84 *
85 * \return a GtkEntry widget
86 * */
gsb_calendar_entry_new(gint set_today)87 GtkWidget *gsb_calendar_entry_new (gint set_today)
88 {
89 GtkWidget *entry;
90
91 entry = gtk_entry_new ();
92 gsb_calendar_entry_new_from_ui (entry, set_today);
93
94 return entry;
95 }
96
97 /**
98 * init a new entry for contain a date
99 * if double-click on that entry, popup a calendar
100 * when leave the entry, check the date and complete it
101 *
102 * \param entry entry from ui
103 * \param set_today TRUE if we want to set the current day if the entry is left empty
104 *
105 * \return
106 **/
gsb_calendar_entry_new_from_ui(GtkWidget * entry,gint set_today)107 void gsb_calendar_entry_new_from_ui (GtkWidget *entry,
108 gint set_today)
109 {
110 gtk_widget_set_size_request (entry, ENTRY_MIN_WIDTH_1, -1);
111
112 g_signal_connect ( G_OBJECT (entry),
113 "button-press-event",
114 G_CALLBACK (gsb_calendar_entry_button_press),
115 NULL);
116 g_signal_connect_after (G_OBJECT (entry),
117 "focus-out-event",
118 G_CALLBACK (gsb_calendar_entry_focus_out),
119 GINT_TO_POINTER (set_today));
120 g_signal_connect (G_OBJECT (entry),
121 "key-press-event",
122 G_CALLBACK (gsb_calendar_entry_key_press),
123 NULL);
124 g_signal_connect (G_OBJECT (entry),
125 "changed",
126 G_CALLBACK (gsb_calendar_entry_changed),
127 NULL);
128 }
129
130 /**
131 * set the date in the date entry
132 *
133 * \param entry
134 * \param date a GDate or NULL to set nothing
135 *
136 * \return FALSE if problem, TRUE if ok
137 * */
gsb_calendar_entry_set_date(GtkWidget * entry,const GDate * date)138 gboolean gsb_calendar_entry_set_date ( GtkWidget *entry,
139 const GDate *date )
140 {
141 gchar *string;
142
143 if (!entry
144 ||
145 !GTK_IS_ENTRY (entry))
146 return FALSE;
147
148 if (!date
149 ||
150 !g_date_valid (date))
151 {
152 gtk_entry_set_text ( GTK_ENTRY (entry), "" );
153 return FALSE;
154 }
155
156 string = gsb_format_gdate ( date );
157 gtk_entry_set_text ( GTK_ENTRY ( entry ), string );
158 g_free ( string );
159 return TRUE;
160 }
161
162
163 /**
164 * get the date in the date entry
165 *
166 * \param entry
167 *
168 * \return a newly allocated GDate or NULL if invalid date
169 * */
gsb_calendar_entry_get_date(GtkWidget * entry)170 GDate *gsb_calendar_entry_get_date ( GtkWidget *entry )
171 {
172 GDate *date;
173
174 if (!entry
175 ||
176 !GTK_IS_ENTRY (entry)
177 ||
178 !gsb_date_check_entry (entry))
179 return NULL;
180
181 date = gsb_date_get_last_entry_date (gtk_entry_get_text (GTK_ENTRY (entry)));
182 return date;
183 }
184
185
186
187 /**
188 * manual set of the color of the entry
189 * used for example in reconciliation, if cancel it, to set back the good color
190 * of the entry
191 *
192 * \param entry
193 * \param normal_color TRUE if we want to set the normal color, FALSE to set to red
194 *
195 * \return FALSE
196 * */
gsb_calendar_entry_set_color(GtkWidget * entry,gboolean normal_color)197 gboolean gsb_calendar_entry_set_color ( GtkWidget *entry,
198 gboolean normal_color )
199 {
200 if (!entry)
201 return FALSE;
202
203 if (normal_color)
204 {
205 if (gsb_form_widget_check_empty (entry))
206 gtk_widget_set_name (entry, "form_entry_empty");
207 else
208 gtk_widget_set_name (entry, "form_entry");
209 }
210 else
211 {
212 gtk_widget_set_name (entry, "form_entry_error");
213 }
214
215 return FALSE;
216 }
217
218
219
220 /**
221 * callback called when press button on the date entry
222 * used to popup a calendar if double click
223 *
224 * \param entry the entry which receive the signal
225 * \param event
226 * \param null not used
227 *
228 * \return FALSE
229 * */
gsb_calendar_entry_button_press(GtkWidget * entry,GdkEventButton * event,gpointer null)230 gboolean gsb_calendar_entry_button_press ( GtkWidget *entry,
231 GdkEventButton *event,
232 gpointer null )
233 {
234 if ( event -> type == GDK_2BUTTON_PRESS )
235 gsb_calendar_entry_popup (entry);
236
237 return FALSE;
238 }
239
240
241 /**
242 * callback called when press a key on the date entry
243 * used to increase/decrease the date and popup the calendar
244 *
245 * \param entry the entry which receive the signal
246 * \param event
247 * \param null not used
248 *
249 * \return FALSE
250 * */
gsb_calendar_entry_key_press(GtkWidget * entry,GdkEventKey * ev,gpointer null)251 gboolean gsb_calendar_entry_key_press ( GtkWidget *entry,
252 GdkEventKey *ev,
253 gpointer null )
254 {
255 switch ( ev -> keyval )
256 {
257 case GDK_KEY_KP_Enter :
258 case GDK_KEY_Return :
259
260 /* CONTROL ENTER opens the calendar */
261 if ( ( ev -> state & GDK_CONTROL_MASK ) == GDK_CONTROL_MASK)
262 gsb_calendar_entry_popup (entry);
263 break;
264
265 case GDK_KEY_KP_Add:
266 case GDK_KEY_plus:
267 case GDK_KEY_equal: /* This should make all our US users happy */
268
269 /* increase the date of 1 day/week */
270 if ( ( ev -> state & GDK_CONTROL_MASK ) != GDK_CONTROL_MASK ||
271 ev -> keyval != GDK_KEY_KP_Add )
272 gsb_calendar_entry_step_date ( entry, ONE_DAY );
273 else
274 gsb_calendar_entry_step_date ( entry, ONE_WEEK );
275 return TRUE;
276 break;
277
278 case GDK_KEY_KP_Subtract:
279 case GDK_KEY_minus:
280 {
281 const gchar *date_format;
282
283 date_format = gsb_date_get_format_date ();
284 if (g_strcmp0 (date_format, "%Y-%m-%d"))
285 {
286 /* decrease the date of 1 day/week, or the check of 1 */
287 if ( ( ev -> state & GDK_CONTROL_MASK ) != GDK_CONTROL_MASK ||
288 ev -> keyval != GDK_KEY_KP_Subtract )
289 gsb_calendar_entry_step_date ( entry, -ONE_DAY );
290 else
291 gsb_calendar_entry_step_date ( entry, -ONE_WEEK );
292 return TRUE;
293 }
294 break;
295 }
296
297 case GDK_KEY_Page_Up :
298 case GDK_KEY_KP_Page_Up :
299
300 /* increase the date of 1 month/year */
301 if ( ( ev -> state & GDK_CONTROL_MASK ) != GDK_CONTROL_MASK )
302 gsb_calendar_entry_step_date ( entry,
303 ONE_MONTH );
304 else
305 gsb_calendar_entry_step_date ( entry,
306 ONE_YEAR );
307
308 return TRUE;
309 break;
310
311 case GDK_KEY_Page_Down :
312 case GDK_KEY_KP_Page_Down :
313
314 /* decrease the date of 1 month/year */
315 if ( ( ev -> state & GDK_CONTROL_MASK ) != GDK_CONTROL_MASK )
316 gsb_calendar_entry_step_date ( entry,
317 -ONE_MONTH );
318 else
319 gsb_calendar_entry_step_date ( entry,
320 -ONE_YEAR );
321
322 return TRUE;
323 break;
324 }
325 return FALSE;
326 }
327
328
329 /**
330 * increase or decrease the date in the entry date
331 *
332 * \param entry
333 * \param movement + or - ONE_DAY, ONE_WEEK, ONE_MONTH, ONE_YEAR
334 *
335 * \return
336 * */
gsb_calendar_entry_step_date(GtkWidget * entry,gint movement)337 void gsb_calendar_entry_step_date ( GtkWidget *entry,
338 gint movement )
339 {
340 GDate *date;
341 gchar *string;
342
343 /* on commence par vérifier que la date est valide */
344
345 if ( !gsb_date_check_and_complete_entry ( entry, TRUE ) )
346 return;
347
348 date = gsb_date_get_last_entry_date ( gtk_entry_get_text ( GTK_ENTRY ( entry )));
349
350 switch ( movement )
351 {
352 case ONE_DAY :
353 case ONE_WEEK :
354
355 g_date_add_days ( date, movement ) ;
356 break ;
357
358 case -ONE_DAY :
359 case -ONE_WEEK :
360
361 g_date_subtract_days ( date, -movement ) ;
362 break ;
363
364 case ONE_MONTH :
365
366 g_date_add_months ( date, 1 ) ;
367 break ;
368
369 case -ONE_MONTH :
370
371 g_date_subtract_months ( date, 1 ) ;
372 break ;
373
374 case ONE_YEAR :
375
376 g_date_add_years ( date, 1 ) ;
377 break ;
378
379 case -ONE_YEAR :
380
381 g_date_subtract_years ( date, 1 ) ;
382 break ;
383
384 default :
385 break ;
386 }
387
388 string = gsb_format_gdate (date);
389 gtk_entry_set_text ( GTK_ENTRY ( entry ), string );
390 g_date_free (date);
391 g_free (string);
392 }
393
394
395 /**
396 * callback called on focus-out on the date entry
397 * complete and check the date
398 *
399 * \param entry the entry which receive the signal
400 * \param event
401 * \param set_today TRUE (but pointer) if we want to set the current day if the entry is left empty
402 *
403 * \return FALSE
404 * */
gsb_calendar_entry_focus_out(GtkWidget * entry,GdkEventFocus * event,gint * set_today)405 gboolean gsb_calendar_entry_focus_out ( GtkWidget *entry,
406 GdkEventFocus *event,
407 gint *set_today )
408 {
409 gint valid;
410
411 valid = gsb_date_check_and_complete_entry (entry, GPOINTER_TO_INT (set_today));
412 gsb_calendar_entry_set_color ( entry, valid ) ;
413
414 return FALSE;
415 }
416
417 /**
418 * called when date changed
419 * check the date and set the entry red/invalid if not a good date
420 *
421 * \param entry
422 * \param null
423 *
424 * \return FALSE
425 * */
gsb_calendar_entry_changed(GtkWidget * entry,gpointer null)426 gboolean gsb_calendar_entry_changed ( GtkWidget *entry,
427 gpointer null )
428 {
429 GDate *date;
430
431 /* if we are in the form and the entry is empty, do nothing
432 * because it's a special style too */
433 if (gsb_form_widget_check_empty (entry))
434 return FALSE;
435
436 /* if nothing in the entry, keep the normal style */
437 if (!strlen (gtk_entry_get_text (GTK_ENTRY (entry))))
438 {
439 gsb_calendar_entry_set_color ( entry, TRUE );
440 return FALSE;
441 }
442
443 /* to check the date, we just try to see if can have a dote from the entry */
444 date = gsb_date_get_last_entry_date (gtk_entry_get_text (GTK_ENTRY (entry)));
445
446 if (date)
447 {
448 /* the date is valid, make it normal */
449 gsb_calendar_entry_set_color ( entry, TRUE );
450 g_date_free (date);
451 }
452 else
453 {
454 /* the date is not valid, make it red */
455 gsb_calendar_entry_set_color ( entry, FALSE );
456 }
457
458 return FALSE;
459 }
460
461
462 /** popup a calendar next to the entry
463 *
464 * \param entry the date entry
465 *
466 * \return a GtkWindow which contains the calendar
467 * */
gsb_calendar_entry_popup(GtkWidget * entry)468 GtkWidget *gsb_calendar_entry_popup ( GtkWidget *entry )
469 {
470 GtkWidget *popup, *pVBox, *pCalendar, *button, *frame;
471 GdkWindow *window;
472 GtkRequisition popup_size;
473 gint x, y;
474 GDate * date;
475
476 /* make the popup */
477 popup = gtk_window_new ( GTK_WINDOW_TOPLEVEL );
478 gtk_window_set_modal ( GTK_WINDOW ( popup ), TRUE );
479 gtk_window_set_transient_for ( GTK_WINDOW ( popup ),
480 GTK_WINDOW ( grisbi_app_get_active_window (NULL) ) );
481 gtk_window_set_decorated ( GTK_WINDOW ( popup ), FALSE );
482
483 /* set the decoration */
484 frame = gtk_frame_new ( NULL );
485 gtk_container_add ( GTK_CONTAINER ( popup ), frame );
486 gtk_widget_show ( frame );
487
488 pVBox = gtk_box_new ( GTK_ORIENTATION_VERTICAL, MARGIN_BOX );
489 gtk_container_set_border_width ( GTK_CONTAINER ( pVBox ), 5 );
490 gtk_container_add ( GTK_CONTAINER ( frame ), pVBox );
491 gtk_widget_show ( pVBox );
492
493 /* get the date */
494 date = gsb_calendar_entry_get_date (entry);
495 if (!date)
496 date = gdate_today ();
497
498 /* set the calendar */
499 pCalendar = gtk_calendar_new();
500 gtk_calendar_select_month ( GTK_CALENDAR ( pCalendar ),
501 g_date_get_month ( date ) - 1,
502 g_date_get_year ( date ) );
503 gtk_calendar_select_day ( GTK_CALENDAR ( pCalendar ), g_date_get_day ( date ) );
504
505 g_signal_connect ( G_OBJECT ( pCalendar ),
506 "day_selected_double_click",
507 G_CALLBACK ( gsb_calendar_entry_select_date ),
508 entry );
509 g_signal_connect ( G_OBJECT ( pCalendar ),
510 "key-press-event",
511 G_CALLBACK ( gsb_calendar_entry_calendar_key_press ),
512 entry );
513 gtk_box_pack_start ( GTK_BOX ( pVBox ),
514 pCalendar,
515 TRUE,
516 TRUE,
517 0 );
518 gtk_widget_show ( pCalendar );
519
520 /* cancel button */
521 button = gtk_button_new_with_mnemonic ( _("_Cancel") );
522 g_signal_connect_swapped ( G_OBJECT ( button ),
523 "clicked",
524 G_CALLBACK ( gtk_widget_destroy ),
525 G_OBJECT ( popup ));
526 gtk_box_pack_start ( GTK_BOX ( pVBox ),
527 button,
528 TRUE,
529 TRUE,
530 0 );
531 gtk_widget_show ( button );
532
533 /* set the position */
534 window = gtk_widget_get_window (GTK_WIDGET (entry));
535 gdk_window_get_origin (window, &x, &y );
536
537 /* on récupère la taille de la popup */
538 gtk_widget_get_preferred_size (GTK_WIDGET (popup), &popup_size, NULL);
539
540 /* pour la soustraire à la position de l'entrée date */
541 y -= popup_size.height;
542
543 #if GTK_CHECK_VERSION (3,22,0)
544 GdkDisplay *display;
545 GdkMonitor *monitor;
546 GdkRectangle rectangle;
547
548 display = gdk_window_get_display (window);
549 monitor = gdk_display_get_monitor_at_point (display, x, y);
550 gdk_monitor_get_geometry (monitor, &rectangle);
551
552 /* on décale le popup si on est trop près de bord droit de l'écran */
553 if (x > (rectangle.width - popup_size.width))
554 x = rectangle.width - popup_size.width - 10;
555 #else
556 gint screen_width = gdk_screen_width ( );
557
558 /* on décale le popup si on est trop près de bord droit de l'écran */
559 if ( x > ( screen_width - popup_size.width ) )
560 x = screen_width - popup_size.width - 10;
561 #endif
562
563 /* si une des coordonnées est négative, alors la fonction
564 gtk_window_move échoue et affiche la popup en 0,0 */
565 if ( x < 0 )
566 x = 0 ;
567
568 if ( y < 0 )
569 y = 0 ;
570
571 gtk_window_move ( GTK_WINDOW ( popup ), x, y );
572 gtk_widget_show ( popup );
573 gtk_widget_grab_focus ( GTK_WIDGET ( pCalendar ) );
574 return ( popup );
575 }
576
577
578 /**
579 * called with a double-click on a date on a calendar
580 * set the choosen date in the entry
581 *
582 * \param pCalendar
583 * \param entry
584 *
585 * \return FALSE
586 * */
gsb_calendar_entry_select_date(GtkCalendar * pCalendar,GtkWidget * entry)587 gboolean gsb_calendar_entry_select_date ( GtkCalendar *pCalendar,
588 GtkWidget *entry )
589 {
590 guint year, month, day;
591 GtkWidget *pTopLevelWidget;
592
593 /* get the popup to destroy it if we are in a popup */
594 pTopLevelWidget = gtk_widget_get_toplevel ( GTK_WIDGET ( pCalendar ) );
595
596 gtk_calendar_get_date ( pCalendar, &year, &month, &day);
597
598 gtk_entry_set_text ( GTK_ENTRY ( entry ),
599 gsb_format_date ( day, month + 1, year ));
600 gsb_form_widget_set_empty ( entry, FALSE );
601 if ( gtk_widget_is_toplevel ( pTopLevelWidget ) )
602 gtk_widget_destroy ( pTopLevelWidget );
603
604 return FALSE;
605 }
606
607
608 /**
609 * called when a calendar receive a key-press-event
610 *
611 * \param pCalendar
612 * \param ev
613 * \param entry
614 *
615 * \return TRUE to block the signal
616 * */
gsb_calendar_entry_calendar_key_press(GtkCalendar * pCalendar,GdkEventKey * ev,GtkWidget * entry)617 gboolean gsb_calendar_entry_calendar_key_press ( GtkCalendar *pCalendar,
618 GdkEventKey *ev,
619 GtkWidget *entry )
620 {
621 guint day, month, year;
622 GDate *date;
623 GtkWidget *pTopLevelWidget;
624
625 /* get the popup to destroy it if need */
626 pTopLevelWidget = gtk_widget_get_toplevel ( GTK_WIDGET ( pCalendar ) );
627
628 /* most of the time, we will use date so can get it here,
629 * think about free it if not used */
630 gtk_calendar_get_date ( pCalendar, &year, &month, &day );
631 month++;
632 date = g_date_new_dmy (day, month, year);
633
634 switch ( ev -> keyval )
635 {
636 case GDK_KEY_Escape :
637 /* just close the calendar if it's a popup */
638 if ( gtk_widget_is_toplevel ( pTopLevelWidget ) )
639 gtk_widget_destroy ( pTopLevelWidget );
640 g_date_free (date);
641 return TRUE;
642 break ;
643
644 case GDK_KEY_Return :
645 case GDK_KEY_KP_Enter :
646 /* get the date an close the calendar */
647 gtk_entry_set_text ( GTK_ENTRY ( entry ),
648 gsb_format_date ( day, month, year ));
649 if ( gtk_widget_is_toplevel ( pTopLevelWidget ) )
650 gtk_widget_destroy ( pTopLevelWidget );
651 g_date_free (date);
652 return TRUE;
653 break ;
654
655 /* from now, it will change date so just use date, modify it and fill day, month, year
656 * we will set the calendar at the end of that function
657 * so after now, only keys which change the date */
658 case GDK_KEY_Left :
659 case GDK_KEY_KP_Left:
660 case GDK_KEY_minus:
661 case GDK_KEY_KP_Subtract:
662 /* day before */
663 g_date_subtract_days (date, 1);
664 break ;
665
666 case GDK_KEY_Right :
667 case GDK_KEY_KP_Right:
668 case GDK_KEY_plus:
669 case GDK_KEY_KP_Add:
670 /* day after */
671 g_date_add_days (date, 1);
672 break ;
673
674 case GDK_KEY_Up :
675 case GDK_KEY_KP_Up :
676 /* prev week */
677 g_date_subtract_days (date, 7);
678 break ;
679
680 case GDK_KEY_Down :
681 case GDK_KEY_KP_Down :
682 /* next week */
683 g_date_add_days (date, 7);
684 break ;
685
686 case GDK_KEY_Home :
687 case GDK_KEY_KP_Home :
688 /* go to first day of the month */
689 g_date_set_day (date, 1);
690 break ;
691
692 case GDK_KEY_End :
693 case GDK_KEY_KP_End :
694 /* go to last day of the month */
695 g_date_set_day (date,
696 g_date_get_days_in_month (month, year));
697 break ;
698
699 case GDK_KEY_Page_Up :
700 case GDK_KEY_KP_Page_Up :
701 /* prev month */
702 g_date_subtract_months (date, 1);
703 break ;
704
705 case GDK_KEY_Page_Down :
706 case GDK_KEY_KP_Page_Down :
707 /* next month */
708 g_date_add_months (date, 1);
709 break ;
710
711 default:
712 return TRUE;
713 }
714
715 day = g_date_get_day (date);
716 month = g_date_get_month (date);
717 year = g_date_get_year (date);
718 g_date_free (date);
719
720 /* to avoid a warning */
721 gtk_calendar_select_day( pCalendar , 15 );
722
723 month--;
724 gtk_calendar_select_month ( pCalendar , month, year );
725 gtk_calendar_select_day( pCalendar , day );
726 return TRUE;
727 }
728
729
730 /* Local Variables: */
731 /* c-basic-offset: 4 */
732 /* End: */
733