[gnome-weather] Add a world view
- From: Giovanni Campagna <gcampagna src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [gnome-weather] Add a world view
- Date: Mon, 4 Mar 2013 22:04:52 +0000 (UTC)
commit fcebb9795d34046c4ba3ba7dcf0ef6dfff6ec706
Author: Giovanni Campagna <gcampagna src gnome org>
Date: Sat Feb 23 17:16:51 2013 +0100
Add a world view
Following the GNOME 3 style of big icon views, add a world view that
shows the current conditions of multiple cities at once.
Clicking on each city allows to see detailed conditions and forecasts.
configure.ac | 2 +-
data/Makefile.am | 12 ++-
data/application.css | 4 +
data/org.gnome.Weather.Application.gschema.xml | 12 ++
src/Makefile.am | 3 +-
src/{view.js => city.js} | 37 ++++--
src/forecast.js | 9 +--
src/main.js | 7 +-
src/util.js | 41 ++++++
src/window.js | 154 ++++++++++++++++-------
src/world.js | 161 ++++++++++++++++++++++++
11 files changed, 374 insertions(+), 68 deletions(-)
---
diff --git a/configure.ac b/configure.ac
index 5d871e4..d9a7798 100644
--- a/configure.ac
+++ b/configure.ac
@@ -18,7 +18,7 @@ AC_PROG_CC
AM_PROG_CC_C_O
LT_INIT([disable-static])
-LIBGD_INIT([header-bar stack revealer gir])
+LIBGD_INIT([header-bar main-toolbar main-view stack revealer gir])
PKG_PROG_PKG_CONFIG([0.22])
diff --git a/data/Makefile.am b/data/Makefile.am
index 326b97c..56f6681 100644
--- a/data/Makefile.am
+++ b/data/Makefile.am
@@ -6,7 +6,15 @@ apps_DATA = gnome-weather.desktop
@INTLTOOL_DESKTOP_RULE@
-EXTRA_DIST = gnome-weather.desktop.in
-CLEANFILES = $(apps_DATA)
+gsettings_SCHEMAS = org.gnome.Weather.Application.gschema.xml
+
+ GSETTINGS_RULES@
+
+EXTRA_DIST = gnome-weather.desktop.in $(gsettings_SCHEMAS)
+CLEANFILES = $(apps_DATA) *.valid gschemas.compiled
+
+# For uninstalled use
+all-local:
+ $(GLIB_COMPILE_SCHEMAS) $(builddir)
include $(top_srcdir)/git.mk
diff --git a/data/application.css b/data/application.css
index bf3cc03..d6a4144 100644
--- a/data/application.css
+++ b/data/application.css
@@ -21,3 +21,7 @@
#conditions-image {
padding-right: 12px;
}
+
+.content-view.cell {
+ font-weight: bold;
+}
\ No newline at end of file
diff --git a/data/org.gnome.Weather.Application.gschema.xml b/data/org.gnome.Weather.Application.gschema.xml
new file mode 100644
index 0000000..dc36225
--- /dev/null
+++ b/data/org.gnome.Weather.Application.gschema.xml
@@ -0,0 +1,12 @@
+<schemalist gettext-domain="gnome-weather">
+ <schema id="org.gnome.Weather.Application" path="/org/gnome/Weather/Application/">
+ <key name="locations" type="av">
+ <default>[]</default>
+ <summary>Configured cities to show weather for</summary>
+ <description>
+ The locations shown in the world view of gnome-weather. Each value is a
+ GVariant returned by gweather_location_serialize().
+ </description>
+ </key>
+ </schema>
+</schemalist>
diff --git a/src/Makefile.am b/src/Makefile.am
index f9b0641..329cb5a 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -4,12 +4,13 @@ nodist_bin_SCRIPTS = gnome-weather
jsdir = $(pkgdatadir)
dist_js_DATA = \
+ city.js \
forecast.js \
main.js \
strings.js \
util.js \
- view.js \
window.js \
+ world.js \
$(NULL)
gnome-weather: $(top_builddir)/config.status gnome-weather.in
diff --git a/src/view.js b/src/city.js
similarity index 90%
rename from src/view.js
rename to src/city.js
index 086e568..cf239f1 100644
--- a/src/view.js
+++ b/src/city.js
@@ -95,10 +95,7 @@ const WeatherWidget = new Lang.Class({
},
update: function(info) {
- let conditions = info.get_conditions();
- if (conditions == '-') // Not significant
- conditions = info.get_sky();
- this._conditions.label = conditions;
+ this._conditions.label = Util.getWeatherConditions(info);
this._temperature.label = info.get_temp_summary();
let attr = info.get_attribution();
@@ -132,7 +129,6 @@ const WeatherView = new Lang.Class({
Extends: Gd.Stack,
_init: function(params) {
- let filtered = Params.filter(params, { info: null });
this.parent(params);
let loadingPage = new Gtk.Grid({ orientation: Gtk.Orientation.VERTICAL,
@@ -149,9 +145,30 @@ const WeatherView = new Lang.Class({
this._infoPage = new WeatherWidget();
this.add_named(this._infoPage, 'info');
- this._info = filtered.info;
- this._updateId = this._info.connect('updated',
- Lang.bind(this, this._onUpdate));
+ this._info = null;
+ this._updateId = 0;
+ },
+
+ get info() {
+ return this._info;
+ },
+
+ set info(info) {
+ if (this._updateId) {
+ this._info.disconnect(this._updateId);
+ this._updateId = 0;
+
+ this._infoPage.clear();
+ }
+
+ this._info = info;
+
+ if (info) {
+ this._updateId = this._info.connect('updated',
+ Lang.bind(this, this._onUpdate));
+ if (info.is_valid())
+ this._onUpdate(info);
+ }
},
vfunc_destroy: function() {
@@ -163,10 +180,12 @@ const WeatherView = new Lang.Class({
this.parent();
},
- beginUpdate: function() {
+ update: function() {
this.visible_child_name = 'loading';
this._spinner.start();
this._infoPage.clear();
+
+ this._info.update();
},
_onUpdate: function(info) {
diff --git a/src/forecast.js b/src/forecast.js
index d0df482..e984e97 100644
--- a/src/forecast.js
+++ b/src/forecast.js
@@ -252,20 +252,13 @@ const TodaySidebar = new Lang.Class({
this._grid.attach(image, 1, row, 1, 1);
this._infoWidgets.push(image);
- let conditions = new Gtk.Label({ label: this._getConditions(info),
+ let conditions = new Gtk.Label({ label: Util.getWeatherConditions(info),
visible: true,
xalign: 0.0 });
this._grid.attach(conditions, 2, row, 1, 1);
this._infoWidgets.push(conditions);
},
- _getConditions: function(info) {
- let conditions = info.get_conditions();
- if (conditions == '-') // Not significant
- conditions = info.get_sky();
- return conditions;
- },
-
_showMore: function() {
if (!this._hasMore) {
log('_showMore called when _hasMore is false, this should not happen');
diff --git a/src/main.js b/src/main.js
index 224cb05..4662923 100644
--- a/src/main.js
+++ b/src/main.js
@@ -21,13 +21,16 @@ pkg.initGettext();
pkg.initFormat();
pkg.require({ 'Gd': '1.0',
'Gdk': '3.0',
+ 'GdkPixbuf': '2.0',
+ 'Gio': '2.0',
'GLib': '2.0',
'GObject': '2.0',
'Gtk': '3.0',
'GWeather': '3.0',
'Lang': '1.0',
'Mainloop': '1.0',
- 'Params': '1.0' });
+ 'Params': '1.0',
+ 'System': '1.0' });
const Util = imports.util;
const Window = imports.window;
@@ -47,7 +50,7 @@ const Application = new Lang.Class({
Util.loadStyleSheet();
let settings = Gtk.Settings.get_for_screen(Gdk.Screen.get_default());
- settings.gtk_application_prefer_dark_theme = true;
+ settings.gtk_application_prefer_dark_theme = false;
this.world = GWeather.Location.new_world(false);
},
diff --git a/src/util.js b/src/util.js
index 7c9ca4d..6bc3c26 100644
--- a/src/util.js
+++ b/src/util.js
@@ -54,3 +54,44 @@ function arrayEqual(one, two) {
return true;
}
+
+function getSettings(schemaId, path) {
+ const GioSSS = Gio.SettingsSchemaSource;
+ let schemaSource;
+
+ if (pkg.moduledir != pkg.pkgdatadir) {
+ // Running from the source tree
+ schemaSource = GioSSS.new_from_directory(pkg.pkgdatadir,
+ GioSSS.get_default(),
+ false);
+ } else {
+ schemaSource = GioSSS.get_default();
+ }
+
+ let schemaObj = schemaSource.lookup(schemaId, true);
+ if (!schemaObj) {
+ log('Missing GSettings schema ' + schemaId);
+ System.exit(1);
+ }
+
+ if (path === undefined)
+ return new Gio.Settings({ settings_schema: schemaObj });
+ else
+ return new Gio.Settings({ settings_schema: schemaObj,
+ path: path });
+}
+
+function loadIcon(iconName, size) {
+ let theme = Gtk.IconTheme.get_default();
+
+ return theme.load_icon(iconName,
+ size,
+ Gtk.IconLookupFlags.GENERIC_FALLBACK);
+}
+
+function getWeatherConditions(info) {
+ let conditions = info.get_conditions();
+ if (conditions == '-') // Not significant
+ conditions = info.get_sky();
+ return conditions;
+}
diff --git a/src/window.js b/src/window.js
index 62de72e..aeb7a9c 100644
--- a/src/window.js
+++ b/src/window.js
@@ -16,7 +16,8 @@
// with Gnome Weather; if not, write to the Free Software Foundation,
// Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
-const View = imports.view;
+const City = imports.city;
+const World = imports.world;
function makeTitle(location) {
let city = location;
@@ -34,70 +35,74 @@ function makeTitle(location) {
return city.get_name();
}
+const Page = {
+ WORLD: 0,
+ CITY: 1
+};
+
const MainWindow = new Lang.Class({
Name: 'MainWindow',
Extends: Gtk.ApplicationWindow,
- Properties: {
- 'location': GObject.ParamSpec.boxed('location', 'Location', '',
- GObject.ParamFlags.READABLE |
- GObject.ParamFlags.WRITABLE,
- GWeather.Location),
- },
_init: function(params) {
- params = Params.fill(params, { default_width: 700,
- default_height: 500 });
+ params = Params.fill(params, { width_request: 700,
+ height_request: 520 });
this.parent(params);
this._world = this.application.world;
- this._info = new GWeather.Info({ world: this._world,
- forecast_type: GWeather.ForecastType.LIST,
- enabled_providers: GWeather.Provider.METAR |
- GWeather.Provider.YR_NO });
- this._location = this._info.get_location();
+ this._model = new World.WorldModel(this._world);
+ this._currentInfo = null;
+ this._currentPage = Page.WORLD;
+ this._pageWidgets = [[],[]];
let grid = new Gtk.Grid({ orientation: Gtk.Orientation.VERTICAL });
- this._header = new Gd.HeaderBar({ title: makeTitle(this._location),
- hexpand: true });
+ this._header = new Gd.HeaderBar({ hexpand: true });
grid.add(this._header);
- this._search = new Gd.HeaderToggleButton({ symbolic_icon_name: 'edit-find-symbolic' });
- this._header.pack_end(this._search);
+ let newButton = new Gd.HeaderSimpleButton({ label: _("New") });
+ newButton.connect('clicked', Lang.bind(this, this._newLocation));
+ this._header.pack_start(newButton);
+ this._pageWidgets[Page.WORLD].push(newButton);
+
+ let goWorldButton = new Gd.HeaderSimpleButton({ label: _("World Weather") });
+ goWorldButton.connect('clicked', Lang.bind(this, this._goWorld));
+ this._header.pack_start(goWorldButton);
+ this._pageWidgets[Page.CITY].push(goWorldButton);
let refresh = new Gd.HeaderSimpleButton({ symbolic_icon_name: 'view-refresh-symbolic' });
refresh.connect('clicked', Lang.bind(this, this.update));
this._header.pack_end(refresh);
+ this._pageWidgets[Page.CITY].push(refresh);
+
+ let select = new Gd.HeaderToggleButton({ symbolic_icon_name: 'object-select-symbolic' });
+ this._header.pack_end(select);
+ this._pageWidgets[Page.WORLD].push(select);
+
+ this._stack = new Gd.Stack();
+
+ this._cityView = new City.WeatherView({ hexpand: true,
+ vexpand: true });
+ this._stack.add(this._cityView);
+
+ this._worldView = new Gd.MainView({ view_type: Gd.MainViewType.ICON });
+ this._worldView.model = this._model;
+ this._worldView.connect('item-activated', Lang.bind(this, this._itemActivated));
+ this._worldView.connect('selection-mode-request', function() {
+ select.active = true;
+ });
+ select.bind_property('active', this._worldView, 'selection-mode',
+ GObject.BindingFlags.DEFAULT);
+ this._stack.add(this._worldView);
- this._locationEntry = new GWeather.LocationEntry({ top: this._world,
- location: this._location,
- width_request: 500,
- halign: Gtk.Align.CENTER });
- this._locationEntry.bind_property('location', this, 'location',
- GObject.BindingFlags.DEFAULT);
-
- let toolbar = new Gtk.Toolbar({ hexpand: true });
- toolbar.get_style_context().add_class(Gtk.STYLE_CLASS_PRIMARY_TOOLBAR);
- let item = new Gtk.ToolItem();
- item.set_expand(true);
- item.add(this._locationEntry);
- toolbar.insert(item, 0);
-
- let revealer = new Gd.Revealer({ reveal_child: false,
- child: toolbar });
- this._search.bind_property('active', revealer, 'reveal-child',
- GObject.BindingFlags.DEFAULT);
- grid.add(revealer);
-
- this._view = new View.WeatherView({ info: this._info,
- hexpand: true,
- vexpand: true });
- grid.add(this._view);
+ this._stack.set_visible_child(this._worldView);
+ grid.add(this._stack);
this.add(grid);
grid.show_all();
- this._view.beginUpdate();
+ for (let i = 0; i < this._pageWidgets[Page.CITY].length; i++)
+ this._pageWidgets[Page.CITY][i].hide();
},
get location() {
@@ -114,7 +119,66 @@ const MainWindow = new Lang.Class({
},
update: function() {
- this._view.beginUpdate();
- this._info.update();
+ this._cityView.update();
+ },
+
+ _getTitle: function() {
+ if (this._currentPage == Page.WORLD)
+ return '';
+
+ return makeTitle(this._cityView.info.location);
+ },
+
+ _goToPage: function(page) {
+ if (page == this._currentPage)
+ return;
+
+ for (let i = 0; i < this._pageWidgets[this._currentPage].length; i++)
+ this._pageWidgets[this._currentPage][i].hide();
+
+ for (let i = 0; i < this._pageWidgets[page].length; i++)
+ this._pageWidgets[page][i].show();
+
+ this._currentPage = page;
+ this._header.title = this._getTitle();
+ },
+
+ _itemActivated: function(view, id, path) {
+ let [ok, iter] = this._model.get_iter(path);
+ let info = this._model.get_value(iter, World.Columns.INFO);
+
+ this._cityView.info = info;
+ this._stack.set_visible_child(this._cityView);
+ this._goToPage(Page.CITY);
+ },
+
+ _goWorld: function() {
+ this._cityView.info = null;
+ this._stack.set_visible_child(this._worldView);
+ this._goToPage(Page.WORLD);
},
+
+ _newLocation: function() {
+ let dialog = new Gtk.Dialog({ title: _("New Location"),
+ transient_for: this.get_toplevel(),
+ modal: true });
+ let entry = new GWeather.LocationEntry({ top: this._world });
+
+ dialog.get_content_area().add(entry);
+ dialog.add_button('gtk-add', Gtk.ResponseType.OK);
+
+ dialog.connect('response', Lang.bind(this, function(dialog, response) {
+ dialog.destroy();
+
+ if (response != Gtk.ResponseType.OK)
+ return;
+
+ let location = entry.location;
+ if (!location)
+ return;
+
+ this._model.addLocation(entry.location);
+ }));
+ dialog.show_all();
+ }
});
diff --git a/src/world.js b/src/world.js
new file mode 100644
index 0000000..d02ac24
--- /dev/null
+++ b/src/world.js
@@ -0,0 +1,161 @@
+// -*- Mode: js; indent-tabs-mode: nil; c-basic-offset: 4; tab-width: 4 -*-
+//
+// Copyright (c) 2012 Giovanni Campagna <scampa giovanni gmail com>
+//
+// Gnome Weather is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by the
+// Free Software Foundation; either version 2 of the License, or (at your
+// option) any later version.
+//
+// Gnome Weather is distributed in the hope that it will be useful, but
+// WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+// or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+// for more details.
+//
+// You should have received a copy of the GNU General Public License along
+// with Gnome Weather; if not, write to the Free Software Foundation,
+// Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+
+const Util = imports.util;
+
+const Columns = {
+ ID: Gd.MainColumns.ID,
+ URI: Gd.MainColumns.URI,
+ PRIMARY_TEXT: Gd.MainColumns.PRIMARY_TEXT,
+ SECONDARY_TEXT: Gd.MainColumns.SECONDARY_TEXT,
+ ICON: Gd.MainColumns.ICON,
+ MTIME: Gd.MainColumns.MTIME,
+ SELECTED: Gd.MainColumns.SELECTED,
+ LOCATION: 7,
+ INFO: 8
+};
+
+const ICON_SIZE = 128;
+
+const WorldModel = new Lang.Class({
+ Name: 'WorldModel',
+ Extends: Gtk.ListStore,
+ Signals: {
+ 'updated': { param_types: [ GWeather.Info ] }
+ },
+
+ _init: function(world) {
+ this.parent();
+ this.set_column_types([GObject.TYPE_STRING,
+ GObject.TYPE_STRING,
+ GObject.TYPE_STRING,
+ GObject.TYPE_STRING,
+ GdkPixbuf.Pixbuf,
+ GObject.TYPE_INT,
+ GObject.TYPE_BOOLEAN,
+ GWeather.Location,
+ GWeather.Info]);
+
+ this._world = world;
+
+ this._settings = Util.getSettings('org.gnome.Weather.Application');
+
+ let locations = this._settings.get_value('locations').deep_unpack();
+ for (let i = 0; i < locations.length; i++) {
+ let variant = locations[i];
+ let location = this._world.deserialize(variant);
+ this._addLocationInternal(location);
+ }
+
+ this._settings.connect('changed::locations', Lang.bind(this, this._onChanged));
+ },
+
+ _addLocationInternal: function(location) {
+ let info = new GWeather.Info({ world: this._world,
+ location: location,
+ forecast_type: GWeather.ForecastType.LIST,
+ enabled_providers: (GWeather.Provider.METAR |
+ GWeather.Provider.YR_NO) });
+ let iter;
+ info.connect('updated', Lang.bind(this, function(info) {
+ let icon = Util.loadIcon(info.get_symbolic_icon_name(), ICON_SIZE);
+ let secondary_text = Util.getWeatherConditions(info);
+ this.set(iter,
+ [Columns.ICON, Columns.SECONDARY_TEXT],
+ [icon, secondary_text]);
+
+ this.emit('updated', info);
+ }));
+ info.update();
+
+ let primary_text = location.get_city_name();
+ let icon = Util.loadIcon('view-refresh-symbolic', ICON_SIZE);
+
+ iter = this.insert_with_valuesv(-1,
+ [Columns.PRIMARY_TEXT,
+ Columns.ICON,
+ Columns.LOCATION,
+ Columns.INFO],
+ [primary_text,
+ icon,
+ location,
+ info]);
+ },
+
+ _onChanged: function() {
+ let newLocations = this._settings.get_value('locations').deep_unpack();
+ let toErase = [];
+
+ let [ok, iter] = this.get_iter_first();
+ while (ok) {
+ let location = this.get_value(iter, Columns.LOCATION);
+
+ let found = true;
+ for (let j = 0; j < newLocations.length; j++) {
+ let variant = newLocations[j];
+ if (variant == null)
+ continue;
+
+ let newLocation = this._world.deserialize(variant);
+
+ if (location.equal(newLocation)) {
+ newLocations[j] = null;
+ found = true;
+ break;
+ }
+ }
+
+ if (!found)
+ toErase.push(iter.copy());
+
+ ok = this.iter_next(iter);
+ }
+
+ for (let i = 0; i < toErase.length; i++)
+ this.erase(toErase[i]);
+
+ for (let i = 0; i < newLocations.length; i++) {
+ let variant = newLocations[i];
+ if (variant == null)
+ continue;
+
+ let newLocation = this._world.deserialize(variant);
+ this._addLocationInternal(newLocation);
+ }
+ },
+
+ addLocation: function(location) {
+ let newLocations = this._settings.get_value('locations').deep_unpack();
+ newLocations.push(location.serialize());
+ this._settings.set_value('locations', new GLib.Variant('av', newLocations));
+ },
+
+ removeLocation: function(iter) {
+ let location = this.get_value(iter, Columns.LOCATION);
+ let variant = location.serialize();
+
+ let newLocations = this._settings.get_value('locations').deep_unpack();
+ for (let i = 0; i < newLocations.length; i++) {
+ if (newLocations[i].equal(variant)) {
+ newLocations.splice(i, 1);
+ break;
+ }
+ }
+ this._settings.set_value('locations', new GLib.Variant('av', newLocations));
+ },
+});
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]