]> Pileus Git - ~andy/gtk/blobdiff - gtk/gtkcalendar.c
Bug 526987 - GtkCellRendererCombo should allow model to be NULL
[~andy/gtk] / gtk / gtkcalendar.c
index 1b6f0f18e759e8981ffb199e372ed7dec0060067..9db1726fb9b2b5d2d9b9355afee8836242b7fce8 100644 (file)
@@ -54,6 +54,7 @@
 #include "gtkintl.h"
 #include "gtkmain.h"
 #include "gtkmarshalers.h"
+#include "gtktooltip.h"
 #include "gtkprivate.h"
 #include "gdk/gdkkeysyms.h"
 #include "gtkalias.h"
@@ -191,10 +192,10 @@ dates_difference(guint year1, guint mm1, guint dd1,
 #define HEADER_BG_COLOR(widget)                 (& (widget)->style->bg[GTK_WIDGET_STATE (widget)])
 #define SELECTED_BG_COLOR(widget)       (& (widget)->style->base[GTK_WIDGET_HAS_FOCUS (widget) ? GTK_STATE_SELECTED : GTK_STATE_ACTIVE])
 #define SELECTED_FG_COLOR(widget)       (& (widget)->style->text[GTK_WIDGET_HAS_FOCUS (widget) ? GTK_STATE_SELECTED : GTK_STATE_ACTIVE])
-#define NORMAL_DAY_COLOR(widget)        (& (widget)->style->fg[GTK_WIDGET_STATE (widget)])
+#define NORMAL_DAY_COLOR(widget)        (& (widget)->style->text[GTK_WIDGET_STATE (widget)])
 #define PREV_MONTH_COLOR(widget)        (& (widget)->style->mid[GTK_WIDGET_STATE (widget)])
 #define NEXT_MONTH_COLOR(widget)        (& (widget)->style->mid[GTK_WIDGET_STATE (widget)])
-#define MARKED_COLOR(widget)            (& (widget)->style->fg[GTK_WIDGET_STATE (widget)])
+#define MARKED_COLOR(widget)            (& (widget)->style->text[GTK_WIDGET_STATE (widget)])
 #define BACKGROUND_COLOR(widget)        (& (widget)->style->base[GTK_WIDGET_STATE (widget)])
 #define HIGHLIGHT_BACK_COLOR(widget)    (& (widget)->style->mid[GTK_WIDGET_STATE (widget)])
 
@@ -232,7 +233,9 @@ enum
   PROP_SHOW_DAY_NAMES,
   PROP_NO_MONTH_CHANGE,
   PROP_SHOW_WEEK_NUMBERS,
-  PROP_LAST
+  PROP_SHOW_DETAILS,
+  PROP_DETAIL_WIDTH_CHARS,
+  PROP_DETAIL_HEIGHT_ROWS
 };
 
 static guint gtk_calendar_signals[LAST_SIGNAL] = { 0 };
@@ -280,6 +283,16 @@ struct _GtkCalendarPrivate
 
   gint drag_start_x;
   gint drag_start_y;
+
+  /* Optional callback, used to display extra information for each day. */
+  GtkCalendarDetailFunc detail_func;
+  gpointer              detail_func_user_data;
+  GDestroyNotify        detail_func_destroy;
+
+  /* Size requistion for details provided by the hook. */
+  gint detail_height_rows;
+  gint detail_width_chars;
+  gint detail_overflow[6];
 };
 
 #define GTK_CALENDAR_GET_PRIVATE(widget)  (GTK_CALENDAR (widget)->priv)
@@ -325,6 +338,11 @@ static void     gtk_calendar_state_changed  (GtkWidget        *widget,
                                             GtkStateType      previous_state);
 static void     gtk_calendar_style_set      (GtkWidget        *widget,
                                             GtkStyle         *previous_style);
