[gnome-calendar/wip/flb/weather-forecast: 15/50] weather: Integrate weather into our main windows.



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

    weather: Integrate weather into our main windows.

 data/org.gnome.calendar.gschema.xml |    5 +
 data/ui/menus.ui                    |  165 ++++++++++-
 src/gcal-window.c                   |  532 ++++++++++++++++++++++++++++++++++-
 src/views/gcal-week-view.c          |   48 +++-
 4 files changed, 725 insertions(+), 25 deletions(-)
---
diff --git a/data/org.gnome.calendar.gschema.xml b/data/org.gnome.calendar.gschema.xml
index e938690..62b8e9a 100644
--- a/data/org.gnome.calendar.gschema.xml
+++ b/data/org.gnome.calendar.gschema.xml
@@ -21,5 +21,10 @@
             <summary>Type of the active view</summary>
             <description>Type of the active window view, default value is: monthly view</description>
         </key>
+        <key name="weather-settings" type="(bbsmv)">
+            <default>(true, true, '', nothing)</default>
+            <summary>Weather Service Configuration</summary>
+            <description>Whether weather reports are shown, automatic locations are used and a 
location-name</description>
+        </key>
     </schema>
 </schemalist>
diff --git a/data/ui/menus.ui b/data/ui/menus.ui
index cea2f5d..d5ed908 100644
--- a/data/ui/menus.ui
+++ b/data/ui/menus.ui
@@ -30,19 +30,158 @@
       </item>
     </section>
   </menu>
-  <menu id="win-menu">
-    <section>
-      <item>
-        <attribute name="label" translatable="yes">Add Eve_nt…</attribute>
-        <attribute name="action">app.new</attribute>
-      </item>
-      <item>
-        <attribute name="label" translatable="yes">_Synchronize</attribute>
-        <attribute name="action">app.sync</attribute>
-        <attribute name="accel">F5</attribute>
-      </item>
-    </section>
-  </menu>
+  <object class="GtkPopoverMenu" id="win-menu">
+    <child>
+      <object class="GtkStack" id="win-menu-stack">
+        <property name="visible">True</property>
+        <property name="can_focus">False</property>
+        <property name="vhomogeneous">False</property>
+        <child>
+          <!-- Initial user menu: -->
+          <object class="GtkBox">
+            <property name="margin">12</property>
+            <property name="visible">True</property>
+            <property name="orientation">vertical</property>
+            <child>
+              <object class="GtkModelButton">
+                <property name="text" translatable="yes">Add Eve_nt</property>
+                <property name="action-name">app.new</property>
+                <property name="visible">True</property>
+              </object>
+            </child>
+            <child>
+              <object class="GtkModelButton">
+                <property name="text" translatable="yes">_Synchronize</property>
+                <property name="action-name">app.sync</property>
+                <property name="visible">True</property>
+              </object>
+            </child>
+            <child>
+              <object class="GtkModelButton" id="win-menu-open-weather">
+                <property name="text" translatable="yes">_Weather</property>
+                <property name="menu-name" translatable="yes">win-menu-weather</property>
+                <property name="visible">True</property>
+              </object>
+            </child>
+          </object>
+          <packing>
+            <property name="name">win-menu-main</property>
+          </packing>
+        </child>
+        <child>
+          <!-- Weather Box: -> name this weather-settings -->
+          <object class="GtkBox">
+            <property name="visible">True</property>
+            <property name="can_focus">False</property>
+            <property name="orientation">vertical</property>
+            <property name="margin">12</property>
+            <property name="spacing">5</property> <!-- TODO: check HIG for right value -->
+            <child>
+              <object class="GtkModelButton" id="win-menu-close-weather">
+                <property name="text" translatable="yes">_Weather</property>
+                <property name="menu-name" translatable="yes">win-menu-main</property>
+                <property name="inverted">True</property>
+                <property name="centered">True</property>
+                <property name="visible">True</property>
+              </object>
+              <packing>
+                <property name="position">0</property>
+              </packing>
+            </child>
+            <child>
+              <object class="GtkBox">
+                <property name="visible">True</property>
+                <property name="can_focus">False</property>
+                <property name="spacing">5</property> <!-- TODO: check HIG for right value -->
+                <child>
+                  <object class="GtkLabel">
+                    <property name="visible">True</property>
+                    <property name="can_focus">False</property>
+                    <property name="halign">start</property>
+                    <property name="label" translatable="yes">Show Weather</property>
+                  </object>
+                  <packing>
+                    <property name="expand">True</property>
+                    <property name="fill">True</property>
+                    <property name="position">0</property>
+                  </packing>
+                </child>
+                <child>
+                  <object class="GtkSwitch" id="show-weather-switch">
+                    <property name="visible">True</property>
+                    <property name="can_focus">True</property>
+                  </object>
+                  <packing>
+                    <property name="expand">False</property>
+                    <property name="fill">True</property>
+                    <property name="position">1</property>
+                  </packing>
+                </child>
+              </object>
+              <packing>
+                <property name="expand">False</property>
+                <property name="fill">True</property>
+                <property name="position">1</property>
+              </packing>
+            </child>
+            <child>
+              <object class="GtkBox">
+                <property name="visible">True</property>
+                <property name="can_focus">False</property>
+                <property name="spacing">5</property> <!-- TODO: check HIG for right value -->
+                <child>
+                  <object class="GtkLabel">
+                    <property name="visible">True</property>
+                    <property name="can_focus">False</property>
+                    <property name="halign">start</property>
+                    <property name="label" translatable="yes">Automatic Location</property>
+                  </object>
+                  <packing>
+                    <property name="expand">True</property>
+                    <property name="fill">True</property>
+                    <property name="position">0</property>
+                  </packing>
+                </child>
+                <child>
+                  <object class="GtkSwitch" id="auto-location-switch">
+                    <property name="visible">True</property>
+                    <property name="can_focus">True</property>
+                  </object>
+                  <packing>
+                    <property name="expand">False</property>
+                    <property name="fill">True</property>
+                    <property name="position">1</property>
+                  </packing>
+                </child>
+              </object>
+              <packing>
+                <property name="expand">False</property>
+                <property name="fill">True</property>
+                <property name="position">2</property>
+              </packing>
+            </child>
+            <child>
+              <object class="GWeatherLocationEntry" id="fixed-weather-location">
+                <property name="visible">True</property>
+                <property name="can_focus">True</property>
+                <property name="primary_icon_name">edit-find-symbolic</property>
+                <property name="primary_icon_activatable">False</property>
+                <property name="primary_icon_sensitive">False</property>
+              </object>
+              <packing>
+                <property name="expand">False</property>
+                <property name="fill">True</property>
+                <property name="position">3</property>
+              </packing>
+            </child>
+          </object>
+          <packing>
+            <property name="name">win-menu-weather</property>
+          </packing>
+        </child>
+      </object>
+    </child>
+  </object>
   <menu id="add-source-menu">
     <section>
       <item>
