[tempo] all: make WeatherLoader a global model-like object



commit 16d4d36fe3700bf2bc7151b0ae31d72ae9a957ff
Author: Cosimo Cecchi <cosimoc gnome org>
Date:   Fri Sep 23 21:32:32 2011 -0400

    all: make WeatherLoader a global model-like object
    
    WeatherLoader can now emit three signals:
    - changed in case the weather has been successfully loaded (from cache
      or remotely)
    - error in case the weather has failed to remotely refresh
    - loading in case the weather is pending a remote refresh
    
    Use those signals from the window to cleanup the loading code.

 tempo/main.js          |   14 +++-
 tempo/mainWindow.js    |  185 ++++++++++++++++++++++-------------------------
 tempo/weatherLoader.js |  103 +++++++++++++++++----------
 3 files changed, 163 insertions(+), 139 deletions(-)
---
diff --git a/tempo/main.js b/tempo/main.js
index 4fd4225..722098f 100644
--- a/tempo/main.js
+++ b/tempo/main.js
@@ -18,6 +18,7 @@
  */
 
 const Gettext = imports.gettext;
+const Gio = imports.gi.Gio;
 const GLib = imports.gi.GLib;
 const Gdk = imports.gi.Gdk;
 const Gtk = imports.gi.Gtk;
@@ -26,6 +27,10 @@ const _ = imports.gettext.gettext;
 
 const Format = imports.format;
 const MainWindow = imports.mainWindow;
+const WeatherLoader = imports.weatherLoader;
+
+let settings = null;
+let loader = null;
 
 function start() {
     Gettext.bindtextdomain('tempo', Path.LOCALE_DIR);
@@ -35,14 +40,17 @@ function start() {
     GLib.set_prgname('tempo');
     Gtk.init(null, null);
 
-    let settings = Gtk.Settings.get_default();
-    settings.gtk_application_prefer_dark_theme = true;
+    let gtkSettings = Gtk.Settings.get_default();
+    gtkSettings.gtk_application_prefer_dark_theme = true;
 
     let provider = new Gtk.CssProvider();
     provider.load_from_path(Path.STYLE_DIR + "gtk-style.css");
     Gtk.StyleContext.add_provider_for_screen(Gdk.Screen.get_default(),
         provider, 600);
 
+    settings = Gio.Settings.new('org.gnome.tempo');
+    loader = new WeatherLoader.WeatherLoader(settings.get_string('last-location'));
     let mainWindow = new MainWindow.MainWindow();
+
     Gtk.main();
-}
\ No newline at end of file
+}
diff --git a/tempo/mainWindow.js b/tempo/mainWindow.js
index dbc7ec3..58ba729 100644
--- a/tempo/mainWindow.js
+++ b/tempo/mainWindow.js
@@ -18,7 +18,7 @@
  */
 
 const ForecastBox = imports.forecastBox;
-const WeatherLoader = imports.weatherLoader;
+const Main = imports.main;
 
 const Gdk = imports.gi.Gdk;
 const Gio = imports.gi.Gio;
@@ -30,12 +30,14 @@ const Path = imports.path;
 const Tempo = imports.gi.Tempo;
 const _ = imports.gettext.gettext;
 