+static gboolean gtk_calendar_query_tooltip  (GtkWidget        *widget,
+                                            gint              x,
+                                            gint              y,
+                                            gboolean          keyboard_mode,
+                                            GtkTooltip       *tooltip);
 
 static void     gtk_calendar_drag_data_get      (GtkWidget        *widget,
                                                 GdkDragContext   *context,
@@ -404,6 +422,7 @@ gtk_calendar_class_init (GtkCalendarClass *class)
   widget_class->state_changed = gtk_calendar_state_changed;
   widget_class->grab_notify = gtk_calendar_grab_notify;
   widget_class->focus_out_event = gtk_calendar_focus_out;
+  widget_class->query_tooltip = gtk_calendar_query_tooltip;
 
   widget_class->drag_data_get = gtk_calendar_drag_data_get;
   widget_class->drag_motion = gtk_calendar_drag_motion;
@@ -411,6 +430,12 @@ gtk_calendar_class_init (GtkCalendarClass *class)
   widget_class->drag_drop = gtk_calendar_drag_drop;
   widget_class->drag_data_received = gtk_calendar_drag_data_received;
   
+  /**
+   * GtkCalendar:year:
+   *
+   * The selected year. 
+   * This property gets initially set to the current year.
+   */  
   g_object_class_install_property (gobject_class,
                                    PROP_YEAR,
                                    g_param_spec_int ("year",
@@ -418,6 +443,13 @@ gtk_calendar_class_init (GtkCalendarClass *class)
                                                     P_("The selected year"),
                                                     0, G_MAXINT, 0,
                                                     GTK_PARAM_READWRITE));
+
+  /**
+   * GtkCalendar:month:
+   *
+   * The selected month (as a number between 0 and 11). 
+   * This property gets initially set to the current month.
+   */
   g_object_class_install_property (gobject_class,
                                    PROP_MONTH,
                                    g_param_spec_int ("month",
@@ -425,6 +457,14 @@ gtk_calendar_class_init (GtkCalendarClass *class)
                                                     P_("The selected month (as a number between 0 and 11)"),
                                                     0, 11, 0,
                                                     GTK_PARAM_READWRITE));
+
+  /**
+   * GtkCalendar:day:
+   *
+   * The selected day (as a number between 1 and 31, or 0 
+   * to unselect the currently selected day).
+   * This property gets initially set to the current day.
+   */
   g_object_class_install_property (gobject_class,
                                    PROP_DAY,
                                    g_param_spec_int ("day",
@@ -492,6 +532,55 @@ gtk_calendar_class_init (GtkCalendarClass *class)
                                                         FALSE,
                                                         GTK_PARAM_READWRITE));
 
+/**
+ * GtkCalendar:detail-width-chars:
+ *
+ * Width of a detail cell, in characters.
+ * A value of 0 allows any width. See gtk_calendar_set_detail_func().
+ *
+ * Since: 2.14
+ */
+  g_object_class_install_property (gobject_class,
+                                   PROP_DETAIL_WIDTH_CHARS,
+                                   g_param_spec_int ("detail-width-chars",
+                                                    P_("Details Width"),
+                                                    P_("Details width in characters"),
+                                                    0, 127, 0,
+                                                    GTK_PARAM_READWRITE));
+
+/**
+ * GtkCalendar:detail-height-rows:
+ *
+ * Height of a detail cell, in rows.
+ * A value of 0 allows any width. See gtk_calendar_set_detail_func().
+ *
+ * Since: 2.14
+ */
+  g_object_class_install_property (gobject_class,
+                                   PROP_DETAIL_HEIGHT_ROWS,
+                                   g_param_spec_int ("detail-height-rows",
+                                                    P_("Details Height"),
+                                                    P_("Details height in rows"),
+                                                    0, 127, 0,
+                                                    GTK_PARAM_READWRITE));
+
+/**
+ * GtkCalendar:show-details:
+ *
+ * Determines whether details are shown directly in the widget, or if they are
+ * available only as tooltip. When this property is set days with details are
+ * marked.
+ *
+ * Since: 2.14
+ */
+  g_object_class_install_property (gobject_class,
+                                   PROP_SHOW_DETAILS,
+                                   g_param_spec_boolean ("show-details",
+                                                        P_("Show Details"),
+                                                        P_("If TRUE, details are shown"),
+                                                        TRUE,
+                                                        GTK_PARAM_READWRITE));
+
   gtk_calendar_signals[MONTH_CHANGED_SIGNAL] =
     g_signal_new (I_("month_changed"),
                  G_OBJECT_CLASS_TYPE (gobject_class),
@@ -568,7 +657,7 @@ gtk_calendar_init (GtkCalendar *calendar)
   GtkCalendarPrivate *priv;
   gchar *year_before;
 #ifdef HAVE__NL_TIME_FIRST_WEEKDAY
-  gchar *langinfo;
+  union { unsigned int word; char *string; } langinfo;
   gint week_1stday = 0;
   gint first_weekday = 1;
   guint week_origin;
@@ -625,8 +714,9 @@ gtk_calendar_init (GtkCalendar *calendar)
   calendar->num_marked_dates = 0;
   calendar->selected_day = tm->tm_mday;
   
-  calendar->display_flags = ( GTK_CALENDAR_SHOW_HEADING | 
-                             GTK_CALENDAR_SHOW_DAY_NAMES );
+  calendar->display_flags = (GTK_CALENDAR_SHOW_HEADING |
+                            GTK_CALENDAR_SHOW_DAY_NAMES |
+                            GTK_CALENDAR_SHOW_DETAILS);
   
   calendar->highlight_row = -1;
   calendar->highlight_col = -1;
@@ -688,10 +778,10 @@ gtk_calendar_init (GtkCalendar *calendar)
     }
 #else
 #ifdef HAVE__NL_TIME_FIRST_WEEKDAY
-  langinfo = nl_langinfo (_NL_TIME_FIRST_WEEKDAY);
-  first_weekday = langinfo[0];
-  langinfo = nl_langinfo (_NL_TIME_WEEK_1STDAY);
-  week_origin = GPOINTER_TO_INT (langinfo);
+  langinfo.string = nl_langinfo (_NL_TIME_FIRST_WEEKDAY);
+  first_weekday = langinfo.string[0];
+  langinfo.string = nl_langinfo (_NL_TIME_WEEK_1STDAY);
+  week_origin = langinfo.word;
   if (week_origin == 19971130) /* Sunday */
     week_1stday = 0;
   else if (week_origin == 19971201) /* Monday */
@@ -728,13 +818,24 @@ gtk_calendar_init (GtkCalendar *calendar)
  *          Utility Functions           *
  ****************************************/
 
+static void
+calendar_queue_refresh (GtkCalendar *calendar)
+{
+  GtkCalendarPrivate *priv = GTK_CALENDAR_GET_PRIVATE (calendar);
+
+  if (!(priv->detail_func) ||
+      !(calendar->display_flags & GTK_CALENDAR_SHOW_DETAILS) ||
+       (priv->detail_width_chars && priv->detail_height_rows))
+    gtk_widget_queue_draw (GTK_WIDGET (calendar));
+  else
+    gtk_widget_queue_resize (GTK_WIDGET (calendar));
+}
+
 static void
 calendar_set_month_next (GtkCalendar *calendar)
 {
   gint month_len;
-  
-  g_return_if_fail (GTK_IS_WIDGET (calendar));
-  
+
   if (calendar->display_flags & GTK_CALENDAR_NO_MONTH_CHANGE)
     return;
   
@@ -765,16 +866,14 @@ calendar_set_month_next (GtkCalendar *calendar)
   else
     gtk_calendar_select_day (calendar, calendar->selected_day);
 
-  gtk_widget_queue_draw (GTK_WIDGET (calendar));
+  calendar_queue_refresh (calendar);
 }
 
 static void
 calendar_set_year_prev (GtkCalendar *calendar)
 {
   gint month_len;
-  
-  g_return_if_fail (GTK_IS_WIDGET (calendar));
-  
+
   calendar->year--;
   calendar_compute_days (calendar);
   g_signal_emit (calendar,
@@ -794,16 +893,14 @@ calendar_set_year_prev (GtkCalendar *calendar)
   else
     gtk_calendar_select_day (calendar, calendar->selected_day);
   
-  gtk_widget_queue_draw (GTK_WIDGET (calendar));
+  calendar_queue_refresh (calendar);
 }
 
 static void
 calendar_set_year_next (GtkCalendar *calendar)
 {
   gint month_len;
-  
-  g_return_if_fail (GTK_IS_WIDGET (calendar));
-  
+
   calendar->year++;
   calendar_compute_days (calendar);
   g_signal_emit (calendar,
@@ -823,7 +920,7 @@ calendar_set_year_next (GtkCalendar *calendar)
   else
     gtk_calendar_select_day (calendar, calendar->selected_day);
   
-  gtk_widget_queue_draw (GTK_WIDGET (calendar));
+  calendar_queue_refresh (calendar);
 }
 
 static void
@@ -839,8 +936,6 @@ calendar_compute_days (GtkCalendar *calendar)
   gint col;
   gint day;
 
-  g_return_if_fail (GTK_IS_CALENDAR (calendar));
-
   year = calendar->year;
   month = calendar->month + 1;
   
@@ -1131,7 +1226,7 @@ calendar_set_month_prev (GtkCalendar *calendar)
       gtk_calendar_select_day (calendar, calendar->selected_day);
     }
 
-  gtk_widget_queue_draw (GTK_WIDGET (calendar));
+  calendar_queue_refresh (calendar);
 }
 
 \f
@@ -1148,8 +1243,18 @@ gtk_calendar_finalize (GObject *object)
 static void
 gtk_calendar_destroy (GtkObject *object)
 {
+  GtkCalendarPrivate *priv = GTK_CALENDAR_GET_PRIVATE (object);
+
   calendar_stop_spinning (GTK_CALENDAR (object));
   
+  /* Call the destroy function for the extra display callback: */
+  if (priv->detail_func_destroy && priv->detail_func_user_data)
+    {
+      priv->detail_func_destroy (priv->detail_func_user_data);
+      priv->detail_func_user_data = NULL;
+      priv->detail_func_destroy = NULL;
+    }
+
   GTK_OBJECT_CLASS (gtk_calendar_parent_class)->destroy (object);
 }
 
@@ -1220,6 +1325,19 @@ gtk_calendar_set_property (GObject      *object,
                                   GTK_CALENDAR_SHOW_WEEK_NUMBERS,
                                   g_value_get_boolean (value));
       break;
+    case PROP_SHOW_DETAILS:
+      calendar_set_display_option (calendar,
+                                  GTK_CALENDAR_SHOW_DETAILS,
+                                  g_value_get_boolean (value));
+      break;
+    case PROP_DETAIL_WIDTH_CHARS:
+      gtk_calendar_set_detail_width_chars (calendar,
+                                           g_value_get_int (value));
+      break;
+    case PROP_DETAIL_HEIGHT_ROWS:
+      gtk_calendar_set_detail_height_rows (calendar,
+                                           g_value_get_int (value));
+      break;
     default:
       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
       break;
@@ -1232,9 +1350,8 @@ gtk_calendar_get_property (GObject      *object,
                           GValue       *value,
                           GParamSpec   *pspec)
 {
-  GtkCalendar *calendar;
-
-  calendar = GTK_CALENDAR (object);
+  GtkCalendarPrivate *priv = GTK_CALENDAR_GET_PRIVATE (object);
+  GtkCalendar *calendar = GTK_CALENDAR (object);
 
   switch (prop_id) 
     {
@@ -1263,6 +1380,16 @@ gtk_calendar_get_property (GObject      *object,
       g_value_set_boolean (value, calendar_get_display_option (calendar,
                                                               GTK_CALENDAR_SHOW_WEEK_NUMBERS));
       break;
+    case PROP_SHOW_DETAILS:
+      g_value_set_boolean (value, calendar_get_display_option (calendar,
+                                                              GTK_CALENDAR_SHOW_DETAILS));
+      break;
+    case PROP_DETAIL_WIDTH_CHARS:
+      g_value_set_int (value, priv->detail_width_chars);
+      break;
+    case PROP_DETAIL_HEIGHT_ROWS:
+      g_value_set_int (value, priv->detail_height_rows);
+      break;
     default:
       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
       break;
@@ -1344,7 +1471,7 @@ calendar_realize_header (GtkCalendar *calendar)
       attributes.x = widget->style->xthickness;
       attributes.y = widget->style->ythickness;
       attributes.width = widget->allocation.width - 2 * attributes.x;
-      attributes.height = priv->header_h - 2 * attributes.y;
+      attributes.height = priv->header_h;
       priv->header_win = gdk_window_new (widget->window,
                                         &attributes, attributes_mask);
       
@@ -1536,6 +1663,81 @@ gtk_calendar_unrealize (GtkWidget *widget)
     (* GTK_WIDGET_CLASS (gtk_calendar_parent_class)->unrealize) (widget);
 }
 
+static gchar*
+gtk_calendar_get_detail (GtkCalendar *calendar,
+                         gint         row,
+                         gint         column)
+{
+  GtkCalendarPrivate *priv = GTK_CALENDAR_GET_PRIVATE (calendar);
+  gint year, month;
+
+  year = calendar->year;
+  month = calendar->month + calendar->day_month[row][column] - MONTH_CURRENT;
+
+  if (month < 0)
+    {
+      month += 12;
+      year -= 1;
+    }
+  else if (month > 11)
+    {
+      month -= 12;
+      year += 1;
+    }
+
+  return priv->detail_func (calendar,
+                            year, month,
+                            calendar->day[row][column],
+                            priv->detail_func_user_data);
+}
+
+static gboolean
+gtk_calendar_query_tooltip (GtkWidget  *widget,
+                            gint        x,
+                            gint        y,
+                            gboolean    keyboard_mode,
+                            GtkTooltip *tooltip)
+{
+  GtkCalendarPrivate *priv = GTK_CALENDAR_GET_PRIVATE (widget);
+  GtkCalendar *calendar = GTK_CALENDAR (widget);
+  gchar *detail = NULL;
+  GdkRectangle day_rect;
+
+  if (priv->main_win)
+    {
+      gint x0, y0, row, col;
+
+      gdk_window_get_position (priv->main_win, &x0, &y0);
+      col = calendar_column_from_x (calendar, x - x0);
+      row = calendar_row_from_y (calendar, y - y0);
+
+      if (0 != (priv->detail_overflow[row] & (1 << col)) ||
+          0 == (calendar->display_flags & GTK_CALENDAR_SHOW_DETAILS))
+        {
+          detail = gtk_calendar_get_detail (calendar, row, col);
+          calendar_day_rectangle (calendar, row, col, &day_rect);
+
+          day_rect.x += x0;
+          day_rect.y += y0;
+        }
+    }
+
+  if (detail)
+    {
+      gtk_tooltip_set_tip_area (tooltip, &day_rect);
+      gtk_tooltip_set_markup (tooltip, detail);
+
+      g_free (detail);
+
+      return TRUE;
+    }
+
+  if (GTK_WIDGET_CLASS (gtk_calendar_parent_class)->query_tooltip)
+    return GTK_WIDGET_CLASS (gtk_calendar_parent_class)->query_tooltip (widget, x, y, keyboard_mode, tooltip);
+
+  return FALSE;
+}
+
 \f
 /****************************************
  *       Size Request and Allocate      *
@@ -1551,13 +1753,14 @@ gtk_calendar_size_request (GtkWidget      *widget,
   PangoRectangle logical_rect;
 
   gint height;
-  gint i;
+  gint i, r, c;
   gint calendar_margin = CALENDAR_MARGIN;
   gint header_width, main_width;
   gint max_header_height = 0;
   gint focus_width;
   gint focus_padding;
-  
+  gint max_detail_height;
+
   gtk_widget_style_get (GTK_WIDGET (widget),
                        "focus-line-width", &focus_width,
                        "focus-padding", &focus_padding,
@@ -1634,8 +1837,6 @@ gtk_calendar_size_request (GtkWidget        *widget,
       priv->max_day_char_descent = MAX (priv->max_day_char_descent, 
                                                PANGO_DESCENT (logical_rect));
     }
-  /* We add one to max_day_char_width to be able to make the marked day "bold" */
-  priv->max_day_char_width = priv->min_day_width / 2 + 1;
   
   priv->max_label_char_ascent = 0;
   priv->max_label_char_descent = 0;
@@ -1664,6 +1865,77 @@ gtk_calendar_size_request (GtkWidget       *widget,
                                           logical_rect.width / 2);
       }
   
+  /* Calculate detail extents. Do this as late as possible since
+   * pango_layout_set_markup is called which alters font settings. */
+  max_detail_height = 0;
+
+  if (priv->detail_func && (calendar->display_flags & GTK_CALENDAR_SHOW_DETAILS))
+    {
+      gchar *markup, *tail;
+
+      if (priv->detail_width_chars || priv->detail_height_rows)
+        {
+          gint rows = MAX (1, priv->detail_height_rows) - 1;
+          gsize len = priv->detail_width_chars + rows + 16;
+
+          markup = tail = g_alloca (len);
+
+          memcpy (tail,     "<small>", 7);
+          tail += 7;
+
+          memset (tail, 'm', priv->detail_width_chars);
+          tail += priv->detail_width_chars;
+
+          memset (tail, '\n', rows);
+          tail += rows;
+
+          memcpy (tail,     "</small>", 9);
+          tail += 9;
+
+          g_assert (len == (tail - markup));
+
+          pango_layout_set_markup (layout, markup, -1);
+          pango_layout_get_pixel_extents (layout, NULL, &logical_rect);
+
+          if (priv->detail_width_chars)
+            priv->min_day_width = MAX (priv->min_day_width, logical_rect.width);
+          if (priv->detail_height_rows)
+            max_detail_height = MAX (max_detail_height, logical_rect.height);
+        }
+
+      if (!priv->detail_width_chars || !priv->detail_height_rows)
+        for (r = 0; r < 6; r++)
+          for (c = 0; c < 7; c++)
+            {
+              gchar *detail = gtk_calendar_get_detail (calendar, r, c);
+
+              if (detail)
+                {
+                  markup = g_strconcat ("<small>", detail, "</small>", NULL);
+                  pango_layout_set_markup (layout, markup, -1);
+
+                  if (priv->detail_width_chars)
+                    {
+                      pango_layout_set_wrap (layout, PANGO_WRAP_WORD_CHAR);
+                      pango_layout_set_width (layout, PANGO_SCALE * priv->min_day_width);
+                    }
+
+                  pango_layout_get_pixel_extents (layout, NULL, &logical_rect);
+
+                  if (!priv->detail_width_chars)
+                    priv->min_day_width = MAX (priv->min_day_width, logical_rect.width);
+                  if (!priv->detail_height_rows)
+                    max_detail_height = MAX (max_detail_height, logical_rect.height);
+
+                  g_free (markup);
+                  g_free (detail);
+                }
+            }
+    }
+
+  /* We add one to max_day_char_width to be able to make the marked day "bold" */
+  priv->max_day_char_width = priv->min_day_width / 2 + 1;
+
   main_width = (7 * (priv->min_day_width + (focus_padding + focus_width) * 2) + (DAY_XSEP * 6) + CALENDAR_MARGIN * 2
                + (priv->max_week_char_width
                   ? priv->max_week_char_width * 2 + (focus_padding + focus_width) * 2 + CALENDAR_XSEP * 2
@@ -1700,6 +1972,7 @@ gtk_calendar_size_request (GtkWidget        *widget,
   priv->main_h = (CALENDAR_MARGIN + calendar_margin
                          + 6 * (priv->max_day_char_ascent
                                 + priv->max_day_char_descent 
+                                 + max_detail_height
                                 + 2 * (focus_padding + focus_width))
                          + DAY_YSEP * 5);
   
@@ -2008,10 +2281,11 @@ calendar_paint_week_numbers (GtkCalendar *calendar)
   GtkWidget *widget = GTK_WIDGET (calendar);
   GtkCalendarPrivate *priv = GTK_CALENDAR_GET_PRIVATE (calendar);
   cairo_t *cr;
-  gint row, week = 0, year;
-  gint x_loc;
+
+  guint week = 0, year;
+  gint row, x_loc, y_loc;
+  gint day_height;
   char buffer[32];
-  gint y_loc, day_height;
   PangoLayout *layout;
   PangoRectangle logical_rect;
   gint focus_padding;
@@ -2132,6 +2406,14 @@ calendar_invalidate_day (GtkCalendar *calendar,
     }
 }
 
+static gboolean
+is_color_attribute (PangoAttribute *attribute,
+                    gpointer        data)
+{
+  return (attribute->klass->type == PANGO_ATTR_FOREGROUND ||
+          attribute->klass->type == PANGO_ATTR_BACKGROUND);
+}
+
 static void
 calendar_paint_day (GtkCalendar *calendar,
                    gint             row,
@@ -2141,6 +2423,7 @@ calendar_paint_day (GtkCalendar *calendar,
   GtkCalendarPrivate *priv = GTK_CALENDAR_GET_PRIVATE (calendar);
   cairo_t *cr;
   GdkColor *text_color;
+  gchar *detail;
   gchar buffer[32];
   gint day;
   gint x_loc, y_loc;
@@ -2148,13 +2431,16 @@ calendar_paint_day (GtkCalendar *calendar,
 
   PangoLayout *layout;
   PangoRectangle logical_rect;
-  
+  gboolean overflow = FALSE;
+  gboolean show_details;
+
   g_return_if_fail (row < 6);
   g_return_if_fail (col < 7);
 
   cr = gdk_cairo_create (priv->main_win);
 
   day = calendar->day[row][col];
+  show_details = (calendar->display_flags & GTK_CALENDAR_SHOW_DETAILS);
 
   calendar_day_rectangle (calendar, row, col, &day_rect);
   
@@ -2202,24 +2488,88 @@ calendar_paint_day (GtkCalendar *calendar,
    * too.
    */
   g_snprintf (buffer, sizeof (buffer), Q_("calendar:day:digits|%d"), day);
+
+  /* Get extra information to show, if any: */
+
+  if (priv->detail_func)
+    detail = gtk_calendar_get_detail (calendar, row, col);
+  else
+    detail = NULL;
+
   layout = gtk_widget_create_pango_layout (widget, buffer);
+  pango_layout_set_alignment (layout, PANGO_ALIGN_CENTER);
   pango_layout_get_pixel_extents (layout, NULL, &logical_rect);
   
-  x_loc = day_rect.x + day_rect.width / 2 + priv->max_day_char_width;
-  x_loc -= logical_rect.width;
-  y_loc = day_rect.y + (day_rect.height - logical_rect.height) / 2;
-  
+  x_loc = day_rect.x + (day_rect.width - logical_rect.width) / 2;
+  y_loc = day_rect.y;
+
   gdk_cairo_set_source_color (cr, text_color);
   cairo_move_to (cr, x_loc, y_loc);
   pango_cairo_show_layout (cr, layout);
-    
-  if (calendar->marked_date[day-1]
-      && calendar->day_month[row][col] == MONTH_CURRENT)
+
+  if (calendar->day_month[row][col] == MONTH_CURRENT &&
+     (calendar->marked_date[day-1] || (detail && !show_details)))
     {
       cairo_move_to (cr, x_loc - 1, y_loc);
       pango_cairo_show_layout (cr, layout);
     }
 
+  y_loc += priv->max_day_char_descent;
+
+  if (priv->detail_func && show_details)
+    {
+      cairo_save (cr);
+
+      if (calendar->selected_day == day)
+        gdk_cairo_set_source_color (cr, &widget->style->text[GTK_STATE_ACTIVE]);
+      else if (calendar->day_month[row][col] == MONTH_CURRENT)
+        gdk_cairo_set_source_color (cr, &widget->style->base[GTK_STATE_ACTIVE]);
+      else
+        gdk_cairo_set_source_color (cr, &widget->style->base[GTK_STATE_INSENSITIVE]);
+
+      cairo_set_line_width (cr, 1);
+      cairo_move_to (cr, day_rect.x + 2, y_loc + 0.5);
+      cairo_line_to (cr, day_rect.x + day_rect.width - 2, y_loc + 0.5);
+      cairo_stroke (cr);
+
+      cairo_restore (cr);
+
+      y_loc += 2;
+    }
+
+  if (detail && show_details)
+    {
+      gchar *markup = g_strconcat ("<small>", detail, "</small>", NULL);
+      pango_layout_set_markup (layout, markup, -1);
+      g_free (markup);
+
+      if (day == calendar->selected_day)
+        {
+          /* Stripping colors as they conflict with selection marking. */
+
+          PangoAttrList *attrs = pango_layout_get_attributes (layout);
+          PangoAttrList *colors = NULL;
+
+          if (attrs)
+            colors = pango_attr_list_filter (attrs, is_color_attribute, NULL);
+          if (colors)
+            pango_attr_list_unref (colors);
+        }
+
+      pango_layout_set_wrap (layout, PANGO_WRAP_WORD_CHAR);
+      pango_layout_set_width (layout, PANGO_SCALE * day_rect.width);
+
+      if (priv->detail_height_rows)
+        {
+          gint dy = day_rect.height - (y_loc - day_rect.y);
+          pango_layout_set_height (layout, PANGO_SCALE * dy);
+          pango_layout_set_ellipsize (layout, PANGO_ELLIPSIZE_END);
+        }
+
+      cairo_move_to (cr, day_rect.x, y_loc);
+      pango_cairo_show_layout (cr, layout);
+    }
+
   if (GTK_WIDGET_HAS_FOCUS (calendar) 
       && calendar->focus_row == row && calendar->focus_col == col)
     {
@@ -2233,17 +2583,19 @@ calendar_paint_day (GtkCalendar *calendar,
       gtk_paint_focus (widget->style, 
                       priv->main_win,
                       state,
-#if 0
-                      (calendar->selected_day == day) 
-                         ? GTK_STATE_SELECTED : GTK_STATE_NORMAL, 
-#endif
                       NULL, widget, "calendar-day",
                       day_rect.x,     day_rect.y, 
                       day_rect.width, day_rect.height);
     }
 
+  if (overflow)
+    priv->detail_overflow[row] |= (1 << col);
+  else
+    priv->detail_overflow[row] &= ~(1 << col);
+
   g_object_unref (layout);
   cairo_destroy (cr);
+  g_free (detail);
 }
 
 static void
@@ -2934,10 +3286,10 @@ gtk_calendar_focus_out (GtkWidget     *widget,
                        GdkEventFocus *event)
 {
   GtkCalendarPrivate *priv = GTK_CALENDAR_GET_PRIVATE (widget);
+  GtkCalendar *calendar = GTK_CALENDAR (widget);
 
-  gtk_widget_queue_draw (widget);
-
-  calendar_stop_spinning (GTK_CALENDAR (widget));
+  calendar_queue_refresh (calendar);
+  calendar_stop_spinning (calendar);
   
   priv->in_drag = 0; 
 
@@ -3075,7 +3427,8 @@ gtk_calendar_drag_data_received (GtkWidget        *widget,
        * supposed to call drag_status, not actually paste in the
        * data.
        */
-      str = gtk_selection_data_get_text (selection_data);
+      str = (gchar*) gtk_selection_data_get_text (selection_data);
+
       if (str) 
        {
          date = g_date_new ();
@@ -3094,7 +3447,7 @@ gtk_calendar_drag_data_received (GtkWidget        *widget,
     }
 
   date = g_date_new ();
-  str = gtk_selection_data_get_text (selection_data);
+  str = (gchar*) gtk_selection_data_get_text (selection_data);
   if (str) 
     {
       g_date_set_parse (date, str);
@@ -3292,6 +3645,9 @@ gtk_calendar_set_display_options (GtkCalendar            *calendar,
       if ((flags ^ calendar->display_flags) & GTK_CALENDAR_WEEK_START_MONDAY)
        g_warning ("GTK_CALENDAR_WEEK_START_MONDAY is ignored; the first day of the week is determined from the locale");
       
+      if ((flags ^ calendar->display_flags) & GTK_CALENDAR_SHOW_DETAILS)
+        resize++;
+
       calendar->display_flags = flags;
       if (resize)
        gtk_widget_queue_resize (GTK_WIDGET (calendar));
@@ -3334,8 +3690,7 @@ gtk_calendar_select_month (GtkCalendar *calendar,
   calendar->year  = year;
   
   calendar_compute_days (calendar);
-  
-  gtk_widget_queue_draw (GTK_WIDGET (calendar));
+  calendar_queue_refresh (calendar);
 
   g_object_freeze_notify (G_OBJECT (calendar));
   g_object_notify (G_OBJECT (calendar), "month");
@@ -3409,8 +3764,7 @@ gtk_calendar_clear_marks (GtkCalendar *calendar)
     }
 
   calendar->num_marked_dates = 0;
-
-  gtk_widget_queue_draw (GTK_WIDGET (calendar));
+  calendar_queue_refresh (calendar);
 }
 
 /**
@@ -3490,6 +3844,142 @@ gtk_calendar_get_date (GtkCalendar *calendar,
     *day = calendar->selected_day;
 }
 
+/**
+ * gtk_calendar_set_detail_func:
+ * @calendar: a #GtkCalendar.
+ * @func: a function providing details for each day.
+ * @data: data to pass to @func invokations.
+ * @destroy: a function for releasing @data.
+ *
+ * Installs a function which provides Pango markup with detail information
+ * for each day. Examples for such details are holidays or appointments. That
+ * information is shown below each day when #GtkCalendar:show-details is set.
+ * A tooltip containing with full detail information is provided, if the entire
+ * text should not fit into the details area, or if #GtkCalendar:show-details
+ * is not set.
+ *
+ * The size of the details area can be restricted by setting the
+ * #GtkCalendar:detail-width-chars and #GtkCalendar:detail-height-rows
+ * properties.
+ *
+ * Since: 2.14
+ */
+void
+gtk_calendar_set_detail_func (GtkCalendar           *calendar,
+                              GtkCalendarDetailFunc  func,
+                              gpointer               data,
+                              GDestroyNotify         destroy)
+{
+  GtkCalendarPrivate *priv;
+
+  g_return_if_fail (GTK_IS_CALENDAR (calendar));
+
+  priv = GTK_CALENDAR_GET_PRIVATE (calendar);
+
+  if (priv->detail_func_destroy)
+    priv->detail_func_destroy (priv->detail_func_user_data);
+
+  priv->detail_func = func;
+  priv->detail_func_user_data = data;
+  priv->detail_func_destroy = destroy;
+
+  gtk_widget_set_has_tooltip (GTK_WIDGET (calendar),
+                              NULL != priv->detail_func);
+  gtk_widget_queue_resize (GTK_WIDGET (calendar));
+}
+
+/**
+ * gtk_calendar_set_detail_width_chars:
+ * @calendar: a #GtkCalendar.
+ * @chars: detail width in characters.
+ *
+ * Updates the width of detail cells.
+ * See #GtkCalendar:detail-width-chars.
+ *
+ * Since: 2.14
+ */
+void
+gtk_calendar_set_detail_width_chars (GtkCalendar *calendar,
+                                     gint         chars)
+{
+  GtkCalendarPrivate *priv;
+
+  g_return_if_fail (GTK_IS_CALENDAR (calendar));
+
+  priv = GTK_CALENDAR_GET_PRIVATE (calendar);
+
+  if (chars != priv->detail_width_chars)
+    {
+      priv->detail_width_chars = chars;
+      g_object_notify (G_OBJECT (calendar), "detail-width-chars");
+      gtk_widget_queue_resize_no_redraw (GTK_WIDGET (calendar));
+    }
+}
+
+/**
+ * gtk_calendar_set_detail_height_rows:
+ * @calendar: a #GtkCalendar.
+ * @rows: detail height in rows.
+ *
+ * Updates the height of detail cells.
+ * See #GtkCalendar:detail-height-rows.
+ *
+ * Since: 2.14
+ */
+void
+gtk_calendar_set_detail_height_rows (GtkCalendar *calendar,
+                                     gint         rows)
+{
+  GtkCalendarPrivate *priv;
+
+  g_return_if_fail (GTK_IS_CALENDAR (calendar));
+
+  priv = GTK_CALENDAR_GET_PRIVATE (calendar);
+
+  if (rows != priv->detail_height_rows)
+    {
+      priv->detail_height_rows = rows;
+      g_object_notify (G_OBJECT (calendar), "detail-height-rows");
+      gtk_widget_queue_resize_no_redraw (GTK_WIDGET (calendar));
+    }
+}
+
+/**
+ * gtk_calendar_get_detail_width_chars:
+ * @calendar: a #GtkCalendar.
+ *
+ * Queries the width of detail cells, in characters.
+ * See #GtkCalendar:detail-width-chars.
+ *
+ * Since: 2.14
+ *
+ * Return value: The width of detail cells, in characters.
+ */
+gint
+gtk_calendar_get_detail_width_chars (GtkCalendar *calendar)
+{
+  g_return_val_if_fail (GTK_IS_CALENDAR (calendar), 0);
+  return GTK_CALENDAR_GET_PRIVATE (calendar)->detail_width_chars;
+}
+
+/**
+ * gtk_calendar_get_detail_height_rows:
+ * @calendar: a #GtkCalendar.
+ *
+ * Queries the height of detail cells, in rows.
+ * See #GtkCalendar:detail-width-chars.
+ *
+ * Since: 2.14
+ *
+ * Return value: The height of detail cells, in rows.
+ */
+gint
+gtk_calendar_get_detail_height_rows (GtkCalendar *calendar)
+{
+  g_return_val_if_fail (GTK_IS_CALENDAR (calendar), 0);
+  return GTK_CALENDAR_GET_PRIVATE (calendar)->detail_height_rows;
+}
+
 /**
  * gtk_calendar_freeze:
  * @calendar: a #GtkCalendar