diff --git a/src/gcal-window.c b/src/gcal-window.c
index 9202920..c4468c6 100644
--- a/src/gcal-window.c
+++ b/src/gcal-window.c
@@ -33,9 +33,10 @@
 #include "gcal-week-view.h"
 #include "gcal-window.h"
 #include "gcal-year-view.h"
+#include "gcal-weather-service.h"
 
 #include <glib/gi18n.h>
-
+#include <libgweather/gweather.h>
 #include <libecal/libecal.h>
 #include <libical/icaltime.h>
 
@@ -95,6 +96,21 @@ typedef struct
   gchar *uuid;
 } OpenEditDialogData;
 
+/* WeatherSigs:
+ * @instance: (transfer none) Instance this signal is connected to.
+ * @sig_id: Signal uid
+ *
+ * Describes a signal used in weather-menu that triggers
+ * weather service or weather menu changes.
+ *
+ * Used to block signals when loading settings.
+ */
+typedef struct
+{
+  GObject *obj; /* unowned */
+  gulong   id;
+} WeatherSigs;
+
 struct _GcalWindow
 {
   GtkApplicationWindow parent;
@@ -161,12 +177,18 @@ struct _GcalWindow
   gint                 refresh_timeout_id;
   gint                 open_edit_dialog_timeout_id;
 
+  /* weather management */
+  GcalWeatherService    *weather_service; /* owned */
+  GtkSwitch             *weather;         /* unowned */
+  GtkSwitch             *auto_location;   /* unowned */
+  GWeatherLocationEntry *location_entry;  /* unowned */
+  WeatherSigs            weather_cb_ids[5];
+
   /* temp to keep event_creation */
   gboolean             open_edit_dialog;
 
   /* handler for the searh_view */
   gint                 click_outside_handler_id;
-
 };
 
 enum
