1 /*
2 *  RAL -- Rubrica Addressbook Library
3 *  file: ref.c
4 *
5 *  Copyright (C) Nicola Fragale <nicolafragale@libero.it>
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 #include <gtk/gtk.h>
23 #include <glib/gi18n-lib.h>
24 #include <gdk/gdkkeysyms.h>
25 
26 #include "calendar.h"
27 
28 #define BUFSIZE 64
29 
30 enum {
31   PROP_0,
32   CALENDAR_DAY,
33   CALENDAR_MONTH,
34   CALENDAR_YEAR
35 };
36 
37 
38 /*    signals enumeration
39  */
40 enum {
41   CALENDAR_CHANGE,
42   LAST_SIGNAL
43 };
44 
45 
46 struct _RubricaCalendarPrivate {
47   GtkWidget* entry;
48   GtkWidget* button;
49   GtkWidget* popup;
50   GtkWidget* calendar;
51 
52   GDate* date;
53 
54   gint day;
55   gint month;
56   gint year;
57 
58   gboolean dispose_has_run;
59 };
60 
61 
62 static guint calendar_signals[LAST_SIGNAL] = {0};
63 
64 
65 #define RUBRICA_CALENDAR_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE((o),  \
66                                          RUBRICA_CALENDAR_TYPE,            \
67                                          RubricaCalendarPrivate))
68 
69 
70 static GObjectClass *parent_class = NULL;
71 
72 
73 static void rubrica_calendar_class_init   (RubricaCalendarClass* klass);
74 static void rubrica_calendar_init         (RubricaCalendar* obj);
75 
76 static void rubrica_calendar_finalize     (RubricaCalendar* self);
77 static void rubrica_calendar_dispose      (RubricaCalendar* self);
78 
79 
80 static void rubrica_calendar_set_property (GObject* obj, guint property_id,
81 					   GValue* value,  GParamSpec* spec);
82 static void rubrica_calendar_get_property (GObject* obj, guint property_id,
83 					   GValue* value, GParamSpec* spec);
84 
85 
86 static gboolean date_is_valid          (RubricaCalendar* calendar);
87 
88 static void     update_calendar        (RubricaCalendar *calendar);
89 static void     hide_popup             (RubricaCalendar *calendar);
90 static gboolean popup_grab_on_window   (GdkWindow *window,
91 					guint32 activate_time);
92 
93 static void     on_day_selected        (GtkCalendar *calendar, gpointer data);
94 static void     on_day_selected_double (GtkCalendar *calendar, gpointer data);
95 static gint     on_popup_key_press     (GtkWidget *widget, GdkEventKey *event,
96 					gpointer data);
97 static gint     on_popup_button_press  (GtkWidget *widget,
98 					GdkEventButton *event, gpointer data);
99 static void     on_button_toggled_cb   (GtkToggleButton *togglebutton,
100 					gpointer data);
101 
102 
103 static gboolean
date_is_valid(RubricaCalendar * calendar)104 date_is_valid(RubricaCalendar* calendar)
105 {
106   RubricaCalendarPrivate *priv;
107 
108   g_return_val_if_fail(IS_RUBRICA_CALENDAR(calendar), FALSE);
109 
110   priv = RUBRICA_CALENDAR_GET_PRIVATE (calendar);
111 
112   if (priv->day && (priv->month) && priv->year)
113     return TRUE;
114 
115   return FALSE;
116 }
117 
118 
119 static void
update_calendar(RubricaCalendar * calendar)120 update_calendar (RubricaCalendar *calendar)
121 {
122   RubricaCalendarPrivate *priv = RUBRICA_CALENDAR_GET_PRIVATE (calendar);
123   gchar buffer[BUFSIZE];
124 
125   if (date_is_valid(calendar))
126     {
127       g_date_set_dmy(priv->date, priv->day, priv->month, priv->year);
128 
129       g_date_strftime(buffer, BUFSIZE, "%Ex", priv->date);
130       gtk_entry_set_text(GTK_ENTRY(priv->entry), buffer);
131 
132       g_signal_emit_by_name(calendar, "calendar-change",
133 			    priv->date, G_TYPE_POINTER);
134     }
135   else
136     {
137       gtk_entry_set_text(GTK_ENTRY(priv->entry), _("unknown"));
138     }
139 }
140 
141 
142 static void
hide_popup(RubricaCalendar * calendar)143 hide_popup (RubricaCalendar *calendar)
144 {
145   RubricaCalendarPrivate *priv;
146 
147   priv = RUBRICA_CALENDAR_GET_PRIVATE (calendar);
148 
149   gtk_widget_hide (priv->popup);
150   gtk_grab_remove (priv->popup);
151   gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (priv->button), FALSE);
152 }
153 
154 
155 static gboolean
popup_grab_on_window(GdkWindow * window,guint32 activate_time)156 popup_grab_on_window (GdkWindow *window, guint32 activate_time)
157 {
158   if ((gdk_pointer_grab(window, TRUE,
159 			GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK |
160 			GDK_POINTER_MOTION_MASK,
161 			NULL, NULL, activate_time) == 0))
162     {
163       if (gdk_keyboard_grab (window, TRUE, activate_time) == 0)
164 	return TRUE;
165       else
166 	{
167 	  gdk_pointer_ungrab (activate_time);
168 	  return FALSE;
169 	}
170     }
171 
172   return FALSE;
173 }
174 
175 
176 static void
on_day_selected(GtkCalendar * cal,gpointer data)177 on_day_selected (GtkCalendar *cal, gpointer data)
178 {
179   RubricaCalendar *calendar = (RubricaCalendar *) data;
180   RubricaCalendarPrivate *priv = RUBRICA_CALENDAR_GET_PRIVATE (data);
181   guint day, month, year;
182 
183   gtk_calendar_get_date (cal, &year, &month, &day);
184 
185   priv->day   = day;
186   priv->month = month + 1;
187   priv->year  = year;
188 
189   update_calendar(RUBRICA_CALENDAR(calendar));
190 }
191 
192 
193 static void
on_day_selected_double(GtkCalendar * calendar,gpointer data)194 on_day_selected_double (GtkCalendar *calendar, gpointer data)
195 {
196   hide_popup(RUBRICA_CALENDAR(data));
197 }
198 
199 
200 static gint
on_delete_popup(GtkWidget * widget,gpointer data)201 on_delete_popup (GtkWidget *widget, gpointer data)
202 {
203   hide_popup(RUBRICA_CALENDAR(data));
204 
205   return TRUE;
206 }
207 
208 
209 static gint
on_popup_key_press(GtkWidget * widget,GdkEventKey * event,gpointer data)210 on_popup_key_press (GtkWidget *widget, GdkEventKey *event, gpointer data)
211 {
212   /* evaluate event so calendar is navigable with keyboard */
213   switch (event->keyval)
214     {
215     case GDK_Escape:
216       break;
217 
218     case GDK_Return:
219     case GDK_KP_Enter:
220       break;
221 
222     default:
223       return FALSE;
224     }
225 
226   g_signal_stop_emission_by_name (widget, "key_press_event");
227   hide_popup (RUBRICA_CALENDAR(data));
228 
229   return TRUE;
230 }
231 
232 
233 
234 /* This function is yanked from gtkcombo.c
235 */
236 static gint
on_popup_button_press(GtkWidget * widget,GdkEventButton * event,gpointer data)237 on_popup_button_press (GtkWidget *widget, GdkEventButton *event, gpointer data)
238 {
239   GtkWidget *child;
240 
241   /* We don't ask for button press events on the grab widget, so
242    *  if an event is reported directly to the grab widget, it must
243    *  be on a window outside the application (and thus we remove
244    *  the popup window). Otherwise, we check if the widget is a child
245    *  of the grab widget, and only remove the popup window if it
246    *  is not.
247    */
248   child = gtk_get_event_widget ((GdkEvent *)event);
249 
250   if (child != widget)
251     {
252       while (child)
253 	{
254 	  if (child == widget) return FALSE;
255 	  child = child->parent;
256       }
257     }
258 
259   hide_popup (RUBRICA_CALENDAR(data));
260 
261   return TRUE;
262 }
263 
264 /*
265   gtk_calendar_select_month (GtkCalendar *calendar, guint month, guint year);
266 
267   Shifts the calendar to a different month.
268   ...
269 
270   month : a month number between 0 and 11. <==
271 
272   typedef enum
273   {
274      G_DATE_BAD_MONTH = 0,
275      G_DATE_JANUARY   = 1,
276      G_DATE_FEBRUARY  = 2,
277      G_DATE_MARCH     = 3,
278      G_DATE_APRIL     = 4,
279      G_DATE_MAY       = 5,
280      G_DATE_JUNE      = 6,
281      G_DATE_JULY      = 7,
282      G_DATE_AUGUST    = 8,
283      G_DATE_SEPTEMBER = 9,
284      G_DATE_OCTOBER   = 10,
285      G_DATE_NOVEMBER  = 11,
286      G_DATE_DECEMBER  = 12
287   } GDateMonth;
288 */
289 static void
on_button_toggled_cb(GtkToggleButton * togglebutton,gpointer data)290 on_button_toggled_cb (GtkToggleButton *togglebutton, gpointer data)
291 {
292   RubricaCalendar *calendar = (RubricaCalendar *)data;
293   RubricaCalendarPrivate *priv = RUBRICA_CALENDAR_GET_PRIVATE (calendar);
294 
295   gint x, y, width, height;
296   GtkRequisition req;
297 
298   if (gtk_toggle_button_get_active (togglebutton))
299     {
300       if (date_is_valid(calendar))
301 	{
302 	  /* subtract 1 from priv->month (GDateMonth) and align to
303 	     gtk_calendar_select_month needs
304 	  */
305 	  gtk_calendar_select_month (GTK_CALENDAR (priv->calendar),
306 				     priv->month - 1, priv->year);
307 	  gtk_calendar_select_day (GTK_CALENDAR (priv->calendar), priv->day);
308 	}
309 
310       /* show calendar
311        */
312       gtk_widget_size_request (priv->popup, &req);
313       gdk_window_get_origin (GDK_WINDOW (priv->button->window), &x, &y);
314       x += priv->button->allocation.x;
315       y += priv->button->allocation.y;
316       width = priv->button->allocation.width;
317       height = priv->button->allocation.height;
318       x += width - req.width;
319       y += height;
320       if (x < 0) x = 0;
321       if (y < 0) y = 0;
322 
323       gtk_grab_add (priv->popup);
324       gtk_window_move (GTK_WINDOW (priv->popup), x, y);
325       gtk_widget_show (priv->popup);
326       gtk_widget_grab_focus (priv->calendar);
327       popup_grab_on_window (priv->popup->window,
328 			    gtk_get_current_event_time ());
329     }
330 }
331 
332 
333 
334 
335 GType
rubrica_calendar_get_type()336 rubrica_calendar_get_type()
337 {
338   static GType calendar_type = 0;
339 
340   if (!calendar_type)
341     {
342       static const GTypeInfo calendar_info =
343 	{
344 	  sizeof(RubricaCalendarClass),
345 	  NULL,
346 	  NULL,
347 	  (GClassInitFunc) rubrica_calendar_class_init,
348 	  NULL,
349 	  NULL,
350 	  sizeof(RubricaCalendar),
351 	  0,
352 	  (GInstanceInitFunc) rubrica_calendar_init
353 	};
354 
355       calendar_type = g_type_register_static (GTK_TYPE_HBOX,
356 					      "RubricaCalendar",
357 					      &calendar_info, 0);
358     }
359 
360   return calendar_type;
361 }
362 
363 
364 static void
rubrica_calendar_dispose(RubricaCalendar * self)365 rubrica_calendar_dispose (RubricaCalendar* self)
366 {
367   RubricaCalendarPrivate* priv;
368 
369   g_return_if_fail(IS_RUBRICA_CALENDAR(self));
370 
371   priv = RUBRICA_CALENDAR_GET_PRIVATE(self);
372 
373   if (priv->dispose_has_run)
374     return;
375 
376   priv->dispose_has_run = TRUE;
377 }
378 
379 
380 static void
rubrica_calendar_finalize(RubricaCalendar * self)381 rubrica_calendar_finalize (RubricaCalendar* self)
382 {
383   g_return_if_fail(IS_RUBRICA_CALENDAR(self));
384 }
385 
386 
387 static void
rubrica_calendar_class_init(RubricaCalendarClass * klass)388 rubrica_calendar_class_init(RubricaCalendarClass* klass)
389 {
390   GObjectClass *object_class = G_OBJECT_CLASS (klass);
391   GParamSpec* pspec;
392 
393   parent_class = g_type_class_peek_parent (klass);
394 
395   object_class->finalize     = (GObjectFinalizeFunc) rubrica_calendar_finalize;
396   object_class->dispose      = (GObjectFinalizeFunc) rubrica_calendar_dispose;
397 
398   object_class->set_property = (gpointer) rubrica_calendar_set_property;
399   object_class->get_property = (gpointer) rubrica_calendar_get_property;
400 
401   g_type_class_add_private (klass, sizeof(RubricaCalendarPrivate));
402 
403   /**
404    */
405   calendar_signals[CALENDAR_CHANGE] =
406     g_signal_new("calendar-change",
407 		 RUBRICA_CALENDAR_TYPE,
408 		 G_SIGNAL_RUN_LAST,
409 		 G_STRUCT_OFFSET(RubricaCalendarClass, calendar_change),
410 		 NULL,
411 		 NULL,
412 		 g_cclosure_marshal_VOID__POINTER,
413 		 G_TYPE_NONE,            /* return type */
414 		 1,                      /* params      */
415 		 G_TYPE_POINTER);        /* params type: error code */
416 
417   pspec = g_param_spec_int("calendar-day",
418 			   "day",
419 			   "day",
420 			   0,      // min
421 			   31,     // max
422 			   0,      // default, invalid day
423 			   G_PARAM_READWRITE);
424   g_object_class_install_property(object_class, CALENDAR_DAY, pspec);
425 
426   pspec = g_param_spec_int("calendar-month",
427 			   "month",
428 			   "month",
429 			   0,       // min
430 			   12,      // max
431 			   0,       // default, invalid month
432 			   G_PARAM_READWRITE);
433   g_object_class_install_property(object_class, CALENDAR_MONTH, pspec);
434 
435   pspec = g_param_spec_int("calendar-year",
436 			   "year",
437 			   "year",
438 			   0,        // min
439 			   10000,    // max
440 			   0,        // default, invalid year
441 			   G_PARAM_READWRITE);
442   g_object_class_install_property(object_class, CALENDAR_YEAR, pspec);
443 }
444 
445 
446 static void
rubrica_calendar_init(RubricaCalendar * self)447 rubrica_calendar_init(RubricaCalendar* self)
448 {
449   GtkWidget* frame;
450   GtkWidget* arrow;
451   GtkIconTheme* theme;
452   GtkWidget* image;
453   GdkPixbuf* pixbuf;
454   RubricaCalendarPrivate* priv;
455 
456   priv = RUBRICA_CALENDAR_GET_PRIVATE(self);
457 
458   gtk_box_set_homogeneous(GTK_BOX(self), FALSE);
459 
460   theme  = gtk_icon_theme_get_default();
461   pixbuf = gtk_icon_theme_load_icon(theme, "stock_calendar",
462 				    GTK_ICON_SIZE_BUTTON,
463 				    GTK_ICON_LOOKUP_USE_BUILTIN, NULL);
464 
465   priv->date = g_date_new();
466   g_date_clear (priv->date, 1);
467   g_date_set_time_t(priv->date, time(NULL));
468 
469   priv->day      = 0;
470   priv->month    = 0;
471   priv->year     = 0;
472   priv->entry    = gtk_entry_new();
473   priv->button   = gtk_toggle_button_new ();
474   priv->popup    = gtk_window_new (GTK_WINDOW_POPUP);
475   priv->calendar = gtk_calendar_new ();
476 
477   gtk_entry_set_editable(GTK_ENTRY(priv->entry), FALSE);
478   gtk_entry_set_text(GTK_ENTRY(priv->entry), _("unknown"));
479   gtk_box_pack_start (GTK_BOX (self), priv->entry, TRUE, TRUE, 0);
480   gtk_widget_show (priv->entry);
481 
482   gtk_box_pack_start (GTK_BOX (self), priv->button, FALSE, FALSE, 0);
483   if (pixbuf)
484     {
485       image = gtk_image_new_from_pixbuf(pixbuf);
486       gtk_button_set_image(GTK_BUTTON(priv->button), image);
487       gtk_button_set_relief(GTK_BUTTON(priv->button), GTK_RELIEF_NONE);
488       gtk_widget_show (image);
489 
490       gdk_pixbuf_unref (pixbuf);
491     }
492   else
493     {
494       arrow = (GtkWidget *) gtk_arrow_new (GTK_ARROW_DOWN, GTK_SHADOW_NONE);
495       gtk_container_add (GTK_CONTAINER (priv->button), arrow);
496       gtk_widget_show (arrow);
497     }
498   gtk_widget_show (priv->button);
499 
500   frame = gtk_frame_new(NULL);
501   gtk_frame_set_shadow_type(GTK_FRAME(frame), GTK_SHADOW_ETCHED_IN);
502   gtk_widget_show(frame);
503 
504   gtk_window_set_resizable (GTK_WINDOW (priv->popup), FALSE);
505   gtk_container_add(GTK_CONTAINER(priv->popup), frame);
506   gtk_widget_set_events (priv->popup, gtk_widget_get_events (priv->popup) |
507 			 GDK_KEY_PRESS_MASK);
508 
509   gtk_container_add (GTK_CONTAINER (frame), priv->calendar);
510   gtk_widget_show (priv->calendar);
511 
512   g_signal_connect (G_OBJECT(priv->button), "toggled",
513 		    G_CALLBACK (on_button_toggled_cb), self);
514 
515   g_signal_connect (priv->popup, "delete_event",
516 		    G_CALLBACK (on_delete_popup), self);
517   g_signal_connect (priv->popup, "key_press_event",
518 		    G_CALLBACK (on_popup_key_press), self);
519   g_signal_connect (priv->popup, "button_press_event",
520 		    G_CALLBACK (on_popup_button_press), self);
521 
522   g_signal_connect (G_OBJECT (priv->calendar), "day-selected",
523 		    G_CALLBACK (on_day_selected), self);
524   g_signal_connect (G_OBJECT (priv->calendar), "day-selected-double-click",
525 		    G_CALLBACK (on_day_selected_double), self);
526 
527   priv->dispose_has_run = FALSE;
528 }
529 
530 
531 static void
rubrica_calendar_set_property(GObject * obj,guint property_id,GValue * value,GParamSpec * spec)532 rubrica_calendar_set_property (GObject* obj, guint property_id,
533 			       GValue* value, GParamSpec* spec)
534 {
535   RubricaCalendar* self = (RubricaCalendar*) obj;
536   RubricaCalendarPrivate* priv = RUBRICA_CALENDAR_GET_PRIVATE(self);
537 
538   switch (property_id)
539     {
540     case CALENDAR_DAY:
541       priv->day = g_value_get_int(value);
542       break;
543 
544     case CALENDAR_MONTH:
545       priv->month = g_value_get_int(value);
546       break;
547 
548     case CALENDAR_YEAR:
549       priv->year = g_value_get_int(value);
550       break;
551 
552     default:
553       break;
554     }
555 
556   update_calendar(self);
557 }
558 
559 
560 static void
rubrica_calendar_get_property(GObject * obj,guint property_id,GValue * value,GParamSpec * spec)561 rubrica_calendar_get_property (GObject* obj, guint property_id,
562 			       GValue* value, GParamSpec* spec)
563 {
564   RubricaCalendar* self = (RubricaCalendar*) obj;
565   RubricaCalendarPrivate* priv = RUBRICA_CALENDAR_GET_PRIVATE(self);
566 
567   switch (property_id)
568     {
569     case CALENDAR_DAY:
570       g_value_set_int(value, priv->day);
571       break;
572 
573     case CALENDAR_MONTH:
574       g_value_set_int(value, priv->month);
575       break;
576 
577     case CALENDAR_YEAR:
578       g_value_set_int(value, priv->year);
579       break;
580 
581     default:
582       break;
583     }
584 }
585 
586 
587 
588 
589 /*   Public
590 */
591 /**
592  * rubrica_calendar_new
593  *
594  * create a #RubricaCalendar
595  *
596  * returns: a new #RubricaCalendar*
597  */
598 GtkWidget*
rubrica_calendar_new()599 rubrica_calendar_new()
600 {
601   GtkWidget* calendar;
602 
603   calendar = GTK_WIDGET(g_object_new(RUBRICA_CALENDAR_TYPE, NULL));
604 
605   return calendar;
606 }
607 
608 
609 GtkWidget*
rubrica_calendar_get_button(RubricaCalendar * cal)610 rubrica_calendar_get_button (RubricaCalendar *cal)
611 {
612   RubricaCalendarPrivate* priv;
613 
614   g_return_val_if_fail(IS_RUBRICA_CALENDAR(cal), NULL);
615 
616   priv = RUBRICA_CALENDAR_GET_PRIVATE(cal);
617 
618   return priv->button;
619 }
620 
621 gboolean
rubrica_calendar_has_date(RubricaCalendar * cal)622 rubrica_calendar_has_date  (RubricaCalendar *cal)
623 {
624   g_return_val_if_fail(IS_RUBRICA_CALENDAR(cal), FALSE);
625 
626   return date_is_valid(cal);
627 }
628 
629 
630 GDate*
rubrica_calendar_get_gdate(RubricaCalendar * cal)631 rubrica_calendar_get_gdate (RubricaCalendar *cal)
632 {
633   RubricaCalendarPrivate *priv;
634 
635   g_return_val_if_fail(IS_RUBRICA_CALENDAR(cal), NULL);
636 
637   priv = RUBRICA_CALENDAR_GET_PRIVATE (cal);
638 
639   if (date_is_valid(cal))
640     return priv->date;
641 
642   return NULL;
643 }
644 
645 
646 gchar*
rubrica_calendar_get_date(RubricaCalendar * cal)647 rubrica_calendar_get_date (RubricaCalendar *cal)
648 {
649   RubricaCalendarPrivate *priv;
650   gchar buffer[BUFSIZE];
651 
652   g_return_val_if_fail(IS_RUBRICA_CALENDAR(cal), NULL);
653 
654   priv = RUBRICA_CALENDAR_GET_PRIVATE (cal);
655 
656   if (date_is_valid(cal))
657     {
658       g_date_set_dmy(priv->date, priv->day, priv->month, priv->year);
659       g_date_strftime(buffer, BUFSIZE, "%Ex", priv->date);
660 
661       return g_strdup_printf("%s", buffer);
662     }
663 
664   return NULL;
665 }
666 
667 
668 void
rubrica_calendar_clean(RubricaCalendar * calendar)669 rubrica_calendar_clean(RubricaCalendar* calendar)
670 {
671   RubricaCalendarPrivate *priv;
672 
673   g_return_if_fail(IS_RUBRICA_CALENDAR(calendar));
674 
675   priv = RUBRICA_CALENDAR_GET_PRIVATE (calendar);
676 
677   priv->day   = 0;
678   priv->month = 0;
679   priv->year  = 0;
680 
681   update_calendar (calendar);
682 }
683