-function MainWindow(args) {
+const _CONFIGURE_ID_TIMEOUT = 100; // msecs
+
+function MainWindow() {
     this._init();
 }
 
 MainWindow.prototype = {
-    _init : function(args) {
+    _init: function() {
         this._configureId = 0;
 
         this._gtkWindow = new Gtk.Window({ type: Gtk.WindowType.TOPLEVEL,
@@ -43,7 +45,6 @@ MainWindow.prototype = {
                                            has_resize_grip: false,
                                            resizable: false,
                                            window_position: Gtk.WindowPosition.CENTER });
-
         Tempo.ensure_rounded_corners(this._gtkWindow);
 
         this._gtkWindow.connect('delete-event',
@@ -53,20 +54,13 @@ MainWindow.prototype = {
         this._gtkWindow.connect('configure-event',
                                 Lang.bind(this, this._onConfigureEvent));
 
-        this._settings = Gio.Settings.new('org.gnome.tempo');
-        this._lastLocation = this._settings.get_string('last-location');
-
-        let position = this._settings.get_value('window-position');
-
-        if (position.n_children() == 2) {
-            let x = position.get_child_value(0);
-            let y = position.get_child_value(1);
-
-            this._gtkWindow.move(x.get_int32(),
-                                 y.get_int32());
-        }
-
+        // build an event box to trap clicks on the window
         this._eventBox = new Gtk.EventBox();
+        this._gtkWindow.add(this._eventBox);
+        this._eventBox.connect('button-press-event',
+                               Lang.bind(this, this._onButtonPressEvent));
+
+        // build the UI
         this._mainGrid = new Gtk.Grid({ orientation: Gtk.Orientation.VERTICAL,
                                         row_spacing: 12,
                                         margin_left: 12,
@@ -74,17 +68,8 @@ MainWindow.prototype = {
                                         margin_top: 6,
                                         margin_bottom: 6 });
         this._eventBox.add(this._mainGrid);
-        this._eventBox.connect('button-press-event', Lang.bind(this,
-            function(widget, event) {
-                let rootCoords = event.get_root_coords();
-                this._gtkWindow.begin_move_drag(1,
-                                                rootCoords[1],
-                                                rootCoords[2],
-                                                event.get_time());
-
-                return false;
-            }));
 
+        // decoGrid holds the pseudo window decorations
         this._decoGrid = new Gtk.Grid({ orientation: Gtk.Orientation.HORIZONTAL });
         this._mainGrid.add(this._decoGrid);
 
@@ -94,7 +79,7 @@ MainWindow.prototype = {
         this._decoGrid.add(this._cityLabel);
 
         this._settingsButton =
-            new Gtk.Button({ image: new Gtk.Image ({ icon_size: Gtk.IconSize.MENU,
+            new Gtk.Button({ child: new Gtk.Image ({ icon_size: Gtk.IconSize.MENU,
                                                      icon_name: 'system-run-symbolic',
                                                      halign: Gtk.Align.END }),
                              relief: Gtk.ReliefStyle.NONE });
@@ -102,17 +87,6 @@ MainWindow.prototype = {
                                      Lang.bind(this, this._showSettings));
         this._decoGrid.add(this._settingsButton);
 
-        this._settingsSpinner = new Gtk.Spinner();
-        this._settingsSpinner.set_size_request(16, 16);
-        this._settingsSpinner.start();
-        this._decoGrid.add(this._settingsSpinner);
-        this._settingsSpinner.show();
-
-        this._settingsButton.connect('notify::visible', Lang.bind(this,
-            function(){
-                this._settingsSpinner.set_visible(!this._settingsButton.get_visible());
-            }));
-
         this._quitButton =
             new Gtk.Button({ image: new Gtk.Image ({ icon_size: Gtk.IconSize.MENU,
                                                      icon_name: 'window-close-symbolic',
@@ -122,6 +96,7 @@ MainWindow.prototype = {
                                  Lang.bind(this, this._quitApp));
         this._decoGrid.add(this._quitButton);
 
+        // boxGrid holds the primary information
         this._boxGrid = new Gtk.Grid({ orientation: Gtk.Orientation.HORIZONTAL,
                                        column_spacing: 16 });
         this._mainGrid.add(this._boxGrid);
@@ -134,6 +109,7 @@ MainWindow.prototype = {
                                             name: 'tempo-degree-label' });
         this._boxGrid.add(this._degreeLabel);
 
+        // infoGrid holds the right side of the primary information
         this._infoGrid = new Gtk.Grid({ orientation: Gtk.Orientation.VERTICAL,
                                         row_spacing: 3 });
         this._boxGrid.add(this._infoGrid);
@@ -152,11 +128,13 @@ MainWindow.prototype = {
                                           name: 'tempo-detail-label' });
         this._infoGrid.add(this._windLabel);
 
+        // separator between the primary and secondary sections
         let separator = new Gtk.Separator({ orientation: Gtk.Orientation.HORIZONTAL,
                                             hexpand: true,
                                             name: 'tempo-separator' });
         this._mainGrid.add(separator);
 
+        // forecastGrid holds the bottom secondary information
         this._forecastGrid = new Gtk.Grid({ orientation: Gtk.Orientation.HORIZONTAL,
                                             hexpand: true,
                                             halign: Gtk.Align.CENTER,
@@ -166,44 +144,50 @@ MainWindow.prototype = {
         this._mainGrid.add(this._forecastGrid);
         this._createForecastGrid();
 
-        this._spinnerBox = new Gtk.Grid({ orientation: Gtk.Orientation.VERTICAL,
-                                          row_spacing: 12,
-                                          halign: Gtk.Align.CENTER,
-                                          valign: Gtk.Align.CENTER });
-        this._spinnerBox.set_border_width(18);
+        // connect to the loader signals and see if we can populate
+        // something from the cache already
+        Main.loader.connect('changed', Lang.bind(this, this._onLoaderChanged));
+        Main.loader.connect('loading', Lang.bind(this, this._onLoaderLoading));
+        Main.loader.connect('error', Lang.bind(this, this._onLoaderError));
+        this._onLoaderChanged(Main.loader);
 
-        this._spinner = new Gtk.Spinner();
-        this._spinner.set_size_request (48, 48);
-        this._spinner.start();
-        this._spinnerBox.add(this._spinner);
+        // show all
+        this._gtkWindow.show_all();
 
-        let label = new Gtk.Label ({ label: _("Loading...")});
-        this._spinnerBox.add(label);
+        // apply the last saved window position
+        let position = Main.settings.get_value('window-position');
+        if (position.n_children() == 2) {
+            let x = position.get_child_value(0);
+            let y = position.get_child_value(1);
 
-        this._gtkWindow.show();
-        this._startLoader();
+            this._gtkWindow.move(x.get_int32(),
+                                 y.get_int32());
+        }
 
-        Mainloop.timeout_add_seconds(this._settings.get_int('refresh-interval'), Lang.bind(this,
-            function() {
-                this._startLoader();
-                return true;
-            }));
+        if (Main.settings.get_string('last-location') == '') {
+            this._showSettings();
+            return;
+        }
     },
 
     _quitApp : function() {
+        // remove configure event handler if still there
         if (this._configureId != 0) {
             Mainloop.source_remove(this._configureId);
             this._configureId = 0;
-            this._saveWindowPosition();
         }
 
+        // always save before quitting
+        this._saveWindowPosition();
+
         Gtk.main_quit();
     },
 
     _saveWindowPosition: function() {
+        // GLib.Variant.new() can handle arrays just fine
         let position = this._gtkWindow.get_position();
         let variant = GLib.Variant.new ('ai', position);
-        this._settings.set_value('window-position', variant);
+        Main.settings.set_value('window-position', variant);
     },
 
     _onConfigureEvent: function(widget, event) {
@@ -212,7 +196,7 @@ MainWindow.prototype = {
             this._configureId = 0;
         }
 
-        this._configureId = Mainloop.timeout_add(100, Lang.bind(this,
+        this._configureId = Mainloop.timeout_add(_CONFIGURE_ID_TIMEOUT, Lang.bind(this,
             function() {
                 this._saveWindowPosition();
                 return false;
@@ -223,6 +207,7 @@ MainWindow.prototype = {
         let keyval = event.get_keyval()[1];
         let state = event.get_state()[1];
 
+        // quit with Ctrl+Q
         if ((keyval == Gdk.KEY_q) &&
             ((state & Gdk.ModifierType.CONTROL_MASK) != 0)) {
             this._quitApp();
@@ -232,6 +217,17 @@ MainWindow.prototype = {
         return false;
     },
 
+    _onButtonPressEvent: function(widget, event) {
+        // drag on click
+        let rootCoords = event.get_root_coords();
+            this._gtkWindow.begin_move_drag(1,
+                                            rootCoords[1],
+                                            rootCoords[2],
+                                            event.get_time());
+
+        return false;
+    },
+
     _onEntryChanged : function() {
         this._okButton.set_sensitive((this._settingsEntry.get_text() != ""));
     },
@@ -266,7 +262,7 @@ MainWindow.prototype = {
         this._settingsEntry.connect('changed',
                                     Lang.bind(this, this._onEntryChanged));
 
-        this._settingsEntry.set_text(this._lastLocation);
+        this._settingsEntry.set_text(Main.settings.get_string('last-location'));
         grid.add(this._settingsEntry);
 
         content.pack_start(grid, true, true, 6);
@@ -282,11 +278,9 @@ MainWindow.prototype = {
         if (id == Gtk.ResponseType.ACCEPT) {
             let newLocation = this._settingsEntry.get_text();
 
-            if (newLocation != this._lastLocation) {
-                this._lastLocation = this._settingsEntry.get_text();
-                this._settings.set_string('last-location', this._lastLocation);
-
-                this._startLoader();
+            if (newLocation != Main.settings.get_string('last-location')) {
+                Main.settings.set_string('last-location', newLocation);
+                Main.loader.setLocation(newLocation);
             }
         }
 
@@ -314,42 +308,37 @@ MainWindow.prototype = {
         }
     },
 
-    _startLoader : function() {
-        if (this._lastLocation == "") {
-            this._showSettings();
-            return;
-        }
+    _onLoaderError: function(loader) {
+        this._settingsButton.get_child().destroy();
+        this._settingsButton.child = new Gtk.Image ({ icon_size: Gtk.IconSize.MENU,
+                                                      icon_name: 'dialog-warning-symbolic',
+                                                      halign: Gtk.Align.END,
+                                                      visible: true });
+    },
 
-        this._loader = new WeatherLoader.WeatherLoader(this._lastLocation);
-        this._loader.loadCache(Lang.bind(this, this._onWeatherRefreshed));
-        this._loader.refreshWeather(Lang.bind(this, this._onWeatherRefreshed));
+    _onLoaderLoading: function(loader) {
+        let spinner = new Gtk.Spinner();
+        spinner.set_size_request(16, 16);
+        spinner.start();
+        spinner.show();
 
-        if (this._eventBox.get_visible())
-            this._settingsButton.hide();
+        this._settingsButton.get_child().destroy();
+        this._settingsButton.child = spinner;
     },
 
-    _onWeatherRefreshed : function(refreshed) {
-        let weather = this._loader.googleXML;
-
-        if (weather == null) {
-            this._gtkWindow.add(this._spinnerBox);
-            this._gtkWindow.show_all();
+    _onLoaderChanged : function(loader) {
+        let weather = loader.getXml();
+        let props = loader.getProps();
 
+        if (weather == null || props == null) {
             return;
         }
 
-        if (!this._eventBox.get_visible()) {
-            this._spinnerBox.destroy();
-            this._gtkWindow.add(this._eventBox);
-            this._gtkWindow.show_all();
-        } else {
-            this._settingsButton.show();
-        }
-
-        if (!refreshed)
-            this._settingsButton.image = new Gtk.Image ({ icon_size: Gtk.IconSize.MENU,
-                                                          icon_name: 'dialog-warning-symbolic',
-                                                          halign: Gtk.Align.END });
+        this._settingsButton.get_child().destroy();
+        this._settingsButton.child = new Gtk.Image ({ icon_size: Gtk.IconSize.MENU,
+                                                      icon_name: 'system-run-symbolic',
+                                                      halign: Gtk.Align.END,
+                                                      visible: true });
 
         let iconName = this._getWeatherIcon(weather current_conditions icon  data toString());
         this._icon.icon_name = iconName;
@@ -369,12 +358,12 @@ MainWindow.prototype = {
         this._degreeLabel.set_markup(temp + ' ' + tempUnit);
 
         let state;
-        if (this._loader.props['countrycode'] == 'US')
-            state = this._loader.props['statecode'];
+        if (props['countrycode'] == 'US')
+            state = props['statecode'];
         else
-            state = this._loader.props['country'];
+            state = props['country'];
 
-        let location = this._loader.props['city'] + ', ' + state;
+        let location = props['city'] + ', ' + state;
         this._cityLabel.set_text(location);
 
         let comment = weather current_conditions condition  data toString();
diff --git a/tempo/weatherLoader.js b/tempo/weatherLoader.js
index c75d5ef..83d6200 100644
--- a/tempo/weatherLoader.js
+++ b/tempo/weatherLoader.js
@@ -19,6 +19,8 @@
 
 const GOOGLE_URL = "http://www.google.com/ig/api?hl=%s&weather=%s";;
 
+const Signals = imports.signals;
+
 const Geocode = imports.gi.GeocodeGlib;
 const GLib = imports.gi.GLib;
 const Json = imports.gi.Json;
@@ -34,12 +36,33 @@ function WeatherLoader(location) {
 WeatherLoader.prototype = {
     _init : function(location) {
         this._location = location;
-        this.googleXML = null;
-        this.props = null;
+        this._googleXml = null;
+        this._props = null;
 
         this.loadCache();
     },
 
+    setLocation: function(location) {
+        if (this._location == location)
+            return;
+
+        if (this._location == null ||
+            this._location == '')
+            return;
+
+        this._props = null;
+        this._location = location;
+        this._refreshWeather();
+    },
+
+    getXml: function() {
+        return this._googleXml;
+    },
+
+    getProps: function() {
+        return this._props;
+    },
+
     loadCache: function(callback) {
         let cachePath = GLib.get_user_cache_dir() + '/tempo/last_cache.xml';
 
@@ -48,46 +71,50 @@ WeatherLoader.prototype = {
             let contentsStr = contents[1].toString();
 
             if (contents[0]) {
-                this.googleXML = new XML(contentsStr);
-                this._resolveWeather(false, Lang.bind(this,
-                    function() {
-                        callback(true);
+                this._googleXml = new XML(contentsStr);
+
+                let geoCode = new Geocode.Object();
+                geoCode.add('location', this._location);
+
+                geoCode.resolve_async(null, Lang.bind(this,
+                    function(obj, res) {
+                        try {
+                            let props = geoCode.resolve_finish(res);
+                            this._props = props;
+
+                            this.emit('changed');
+                        } catch (e) {
+                            log('Unable to resolve weather location ' + this._location + ': ' + e.toString());
+                        }
                     }));
             }
         } catch (e) {
-            Mainloop.idle_add(
-                function() {
-                    callback(true);
-                    return false;
-                });
+            log('Unable to load weather cache: ' + e.toString());
         }
     },
 
-    _resolveWeather: function(refresh, callback) {
-        this._callback = callback;
+    _refreshWeather: function(callback) {
+        this.emit('loading');
 
-        let geoCode = new Geocode.Object();
+        if (!this._props) {
+            let geoCode = new Geocode.Object();
+            geoCode.add('location', this._location);
 
-        geoCode.add('location', this._location);
-        geoCode.resolve_async(null, Lang.bind(this,
-            function(obj, res) {
-                try {
-                    let props = geoCode.resolve_finish(res);
-                    this.props = props;
+            geoCode.resolve_async(null, Lang.bind(this,
+                function(obj, res) {
+                    try {
+                        let props = geoCode.resolve_finish(res);
+                        this._props = props;
 
-                    if (refresh)
                         this._getWeather();
-                    else
-                        this._callback(true);
-                } catch (e) {
-                    log('Unable to resolve weather location ' + this._location + ': ' + e.toString());
-                    this._callback(false);
-                }
-            }));
-    },
-
-    refreshWeather: function(callback) {
-        this._resolveWeather(true, callback);
+                    } catch (e) {
+                        log('Unable to resolve weather location ' + this._location + ': ' + e.toString());
+                        this.emit('error');
+                    }
+                }));
+        } else {
+            this._getWeather();
+        }
     },
 
     _getLanguage : function() {
@@ -111,18 +138,17 @@ WeatherLoader.prototype = {
     },
 
     _onResponseReceived : function(session, message) {
-        let cbval = true;
         let utf8 = Weather.convert_response_to_utf8(message);
 
         try {
-            this.googleXML = new XML(utf8.substr(21)).*;
+            this._googleXml = new XML(utf8.substr(21)).*;
             this._saveCache();
+
+            this.emit('changed');
         } catch (e) {
             log('Unable to parse the remote XML response: ' + e.toString());
-            cbval = false;
+            this.emit('error');
         }
-
-        this._callback(cbval);
     },
 
     _saveCache: function() {
@@ -132,9 +158,10 @@ WeatherLoader.prototype = {
         cachePath += '/last_cache.xml';
 
         try {
-            GLib.file_set_contents(cachePath, this.googleXML.toXMLString());
+            GLib.file_set_contents(cachePath, this._googleXml.toXMLString());
         } catch (e) {
             log('Unable to save cache file: ' + e.toString());
         }
     }
 };
+Signals.addSignalMethods(WeatherLoader.prototype);



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