@@ -174,6 +196,7 @@ enum
   PROP_0,
   PROP_ACTIVE_VIEW,
   PROP_MANAGER,
+  PROP_WEATHER_SERVICE,
   PROP_ACTIVE_DATE,
   PROP_NEW_EVENT_MODE
 };
@@ -200,6 +223,49 @@ static void           on_view_action_activated           (GSimpleAction       *a
                                                           GVariant            *param,
                                                           gpointer             user_data);
 
+static void           on_menu_button_clicked             (GtkMenuButton       *menu,
+                                                          GtkStack            *stack);
+
+static void           on_view_menu_open_sub              (GtkModelButton      *button,
+                                                          GtkStack            *stack);
+
+static void           on_view_menu_open_parent           (GtkModelButton      *button,
+                                                          GtkStack            *stack);
+
+static void           on_weather_location_searchbox_change (GWeatherLocationEntry *entry,
+                                                            GcalWindow            *self);
+
+static void           on_show_weather_change             (GtkSwitch           *wswitch,
+                                                          GParamSpec          *pspec,
+                                                          GcalWindow          *self);
+
+static void           on_auto_location_change            (GtkSwitch           *lswitch,
+                                                          GParamSpec          *pspec,
+                                                          GcalWindow          *self);
+
+static gboolean       on_weather_location_searchbox_match_selected
+                                                         (GtkEntryCompletion  *sender,
+                                                          GtkTreeModel        *model,
+                                                          GtkTreeIter         *iter,
+                                                          GcalWindow          *self);
+
+static void           set_model_button_animation         (GtkBuilder          *builder,
+                                                          GtkStack            *stack,
+                                                          const char          *id,
+                                                          gboolean             go_back);
+static void           update_menu_weather_sensitivity    (GcalWindow          *self);
+
+
+static void           close_menu                         (GcalWindow          *self);
+
+static void           safe_weather_settings              (GcalWindow          *self);
+
+static void           load_weather_settings              (GcalWindow *self);
+
+static void           manage_weather_service             (GcalWindow *self);
+
+static GWeatherLocation* get_checked_fixed_location      (GcalWindow *self);
+
 G_DEFINE_TYPE (GcalWindow, gcal_window, GTK_TYPE_APPLICATION_WINDOW)
 
 static const GActionEntry actions[] = {
@@ -1305,9 +1371,11 @@ gcal_window_finalize (GObject *object)
   g_clear_object (&window->views_switcher);
 
   g_clear_pointer (&window->active_date, g_free);
-
   G_OBJECT_CLASS (gcal_window_parent_class)->finalize (object);
 
+  gcal_weather_service_stop (window->weather_service);
+  g_clear_object (&window->weather_service);
+
   GCAL_EXIT;
 }
 
@@ -1370,6 +1438,19 @@ gcal_window_set_property (GObject      *object,
         }
       return;
 
+    case PROP_WEATHER_SERVICE:
+      {
+        GcalWeatherService *service; /* unowned */
+
+        service = GCAL_WEATHER_SERVICE (g_value_get_object (value));
+        g_return_if_fail (service != NULL);
+
+        g_return_if_fail (self->weather_service == NULL);
+        self->weather_service = (GcalWeatherService*) g_object_ref (service);
+        g_object_notify (object, "weather-service");
+        return ;
+      }
+
     default:
       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
     }
