[gnome-calendar/wip/flb/weather-forecast: 100/135] week-header: Add initial weather support.



commit 4ffbf68b237b325fca4f2b5489ac0dc8f3136d56
Author: Florian Brosch <flo brosch gmail com>
Date:   Thu Oct 12 23:51:57 2017 +0200

    week-header: Add initial weather support.
    
    This is still work in progress. API is stable but presentation
    is not. It also contains some LOCs for unfinished mouse-over and
    click-events.

 src/views/gcal-week-header.c | 379 ++++++++++++++++++++++++++++++++++++++++++-
 src/views/gcal-week-header.h |   9 +
 2 files changed, 379 insertions(+), 9 deletions(-)
---
diff --git a/src/views/gcal-week-header.c b/src/views/gcal-week-header.c
index 1ac9548f..8a9c706d 100644
--- a/src/views/gcal-week-header.c
+++ b/src/views/gcal-week-header.c
@@ -34,6 +34,31 @@
 
 #define COLUMN_PADDING       6
 
+/* WeatherInfoPos:
+ * @winfo: (nullable): Holds weather information for this week-day. All other fields are only valid if this 
one is not %NULL.
+ * @icon_buf: (nullable): Buffered weather icon.
+ * @x: X-position of weather indicators.
+ * @y: Y-positon of weather indicators.
+ * @width: Width of weather indicators.
+ * @height: Height of weather indicators.
+ *
+ * Structure represents a weather indicator for a single day.
+ *
+ * Location information are stored for mouse-over events.
+ */
+typedef struct
+{
+  GcalWeatherInfo  *winfo;    /* owned */
+  GdkPixbuf        *icon_buf; /* owned */
+
+  gint              x;
+  gint              y;
+  gint              width;
+  gint              height;
+} WeatherInfoPos;
+
+
+
 struct _GcalWeekHeader
 {
   GtkGrid           parent;
@@ -74,6 +99,11 @@ struct _GcalWeekHeader
   gint              selection_end;
   gint              dnd_cell;
 
+  GcalWeatherService *weather_service; /* unowned, nullable */
+  gulong              on_weather_changed_hid;
+  /* Array of nullable weather infos for each day, starting with Sunday. */
+  WeatherInfoPos      weather_infos[7];
+
   GtkSizeGroup     *sizegroup;
 };
 
@@ -86,6 +116,9 @@ typedef enum
 enum
 {
   EVENT_ACTIVATED,
+  /*
+  EVENT_WEATHER_MOUSEOVER,
+  */
   LAST_SIGNAL
 };
 
@@ -93,6 +126,62 @@ static guint signals[LAST_SIGNAL] = { 0, };
 
 G_DEFINE_TYPE (GcalWeekHeader, gcal_week_header, GTK_TYPE_GRID);
 
+
+/* WeatherInfoPos methods */
+
+/* wip_set_geometric:
+ *
+ * Setter for geometric weather indicator information.
+ */
+static inline void
+wip_set_geometric (WeatherInfoPos *wip,
+                   gint            x,
+                   gint            y,
+                   gint            width,
+                   gint            height)
+{
+  g_return_if_fail (wip != NULL);
+
+  wip->x = x;
+  wip->y = y;
+  wip->width = width;
+  wip->height = height;
+}
+
+/* wip_clear_geometric:
+ *
+ * Sets all weather indicator information to zero.
+ */
+static inline void
+wip_clear_geometric (WeatherInfoPos *wip)
+{
+  g_return_if_fail (wip != NULL);
+
+  wip->x = 0;
+  wip->y = 0;
+  wip->width = 0;
+  wip->height = 0;
+}
+
+/* wip_clear:
+ *
+ * Drops all internal references and resets
+ * geometric information.
+ */
+static inline void
+wip_clear (WeatherInfoPos *wip)
+{
+   g_return_if_fail (wip != NULL);
+
+  if (wip->winfo != NULL)
+    g_clear_object (&wip->winfo);
+
+  if (wip->icon_buf != NULL)
+    g_clear_object (&wip->icon_buf);
+
+  wip_clear_geometric (wip);
+}
+
 /* Event activation methods */
 static void
 on_event_widget_activated (GcalEventWidget *widget,
@@ -231,6 +320,17 @@ on_button_released (GcalWeekHeader *self,
   return GDK_EVENT_STOP;
 }
 
+static void
+on_weather_update (GcalWeatherService *weather_service,
+                   GcalWeekHeader     *self)
+{
+  g_return_if_fail (GCAL_IS_WEATHER_SERVICE (weather_service));
+  g_return_if_fail (GCAL_IS_WEEK_HEADER (self));
+  g_return_if_fail (self->weather_service == weather_service);
+
+  gcal_week_header_update_weather_infos (self);
+}
+
 static GcalEvent*
 get_event_by_uuid (GcalWeekHeader *self,
                    const gchar    *uuid)
@@ -1031,6 +1131,9 @@ gcal_week_header_finalize (GObject *object)
 
   for (i = 0; i < 7; i++)
     g_list_free (self->events[i]);
+
+  for (i = 0; i < G_N_ELEMENTS (self->weather_infos); i++)
+    wip_clear (&self->weather_infos[i]);
 }
 
 static void