@@ -1395,6 +1476,10 @@ gcal_window_get_property (GObject    *object,
       g_value_set_object (value, self->manager);
       return;
 
+    case PROP_WEATHER_SERVICE:
+      g_value_set_object (value, self->weather_service);
+      return;
+
     case PROP_ACTIVE_DATE:
       g_value_set_boxed (value, self->active_date);
       return;
@@ -1500,6 +1585,15 @@ gcal_window_class_init (GcalWindowClass *klass)
 
   g_object_class_install_property (
       object_class,
+      PROP_WEATHER_SERVICE,
+      g_param_spec_object ("weather-service",
+                           "The weather service object",
+                           "The weather service object",
+                           GCAL_TYPE_WEATHER_SERVICE,
+                           G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE));
+
+  g_object_class_install_property (
+      object_class,
       PROP_ACTIVE_DATE,
       g_param_spec_boxed ("active-date",
                           "Date",
@@ -1582,7 +1676,9 @@ gcal_window_init (GcalWindow *self)
 {
   GApplication *app;
   GtkBuilder *builder;
-  GMenuModel *winmenu;
+  GtkPopoverMenu *winmenu; /* unowned */
+  GtkStack *winmenustack;  /* unowned */
+  GtkEntryCompletion *location_completion; /* unowned */
   GSettings *helper_settings;
   gchar *clock_format;
   gboolean use_24h_format;
@@ -1611,9 +1707,32 @@ gcal_window_init (GcalWindow *self)
                                  "/org/gnome/calendar/gtk/menus.ui",
                                  NULL);
 
-  winmenu = (GMenuModel *)gtk_builder_get_object (builder, "win-menu");
-  gtk_menu_button_set_menu_model (GTK_MENU_BUTTON (self->menu_button),
-                                  winmenu);
+  winmenu = (GtkPopoverMenu *)gtk_builder_get_object (builder, "win-menu");
+  gtk_menu_button_set_popover (GTK_MENU_BUTTON (self->menu_button),
+                               GTK_WIDGET (winmenu));
+  /* Set-up menu animations: */
+  winmenustack = GTK_STACK (gtk_builder_get_object (builder, "win-menu-stack"));
+  g_signal_connect (GTK_BUTTON (self->menu_button), "clicked", (GCallback) on_menu_button_clicked, 
winmenustack);
+  set_model_button_animation (builder, winmenustack, "win-menu-open-weather", TRUE);
+  set_model_button_animation (builder, winmenustack, "win-menu-close-weather", FALSE);
+
+  /* Weather menu: */
+  self->weather_service = NULL;
+  self->weather = GTK_SWITCH(gtk_builder_get_object (builder, "show-weather-switch"));
+  self->auto_location = GTK_SWITCH(gtk_builder_get_object (builder, "auto-location-switch"));
+  self->location_entry = GWEATHER_LOCATION_ENTRY (gtk_builder_get_object (builder, 
"fixed-weather-location"));
+  location_completion = gtk_entry_get_completion (GTK_ENTRY (self->location_entry));
+  /* Store signals that need to be blocked when loading weather information */
+  self->weather_cb_ids[0].id = g_signal_connect (self->location_entry, "changed", (GCallback) 
on_weather_location_searchbox_change, self);
+  self->weather_cb_ids[0].obj = G_OBJECT (self->location_entry);
+  self->weather_cb_ids[1].id = g_signal_connect (self->location_entry, "activate", (GCallback) 
on_weather_location_searchbox_change, self);
+  self->weather_cb_ids[1].obj = G_OBJECT (self->location_entry);
+  self->weather_cb_ids[2].id = g_signal_connect (location_completion, "match-selected", (GCallback) 
on_weather_location_searchbox_match_selected, self);
+  self->weather_cb_ids[2].obj = G_OBJECT (location_completion);
+  self->weather_cb_ids[3].id = g_signal_connect (self->weather, "notify::active", (GCallback) 
on_show_weather_change, self);
+  self->weather_cb_ids[3].obj = G_OBJECT (self->weather);
+  self->weather_cb_ids[4].id = g_signal_connect (self->auto_location, "notify::active", (GCallback) 
on_auto_location_change, self);
+  self->weather_cb_ids[4].obj = G_OBJECT (self->auto_location);
 
   g_object_unref (builder);
 
@@ -1651,6 +1770,7 @@ gcal_window_init (GcalWindow *self)
    * FIXME: this is a hack around the issue that happens when trying to bind
    * there properties using the GtkBuilder .ui file.
    */
+  g_object_bind_property (self, "weather-service", self->week_view, "weather-service", G_BINDING_DEFAULT);
   g_object_bind_property (self, "manager", self->edit_dialog, "manager", G_BINDING_DEFAULT);
   g_object_bind_property (self, "manager", self->source_dialog, "manager", G_BINDING_DEFAULT);
   g_object_bind_property (self, "manager", self->week_view, "manager", G_BINDING_DEFAULT);
@@ -1672,6 +1792,392 @@ gcal_window_init (GcalWindow *self)
   gcal_window_add_accelerator (app, "win.change-view(3)",  "<Ctrl>3");
 }
 
+
+/* set_model_button_animation:
+ * @builder: Builder including menu buttons
+ * @id: name of the menu button to modify
+ * @go_back: Whether this button jumps back to a parent menu
+ *
+ * Used to set right stack animations for hand-written menus.
+ */
+static void
+set_model_button_animation (GtkBuilder *builder,
+                            GtkStack   *stack,
+                            const char *button_id,
+                            gboolean    go_back)
+{
+  g_return_if_fail (GTK_IS_BUILDER (builder));
+  g_return_if_fail (GTK_IS_STACK (stack));
+  g_return_if_fail (button_id != NULL);
+
+  GtkMenuButton *mbutton; /* unowned */
+
+  mbutton = GTK_MENU_BUTTON (gtk_builder_get_object (builder, button_id));
+  g_return_if_fail (mbutton != NULL);
+
+  if (go_back)
+    g_signal_connect (mbutton, "clicked", (GCallback) on_view_menu_open_parent, stack);
+  else
+    g_signal_connect (mbutton, "clicked", (GCallback) on_view_menu_open_sub, stack);
+}
+
+/* on_menu_button_clicked:
+ * @menu: primary menu button
+ * @stack: main menu popover stack
+ *
+ * Resets menu stack state before opening menu-popovers.
+ */
+static void
+on_menu_button_clicked (GtkMenuButton *menu,
+                        GtkStack      *stack)
+{
+  g_return_if_fail (GTK_IS_MENU_BUTTON (menu));
+  g_return_if_fail (GTK_IS_STACK (stack));
+
+  gtk_stack_set_visible_child_name (stack, "win-menu-main");
+  gtk_stack_set_transition_type (stack, GTK_STACK_TRANSITION_TYPE_SLIDE_LEFT);
+}
+
+/* on_view_menu_open_sub:
+ * @button: clicked menu entry
+ * @stack: main menu popover stack
+ *
+ * Callback used to set right stack animations for sub-menus.
+ */
+static void
+on_view_menu_open_sub (GtkModelButton *button,
+                       GtkStack       *stack)
+{
+  g_return_if_fail (GTK_IS_MODEL_BUTTON (button));
+  g_return_if_fail (GTK_IS_STACK (stack));
+
+  gtk_stack_set_transition_type (stack, GTK_STACK_TRANSITION_TYPE_SLIDE_LEFT);
+}
+
+/* on_view_menu_open_sub:
+ * @button: clicked menu entry
+ * @stack: Main menu popover stack
+ *
+ * Callback used to select right stack animation when jumping back to a parent menu.
+ */
+static void
+on_view_menu_open_parent (GtkModelButton *button,
+                          GtkStack       *stack)
+{
+  g_return_if_fail (GTK_IS_MODEL_BUTTON (button));
+  g_return_if_fail (GTK_IS_STACK (stack));
+
+  gtk_stack_set_transition_type (stack, GTK_STACK_TRANSITION_TYPE_SLIDE_RIGHT);
+}
+
+/* on_show_weather_change:
+ * @lswitch: The "show-weather" switch owned by @self
+ * @pspec: spec of changed property.
+ * @self: The #GcalWindow
+ *
+ * Handles show-weather on/off switch changes.
+ */
+static void
+on_show_weather_change (GtkSwitch  *wswitch,
+                        GParamSpec *pspec,
+                        GcalWindow *self)
+{
+  g_return_if_fail (GTK_IS_SWITCH (wswitch));
+  g_return_if_fail (pspec != NULL);
+  g_return_if_fail (GCAL_IS_WINDOW (self));
+
+  safe_weather_settings (self);
+
+  if (!gtk_switch_get_active (wswitch))
+    close_menu (self);
+
+  update_menu_weather_sensitivity (self);
+  manage_weather_service (self);
+}
+
+/* on_auto_location_change:
+ * @lswitch: The "auto-location" switch owned by @self
+ * @pspec: spec of changed property.
+ * @self: The #GcalWindow
+ *
+ * Handles auto-location on/off switch changes.
+ */
+static void
+on_auto_location_change (GtkSwitch  *lswitch,
+                         GParamSpec *pspec,
+                         GcalWindow *self)
+{
+  g_return_if_fail (GTK_IS_SWITCH (lswitch));
+  g_return_if_fail (pspec != NULL);
+  g_return_if_fail (GCAL_IS_WINDOW (self));
+
+  safe_weather_settings (self);
+
+  if (gtk_switch_get_active (lswitch))
+    close_menu (self);
+
+  update_menu_weather_sensitivity (self);
+  manage_weather_service (self);
+}
+
+/* on_weather_location_searchbox_change:
+ * @entry: the #GweatherLocationEntry owned by @self
+ * @self: a #GcalWindow.
+ *
+ * Handles location search-box changes.
+ */
+static void
+on_weather_location_searchbox_change (GWeatherLocationEntry *entry,
+                                      GcalWindow            *self)
+{
+  GWeatherLocation *location; /* owned */
+
+  g_return_if_fail (GWEATHER_IS_LOCATION_ENTRY (entry));
+  g_return_if_fail (GCAL_IS_WINDOW (self));
+
+  safe_weather_settings (self);
+
+  location = get_checked_fixed_location (self);
+  if (location != NULL)
+    {
+      close_menu (self);
+      manage_weather_service (self);
+      gweather_location_unref (location);
+    }
+}
+
+/* on_weather_location_searchbox_match_selected:
+ * @sender: the #GWeatherLocationEntry of @self.
+ * @model: underlining location model
+ * @iter: current position
+ * @self: a #GcalWindow as user data.
+ *
+ * Wrapper around on_weather_location_searchbox_change().
+ *
+ * Returns: %FALSE
+ */
+static gboolean
+on_weather_location_searchbox_match_selected (GtkEntryCompletion *sender,
+                                              GtkTreeModel       *model,
+                                              GtkTreeIter        *iter,
+                                              GcalWindow         *self)
+{
+  g_return_val_if_fail (GTK_IS_ENTRY_COMPLETION (sender), FALSE);
+  g_return_val_if_fail (GTK_IS_TREE_MODEL (model), FALSE);
+  g_return_val_if_fail (iter != NULL, FALSE);
+  g_return_val_if_fail (GCAL_IS_WINDOW (self), FALSE);
+
+  GWeatherLocationEntry *entry; /* unowned */
+
+  entry = GWEATHER_LOCATION_ENTRY (gtk_entry_completion_get_entry (sender));
+
+  on_weather_location_searchbox_change (entry, self);
+  return FALSE;
+}
+
+/* update_menu_weather_sensitivity:
+ * @self: A #GcalWindow
+ *
+ * Greys-out weather settings based on active settings.
+ */
+static void
+update_menu_weather_sensitivity (GcalWindow *self)
+{
+  g_return_if_fail (GCAL_IS_WINDOW (self));
+
+  gboolean weather_enabled;
+  gboolean autoloc_enabled;
+
+  weather_enabled = gtk_switch_get_active (self->weather);
+  autoloc_enabled = gtk_switch_get_active (self->auto_location);
+
+  gtk_widget_set_sensitive (GTK_WIDGET (self->auto_location), weather_enabled);
+  gtk_widget_set_sensitive (GTK_WIDGET (self->location_entry), weather_enabled && !autoloc_enabled);
+}
+
+/* close_menu:
+ * @self: A #GcalWindow
+ *
+ * Closes the main menu ("burger") if open.
+ */
+static void
+close_menu (GcalWindow *self)
+{
+  g_return_if_fail (GCAL_IS_WINDOW (self));
+
+  gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (self->menu_button), FALSE);
+}
+
+/* safe_weather_settings.
+ * @self: A #GcalWindow
+ *
+ * Stores current weather settings. This
+ * includes invalid states.
+ */
+static void
+safe_weather_settings (GcalWindow *self)
+{
+  g_return_if_fail (GCAL_IS_WINDOW (self));
+
+  GSettings *settings;        /* unowned */
+  GVariant *value = NULL;     /* floating */
+  GVariant *vlocation = NULL; /* floating */
+  GWeatherLocation *location; /* owned */
+  gboolean res;
+
+  location = gweather_location_entry_get_location (self->location_entry);
+  if (location != NULL)
+    {
+      vlocation = gweather_location_serialize (location);
+      gweather_location_unref (location);
+      location = NULL;
+    }
+
+  settings = gcal_manager_get_settings (self->manager);
+  value = g_variant_new ("(bbsmv)",
+                         gtk_switch_get_active (self->weather),
+                         gtk_switch_get_active (self->auto_location),
+                         gtk_entry_get_text (GTK_ENTRY (self->location_entry)),
+                         vlocation);
+
+  res = g_settings_set_value (settings, "weather-settings", value);
+
+  if (!res)
+    g_warning ("Could not persist weather settings");
+}
+
+/* load_weather_settings:
+ * @self: A #GcalWindow.
+ *
+ * Loads registered user settings.
+ *
+ * Does not start weather-services on its own.
+ */
+static void
+load_weather_settings (GcalWindow *self)
+{
+  g_return_if_fail (GCAL_IS_WINDOW (self));
+
+  GSettings *settings; /* unowned */
+  g_autoptr (GVariant) value = NULL;
+  gboolean show_weather;
+  gboolean auto_location;
+  g_autoptr (GVariant) location = NULL;
+  g_autofree gchar *location_name = NULL;
+
+  settings = gcal_manager_get_settings (self->manager);
+  value = g_settings_get_value (settings, "weather-settings");
+
+  g_variant_get (value,
+                 "(bbsmv)",
+                 &show_weather,
+                 &auto_location,
+                 &location_name,
+                 &location);
+
+
+  for (int i = 0; i < G_N_ELEMENTS (self->weather_cb_ids); i++)
+    {
+      g_assert (self->weather_cb_ids[i].obj != NULL);
+      g_assert (self->weather_cb_ids[i].id > 0);
+
+      g_signal_handler_block (self->weather_cb_ids[i].obj, self->weather_cb_ids[i].id);
+    }
+
+  gtk_switch_set_active (self->weather, show_weather);
+  gtk_switch_set_active (self->auto_location, auto_location);
+  if (location == NULL)
+    {
+      gtk_entry_set_text (GTK_ENTRY (self->location_entry), location_name);
+    }
+  else
+    {
+      GWeatherLocation *world; /* unowned */
+      GWeatherLocation *loc;   /* owned */
+
+      world = gweather_location_get_world ();
+      loc = gweather_location_deserialize (world, location);
+      gweather_location_entry_set_location (self->location_entry, loc);
+      gweather_location_unref (loc);
+    }
+
+  for (int i = 0; i < G_N_ELEMENTS (self->weather_cb_ids); i++)
+     g_signal_handler_unblock (self->weather_cb_ids[i].obj, self->weather_cb_ids[i].id);
+}
+
+/* manage_weather_service:
+ * @self: A #GcalWindow
+ *
+ * Starts or stops weather service based on current
+ * settings.
+ */
+static void
+manage_weather_service (GcalWindow *self)
+{
+  g_return_if_fail (GCAL_IS_WINDOW (self));
+
+  gboolean show_weather;
+  gboolean auto_location;
+  GWeatherLocation* location; /* owned */
+
+  gcal_weather_service_stop (self->weather_service);
+
+  show_weather = gtk_switch_get_active (self->weather);
+  if (!show_weather)
+    return;
+
+  auto_location = gtk_switch_get_active (self->auto_location);
+  if (auto_location)
+    {
+      gcal_weather_service_run (self->weather_service, NULL);
+      return;
+    }
+
+  location = get_checked_fixed_location (self);
+  if (location == NULL)
+    {
+      /* TODO: this one should get reported to users */
+      g_warning ("Unknown location '%s' selected",
+                 gtk_entry_get_text (GTK_ENTRY (self->location_entry)));
+    }
+  else
+    {
+      gcal_weather_service_run (self->weather_service, location);
+      gweather_location_unref (location);
+    }
+}
+
+/* get_checked_fixed_location:
+ * @self: A #GcalWindow
+ *
+ * Returns user defined location or %NULL
+ * for invalid entries.
+ *
+ * Returns: (transfer full) (nullable): A #GweatherLocation.
+ */
+static GWeatherLocation*
+get_checked_fixed_location (GcalWindow *self)
+{
+  g_return_val_if_fail (GCAL_IS_WINDOW (self), NULL);
+
+  GWeatherLocation *location; /* owned */
+
+  location = gweather_location_entry_get_location (self->location_entry);
+
+  // NOTE: This check feels shabby. However,
+  // I couldn't find a better one without iterating
+  // the model. has-custom-text does not work properly.
+  // Lets go with it for now.
+  if (location != NULL && gweather_location_get_name (location) != NULL)
+    return location;
+
+  if (location != NULL)
+    gweather_location_unref (location);
+
+  return NULL;
+}
+
+
 /* Public API */
 /**
  * gcal_window_new_with_date:
@@ -1686,12 +2192,15 @@ GtkWidget*
 gcal_window_new_with_date (GcalApplication *app,
                            icaltimetype    *date)
 {
-  GcalManager *manager;
-  GcalWindow *win;
+  GcalWeatherService *weather_service; /* unowned */
+  GcalManager *manager; /* unowned */
+  GcalWindow *win;      /* owned */
 
+  weather_service = gcal_application_get_weather_service (GCAL_APPLICATION (app));
   manager = gcal_application_get_manager (GCAL_APPLICATION (app));
   win = g_object_new (GCAL_TYPE_WINDOW,
                       "application", GTK_APPLICATION (app),
+                      "weather-service", weather_service,
                       "manager", manager,
                       "active-date", date,
                       NULL);
@@ -1699,6 +2208,11 @@ gcal_window_new_with_date (GcalApplication *app,
   /* loading size */
   load_geometry (win);
 
+  /* Recover weather settings */
+  load_weather_settings (win);
+  update_menu_weather_sensitivity (win);
+  manage_weather_service (win);
+
   return GTK_WIDGET (win);
 }
 
diff --git a/src/views/gcal-week-view.c b/src/views/gcal-week-view.c
index 0d2fc51..5e65d47 100644
--- a/src/views/gcal-week-view.c
+++ b/src/views/gcal-week-view.c
@@ -27,6 +27,7 @@
 #include "gcal-week-header.h"
 #include "gcal-week-grid.h"
 #include "gcal-week-view.h"
+#include "gcal-weather-service.h"
 
 #include <glib/gi18n.h>
 
@@ -61,8 +62,9 @@ struct _GcalWeekView
   gboolean        use_24h_format;
 
   /* property */
-  icaltimetype   *date;
-  GcalManager    *manager; /* owned */
+  icaltimetype       *date;
+  GcalManager        *manager;         /* owned */
+  GcalWeatherService *weather_service; /* owned */
 
   guint           scroll_grid_timeout_id;
 
@@ -86,6 +88,7 @@ enum
   PROP_0,
   PROP_DATE,
   PROP_MANAGER,
+  PROP_WEATHER_SERVICE,
   NUM_PROPS
 };
 
@@ -522,6 +525,9 @@ gcal_week_view_finalize (GObject       *object)
 
   g_clear_object (&self->manager);
 
+  if (self->weather_service != NULL)
+    g_clear_object (&self->weather_service);
+
   /* Chain up to parent's finalize() method. */
   G_OBJECT_CLASS (gcal_week_view_parent_class)->finalize (object);
 }
@@ -545,10 +551,28 @@ gcal_week_view_set_property (GObject       *object,
 
       gcal_week_grid_set_manager (GCAL_WEEK_GRID (self->week_grid), self->manager);
       gcal_week_header_set_manager (GCAL_WEEK_HEADER (self->header), self->manager);
-
       g_object_notify (object, "manager");
       break;
 
+    case PROP_WEATHER_SERVICE:
+      {
+        GcalWeatherService* weather_service; /* unowned */
+        weather_service = g_value_get_object (value);
+
+        g_return_if_fail (weather_service == NULL || GCAL_IS_WEATHER_SERVICE (weather_service));
+
+        if (self->weather_service != weather_service)
+          {
+            g_set_object (&self->weather_service, weather_service);
+
+            gcal_week_header_set_weather_service (GCAL_WEEK_HEADER (self->header),
+                                                  self->weather_service);
+
+            g_object_notify (object, "weather-service");
+          }
+        break;
+      }
+
     default:
       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
       break;
@@ -576,6 +600,10 @@ gcal_week_view_get_property (GObject       *object,
       g_value_set_object (value, self->manager);
       break;
 
+    case PROP_WEATHER_SERVICE:
+      g_value_set_object (value, self->weather_service);
+      break;
+
     default:
       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
       break;
@@ -598,6 +626,18 @@ gcal_week_view_class_init (GcalWeekViewClass *klass)
   g_object_class_override_property (object_class, PROP_DATE, "active-date");
   g_object_class_override_property (object_class, PROP_MANAGER, "manager");
 
+  /**
+   * GcalWeekView:weather-service:
+   *
+   * Sets the weather service to use.
+   */
+  g_object_class_install_property
+      (object_class,
+       PROP_WEATHER_SERVICE,
+       g_param_spec_object ("weather-service", "weather-service", "weather-service",
+                            GCAL_TYPE_WEATHER_SERVICE,
+                            G_PARAM_STATIC_STRINGS | G_PARAM_READWRITE));
+
   signals[EVENT_ACTIVATED] = g_signal_new ("event-activated",
                                            GCAL_TYPE_WEEK_VIEW,
                                            G_SIGNAL_RUN_FIRST,
@@ -625,6 +665,8 @@ gcal_week_view_init (GcalWeekView *self)
   gtk_widget_init_template (GTK_WIDGET (self));
 
   update_hours_sidebar_size (self);
+
+  self->weather_service = NULL;
 }
 
 /* Public API */


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