@@ -1082,7 +1185,7 @@ gcal_week_header_draw (GtkWidget      *widget,
   PangoFontDescription *bold_font;
 
   gdouble cell_width;
-  gint i, font_height, current_cell, today_column;
+  gint i, day_abv_font_height, current_cell, today_column;
   gint start_x, start_y;
 
   gboolean ltr;
@@ -1174,12 +1277,14 @@ gcal_week_header_draw (GtkWidget      *widget,
       gtk_style_context_restore (context);
     }
 
-  pango_layout_get_pixel_size (layout, NULL, &font_height);
+  pango_layout_get_pixel_size (layout, NULL, &day_abv_font_height);
 
   for (i = 0; i < 7; i++)
     {
+      WeatherInfoPos *wpinfo; /* unowned */
       gchar *weekday_date, *weekday_abv, *weekday;
       gdouble x;
+      gint day_num_font_height, day_num_font_baseline;
       gint font_width;
       gint n_day;
 
@@ -1201,7 +1306,8 @@ gcal_week_header_draw (GtkWidget      *widget,
       pango_layout_set_font_description (layout, bold_font);
       pango_layout_set_text (layout, weekday_date, -1);
 
-      pango_layout_get_pixel_size (layout, &font_width, NULL);
+      pango_layout_get_pixel_size (layout, &font_width, &day_num_font_height);
+      day_num_font_baseline = pango_layout_get_baseline (layout) / PANGO_SCALE;
 
       if (ltr)
         x = padding.left + cell_width * i + COLUMN_PADDING + start_x;
@@ -1211,14 +1317,14 @@ gcal_week_header_draw (GtkWidget      *widget,
       gtk_render_layout (context,
                          cr,
                          x,
-                         font_height + padding.bottom + start_y,
+                         day_abv_font_height + padding.bottom + start_y,
                          layout);
 
       gtk_style_context_restore (context);
 
       /* Draws the days name */
       weekday = g_utf8_strup (gcal_get_weekday ((i + self->first_weekday) % 7), -1);
-      weekday_abv = g_strdup_printf ("%s", weekday);
+      weekday_abv = g_strdup (weekday);
       g_free (weekday);
 
       gtk_style_context_save (context);
@@ -1246,6 +1352,85 @@ gcal_week_header_draw (GtkWidget      *widget,
 
       gtk_style_context_restore (context);
 
+      /* Draws weather icon if given */
+      wpinfo = &self->weather_infos[ltr? i : 6 - i];
+      if (wpinfo->winfo != NULL)
+        {
+          const gchar *weather_icon_name = gcal_weather_info_get_icon_name (wpinfo->winfo);
+          const gchar *weather_temp = gcal_weather_info_get_temperature (wpinfo->winfo);
+
+          /* Imagine a box around weather indicators with length MAX(imgW,tempW)
+           * We compute its width and position in this section.
+           * Its height is derived from day name and numbers. The icon sticks on
+           * the very top and the temperature on its base.
+           */
+          gint icon_size = day_num_font_height;
+          gint temp_width = 0;
+          gint temp_height = 0;
+          gint temp_baseline = 0;
+
+          gdouble wibox_width;
+          gdouble wibox_x;
+
+          // TODO: actually try to style temperature via css
+          if (weather_icon_name != NULL && wpinfo->icon_buf == NULL)
+            {
+              GtkIconTheme *theme; /* unowned */
+              g_autoptr (GError) err = NULL;
+              gint icon_flags;
+
+              theme = gtk_icon_theme_get_default ();
+              // TODO: catch icon theme changes
+
+              icon_flags = ltr? GTK_ICON_LOOKUP_FORCE_SIZE | GTK_ICON_LOOKUP_DIR_LTR
+                              : GTK_ICON_LOOKUP_FORCE_SIZE | GTK_ICON_LOOKUP_DIR_RTL;
+              wpinfo->icon_buf = gtk_icon_theme_load_icon (theme, weather_icon_name, icon_size, icon_flags, 
&err);
+              if (err != NULL)
+                {
+                  g_assert (wpinfo->icon_buf == NULL);
+                  g_warning ("Could not load icon %s: %s", weather_icon_name, err->message);
+                }
+            }
+
+          if (G_LIKELY (weather_temp != NULL))
+            {
+              gtk_style_context_save (context);
+              gtk_style_context_add_class (context, "week-temperature");
+              gtk_style_context_get (context, state, "font", &bold_font, NULL);
+
+              pango_layout_set_font_description (layout, bold_font);
+              pango_layout_set_text (layout, weather_temp, -1);
+
+              pango_layout_get_pixel_size (layout, &temp_width, &temp_height);
+              temp_baseline = pango_layout_get_baseline (layout) / PANGO_SCALE;
+            }
+
+          wibox_width = MAX(icon_size, temp_width);
+          wibox_x = ltr ? alloc.width - (cell_width * (6 - i) + wibox_width + COLUMN_PADDING)
+                        : padding.left + cell_width * i + COLUMN_PADDING + start_x;
+
+          /* Actually draw weather indicator: */
+          if (G_LIKELY (weather_temp != NULL))
+            {
+              gtk_render_layout (context,
+                                 cr,
+                                 wibox_x + (wibox_width - temp_width) / 2,
+                                 day_abv_font_height + padding.bottom + start_y + day_num_font_baseline - 
temp_baseline,
+                                 layout);
+              gtk_style_context_restore (context);
+            }
+
+          if (G_LIKELY (wpinfo->icon_buf != NULL))
+            {
+              gdk_cairo_set_source_pixbuf (cr,
+                                           wpinfo->icon_buf,
+                                           wibox_x + (wibox_width - icon_size) / 2,
+                                           start_y);
+              cairo_paint (cr);
+            }
+        }
+
+
       /* Draws the lines after each day of the week */
       gtk_style_context_save (context);
       gtk_style_context_add_class (context, "lines");
@@ -1257,11 +1442,11 @@ gcal_week_header_draw (GtkWidget      *widget,
 
       cairo_move_to (cr,
                      ALIGNED (ltr ? (cell_width * i + start_x) : (alloc.width - (cell_width * i + start_x))),
-                     font_height + padding.bottom + start_y);
+                     day_abv_font_height + padding.bottom + start_y);
 
       cairo_rel_line_to (cr,
                          0.0,
-                         gtk_widget_get_allocated_height (widget) - font_height - start_y + padding.bottom);
+                         gtk_widget_get_allocated_height (widget) - day_abv_font_height - start_y + 
padding.bottom);
       cairo_stroke (cr);
 
       gtk_style_context_restore (context);
@@ -1456,6 +1641,22 @@ gcal_week_header_class_init (GcalWeekHeaderClass *kclass)
                                            1,
                                            GCAL_TYPE_EVENT_WIDGET);
 
+  /* *
+   * GCalWeekHeader::mouseover-weather:
+   * @info: Hovered GcalWeatherInfo
+   *
+   * Triggered when cursor points to a weather indicator.
+   */
+  /*
+  signals[EVENT_WEATHER_MOUSEOVER] = g_signal_new ("weather-mouseover",
+                                                   GCAL_TYPE_WEEK_HEADER,
+                                                   G_SIGNAL_RUN_FIRST,
+                                                   0, NULL, NULL, NULL,
+                                                   G_TYPE_NONE,
+                                                   1,
+                                                   GCAL_TYPE_WEATHER_INFO);
+  */
+
   gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/calendar/week-header.ui");
 
   gtk_widget_class_bind_template_child (widget_class, GcalWeekHeader, expand_button);
@@ -1484,7 +1685,9 @@ gcal_week_header_init (GcalWeekHeader *self)
   self->selection_start = -1;
   self->selection_end = -1;
   self->dnd_cell = -1;
-
+  self->weather_service = NULL;
+  self->on_weather_changed_hid = 0;
+  memset (self->weather_infos, 0, sizeof(self->weather_infos));
   gtk_widget_init_template (GTK_WIDGET (self));
 
   /* This is to avoid stray lines when adding and removing events */
@@ -1500,7 +1703,141 @@ G_GNUC_END_IGNORE_DEPRECATIONS
                      GDK_ACTION_MOVE);
 }
 
+/* Private API */
+
+/* gcal_week_header_add_weather_infos:
+ * @self:  (not nullable): The #GcalWeekHeader instance.
+ * @winfo: List of #GcalWeatherInfos to add to @self.
+ *
+ * Adds weather information to this header. @self
+ * only adds weather information that can be
+ * displayed with current settings.
+ *
+ * Return: Number of consumed weather information
+ *         objects.
+ */
+static gint
+gcal_week_header_add_weather_infos (GcalWeekHeader *self,
+                                    GSList         *winfos)
+{
+  g_autoptr (GDateTime) _week_start = NULL;
+  GSList *iter; /* unowned */
+  GDate week_start;
+  int consumed = 0;
+
+  g_return_val_if_fail (self != NULL, 0);
+
+  _week_start = get_start_of_week (self->active_date);
+  g_date_set_dmy (&week_start,
+                  g_date_time_get_day_of_month (_week_start),
+                  g_date_time_get_month (_week_start),
+                  g_date_time_get_year (_week_start));
+
+  for (iter = winfos; iter != NULL; iter = iter->next)
+    {
+      GcalWeatherInfo *gwi; /* unowned */
+      GDate gwi_date;
+      gint day_diff;
+
+      gwi = GCAL_WEATHER_INFO (iter->data);
+      gcal_weather_info_get_date (gwi, &gwi_date);
+
+      day_diff = g_date_days_between (&week_start, &gwi_date);
+      if (day_diff >= 0 && day_diff < G_N_ELEMENTS (self->weather_infos))
+        {
+          wip_clear (&self->weather_infos[day_diff]);
+          self->weather_infos[day_diff].winfo = g_object_ref (gwi);
+          consumed++;
+        }
+    }
+
+  if (consumed > 0)
+    gtk_widget_queue_draw (GTK_WIDGET (self));
+
+  return consumed;
+}
+
+/* gcal_week_header_clear_weather_infos:
+ * @self: The #GcalWeekHeader instance.
+ *
+ * Removes all registered weather information objects from
+ * this widget.
+ */
+static void
+gcal_week_header_clear_weather_infos (GcalWeekHeader *self)
+{
+  g_return_if_fail (GCAL_IS_WEEK_HEADER (self));
+
+  for (gint i = 0; i < G_N_ELEMENTS (self->weather_infos); i++)
+    wip_clear (&self->weather_infos[i]);
+
+  gtk_widget_queue_draw (GTK_WIDGET (self));
+}
+
+
 /* Public API */
+
+/**
+ * gcal_week_header_set_weather_service:
+ * @self:    The #GcalWeekHeader instance.
+ * @service: (nullable): The weather service to query.
+ *
+ * Note that #GcalWeekHeader does not hold a strong reference
+ * to its weather service.
+ */
+void
+gcal_week_header_set_weather_service (GcalWeekHeader     *self,
+                                      GcalWeatherService *service)
+{
+  g_return_if_fail (GCAL_IS_WEEK_HEADER (self));
+  g_return_if_fail (service == NULL || GCAL_IS_WEATHER_SERVICE (service));
+
+  if (self->weather_service == service)
+    return;
+
+  if (self->on_weather_changed_hid > 0)
+    {
+      g_signal_handler_disconnect (self->weather_service, self->on_weather_changed_hid);
+      self->on_weather_changed_hid = 0;
+    }
+
+  self->weather_service = service;
+
+  if (self->weather_service != NULL)
+    self->on_weather_changed_hid = g_signal_connect_object (
+                                         self->weather_service,
+                                         "weather-changed",
+                                         G_CALLBACK (on_weather_update),
+                                         self,
+                                         0);
+
+  gcal_week_header_update_weather_infos (self);
+}
+
+/**
+ * gcal_week_header_update_weather_infos:
+ * @self: The #GcalWeekHeader instance.
+ *
+ * Retrieves latest weather information from registered
+ * weather service and displays it.
+ */
+void
+gcal_week_header_update_weather_infos (GcalWeekHeader *self)
+{
+
+  g_return_if_fail (GCAL_IS_WEEK_HEADER (self));
+
+  gcal_week_header_clear_weather_infos (self);
+
+  if (self->weather_service != NULL)
+    {
+      GSList* weather_infos = NULL; /* unowned */
+
+      weather_infos = gcal_weather_service_get_weather_infos (self->weather_service);
+      gcal_week_header_add_weather_infos (self, weather_infos);
+    }
+}
+
 void
 gcal_week_header_set_manager (GcalWeekHeader *self,
                               GcalManager    *manager)
@@ -1523,7 +1860,6 @@ gcal_week_header_set_first_weekday (GcalWeekHeader *self,
 
   self->first_weekday = nr_day;
 }
-
 void
 gcal_week_header_set_use_24h_format (GcalWeekHeader *self,
                                      gboolean        use_24h_format)
@@ -1743,3 +2079,28 @@ gcal_week_header_set_date (GcalWeekHeader *self,
 
   g_clear_pointer (&old_date, g_free);
 }
+
+/**
+ * gcal_week_header_get_weather_infos:
+ * @self: The #GcalWeekHeader instance.
+ *
+ * Returns a list of shown weather informations.
+ *
+ * @Returns: (transfer container):
+ *           A GSList. The callee is responsible for freeing it.
+ *           Elements are owned by the widget. Do not modify.
+ */
+GSList*
+gcal_week_header_get_shown_weather_infos (GcalWeekHeader  *self)
+{
+  g_return_val_if_fail (GCAL_IS_WEEK_HEADER (self), NULL);
+  GSList* lst = NULL; /* owned[unowned] */
+
+  for (int i = 0; i < G_N_ELEMENTS (self->weather_infos); i++)
+    {
+      if (self->weather_infos[i].winfo != NULL)
+        lst = g_slist_prepend (lst, self->weather_infos[i].winfo);
+    }
+
+   return lst;
+}
diff --git a/src/views/gcal-week-header.h b/src/views/gcal-week-header.h
index 7aa78e6a..a5ef2d8a 100644
--- a/src/views/gcal-week-header.h
+++ b/src/views/gcal-week-header.h
@@ -21,6 +21,8 @@
 
 #include "gcal-manager.h"
 #include "gcal-event-widget.h"
+#include "gcal-weather-service.h"
+#include "gcal-weather-info.h"
 
 #include <gtk/gtk.h>
 
@@ -56,6 +58,13 @@ void                 gcal_week_header_clear_marks                (GcalWeekHeader
 void                 gcal_week_header_set_date                   (GcalWeekHeader     *self,
                                                                   icaltimetype       *date);
 
+void                 gcal_week_header_set_weather_service        (GcalWeekHeader     *self,
+                                                                  GcalWeatherService *service);
+
+void                 gcal_week_header_update_weather_infos       (GcalWeekHeader     *self);
+
+GSList*              gcal_week_header_get_shown_weather_infos    (GcalWeekHeader     *self);
+
 G_END_DECLS
 
 #endif /* GCAL_WEEK_HEADER_H */


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]