[gnome-weather/wip/ewlsh/gtk4] Lots of design alignment, cleanup, entry fixes, and model reworking for GTK4
- From: Evan Welsh <ewlsh src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [gnome-weather/wip/ewlsh/gtk4] Lots of design alignment, cleanup, entry fixes, and model reworking for GTK4
- Date: Sun, 13 Feb 2022 05:36:41 +0000 (UTC)
commit bb7bb6d6e81be8e4c28f3d8bb0dfd20848fd1991
Author: Evan Welsh <contact evanwelsh com>
Date: Sat Feb 12 21:36:35 2022 -0800
Lots of design alignment, cleanup, entry fixes, and model reworking for GTK4
data/city.ui | 10 +-
data/org.gnome.Weather.data.gresource.xml | 1 +
data/places-popover.ui | 118 ++++++-----
data/style-dark.css | 5 +-
data/style.css | 39 ++--
data/weather-widget.ui | 1 -
data/window.ui | 19 +-
src/app/application.js | 330 +++++++++++++++--------------
src/app/city.js | 46 ++--
src/app/dailyForecast.js | 19 +-
src/app/entry.js | 104 ++++-----
src/app/hourlyForecast.js | 24 ++-
src/app/locationRow.js | 10 +-
src/app/locationRow.ui | 11 +-
src/app/main.js | 4 +-
src/app/thermometer.js | 14 +-
src/app/window.js | 23 +-
src/app/world.js | 137 +++++-------
src/misc/util.js | 29 ++-
src/org.gnome.Weather.BackgroundService.in | 6 +-
src/service/main.js | 4 +-
src/shared/world.js | 118 +++++++----
22 files changed, 562 insertions(+), 510 deletions(-)
---
diff --git a/data/city.ui b/data/city.ui
index 5bc7e55..6792586 100644
--- a/data/city.ui
+++ b/data/city.ui
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<interface>
- <requires lib="gtk" version="4.0"/>
+ <requires lib="gtk" version="4.0" />
<template class="Gjs_WeatherView">
<child>
<object class="GtkStack" id="stack">
@@ -8,18 +8,20 @@
<object class="GtkStackPage">
<property name="name">loading</property>
<property name="child">
- <object class="GtkGrid" id="loading-grid">
+ <object class="GtkBox" id="loading-grid">
<property name="orientation">vertical</property>
+ <property name="spacing">20</property>
<property name="halign">center</property>
<property name="valign">center</property>
<child>
<object class="GtkSpinner" id="spinner">
- <property name="height_request">128</property>
- <property name="width_request">128</property>
+ <property name="height_request">64</property>
+ <property name="width_request">64</property>
</object>
</child>
<child>
<object class="GtkLabel">
+ <property name="name">loadingLabel</property>
<property name="label" translatable="yes">Loading…</property>
</object>
</child>
diff --git a/data/org.gnome.Weather.data.gresource.xml b/data/org.gnome.Weather.data.gresource.xml
index 56934d0..2f525ee 100644
--- a/data/org.gnome.Weather.data.gresource.xml
+++ b/data/org.gnome.Weather.data.gresource.xml
@@ -8,6 +8,7 @@
<file preprocess="xml-stripblanks">hour-entry.ui</file>
<file preprocess="xml-stripblanks">day-entry.ui</file>
<file>style.css</file>
+ <file>style-dark.css</file>
</gresource>
<gresource prefix="/org/gnome/shell">
<file>ShellWeatherIntegration.xml</file>
diff --git a/data/places-popover.ui b/data/places-popover.ui
index 589969b..3d0862c 100644
--- a/data/places-popover.ui
+++ b/data/places-popover.ui
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<interface>
- <requires lib="gtk" version="4.0"/>
+ <requires lib="gtk" version="4.0" />
<object class="GtkGrid" id="popover-grid">
<property name="name">popoverGrid</property>
<property name="hexpand">1</property>
@@ -10,6 +10,7 @@
<property name="name">locationEntry</property>
<property name="focusable">1</property>
<property name="width-request">300</property>
+ <property name="placeholder-text" translatable="yes">Search for a city</property>
<layout>
<property name="column">0</property>
<property name="row">0</property>
@@ -18,69 +19,80 @@
</child>
<child>
<object class="GtkStack" id="popover-stack">
- <property name="vhomogeneous">0</property>
- <property name="hhomogeneous">0</property>
+ <property name="vhomogeneous">True</property>
+ <property name="hhomogeneous">True</property>
<child>
- <object class="GtkGrid" id="empty-search-grid">
- <property name="name">search-city-grid</property>
- <property name="orientation">vertical</property>
- <property name="margin_top">25</property>
- <property name="margin_bottom">25</property>
- <property name="halign">center</property>
- <property name="valign">center</property>
- <property name="row_homogeneous">1</property>
-
-
- <child>
- <object class="GtkImage" id="search-image">
- <property name="icon_name">edit-find-symbolic</property>
- <property name="icon_size">2</property>
- <property name="use_fallback">1</property>
- <property name="halign">center</property>
- <property name="valign">center</property>
- <layout>
- <property name="column">0</property>
- <property name="row">0</property>
- </layout>
- </object>
- </child>
+ <object class="GtkScrolledWindow" id="search-list-scroll-window">
+ <property name="hscrollbar-policy">never</property>
+ <property name="max-content-width">200</property>
<child>
- <object class="GtkLabel" id="search-label">
- <property name="label" translatable="yes">Search for a city</property>
- <property name="halign">center</property>
- <property name="valign">center</property>
- <style>
- <class name="dim-label"/>
- </style>
- <layout>
- <property name="column">0</property>
- <property name="row">1</property>
- </layout>
+ <object class="GtkViewport">
+ <child>
+ <object class="GtkListView" id="search-list-view">
+ <property name="name">search-list-view</property>
+ <!-- <property name="hexpand">1</property> -->
+ </object>
+ </child>
</object>
-
</child>
-
</object>
</child>
<child>
- <object class="GtkScrolledWindow" id="search-list-scroll-window">
+ <object class="GtkScrolledWindow" id="locations-list-scroll-window">
+ <property name="hscrollbar-policy">never</property>
+ <property name="max-content-width">200</property>
<child>
- <object class="GtkListView" id="search-list-view">
- <property name="name">search-list-view</property>
- <property name="hexpand">1</property>
+ <object class="GtkViewport">
+ <child>
+ <object class="GtkListBox" id="locations-list-box">
+ <property name="name">locations-list-box</property>
+ <property name="hexpand">True</property>
+ <property name="selection-mode">none</property>
+ <property name="show-separators">False</property>
+ <child type="placeholder">
+ <object class="GtkGrid" id="empty-search-grid">
+ <property name="name">search-city-grid</property>
+ <property name="orientation">vertical</property>
+ <property name="margin_top">25</property>
+ <property name="margin_bottom">25</property>
+ <property name="halign">center</property>
+ <property name="valign">center</property>
+ <property name="row_homogeneous">1</property>
+ <child>
+ <object class="GtkImage" id="search-image">
+ <property name="icon_name">edit-find-symbolic</property>
+ <property name="icon_size">2</property>
+ <property name="use_fallback">1</property>
+ <property name="halign">center</property>
+ <property name="valign">center</property>
+ <layout>
+ <property name="column">0</property>
+ <property name="row">0</property>
+ </layout>
+ </object>
+ </child>
+ <child>
+ <object class="GtkLabel" id="search-label">
+ <property name="label" translatable="yes">Search for a city</property>
+ <property name="halign">center</property>
+ <property name="valign">center</property>
+ <style>
+ <class name="dim-label" />
+ </style>
+ <layout>
+ <property name="column">0</property>
+ <property name="row">1</property>
+ </layout>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
</object>
</child>
</object>
</child>
- <child>
- <object class="GtkListBox" id="locations-list-box">
- <property name="name">locations-list-box</property>
- <property name="hexpand">1</property>
- <property name="selection-mode">none</property>
- <property name="show-separators">false</property>
- </object>
- </child>
-
<layout>
<property name="column">0</property>
<property name="row">2</property>
@@ -88,4 +100,4 @@
</object>
</child>
</object>
-</interface>
+</interface>
\ No newline at end of file
diff --git a/data/style-dark.css b/data/style-dark.css
index e4b9fd7..b419d1e 100644
--- a/data/style-dark.css
+++ b/data/style-dark.css
@@ -1 +1,4 @@
-@define-color temp_chart_fill_color rgba(248, 228, 92, 0.15);
\ No newline at end of file
+@define-color weather_temp_chart_fill_color rgba(248, 228, 92, 0.15);
+@define-color weather_thermometer_high_color @yellow_1;
+@define-color weather_thermometer_low_color @blue_1;
+@define-color weather_forecast_color @light_1;
diff --git a/data/style.css b/data/style.css
index 2d1d4f0..ca1b660 100644
--- a/data/style.css
+++ b/data/style.css
@@ -1,8 +1,12 @@
-@define-color temp_chart_fill_color rgba(248, 228, 92, 0.5);
-@define-color temp_chart_stroke_color rgba(246, 211, 45, 1.0);
+@define-color weather_temp_chart_fill_color rgba(248, 228, 92, 0.5);
+@define-color weather_temp_chart_stroke_color rgba(246, 211, 45, 1.0);
-@define-color thermometer_warm_color rgb(245, 194, 17);
-@define-color thermometer_cold_color rgb(28, 113, 216);
+@define-color weather_thermometer_warm_color rgb(245, 194, 17);
+@define-color weather_thermometer_cold_color rgb(28, 113, 216);
+
+@define-color weather_thermometer_high_color #c89009;
+@define-color weather_thermometer_low_color #2174d9;
+@define-color weather_forecast_color #c89009;
#places-label {
font-weight: bold;
@@ -14,6 +18,10 @@
margin-left: 16px;
}
+#loadingLabel {
+ font-size: 16pt;
+}
+
#apparent-label {
font-size: 9pt;
}
@@ -44,21 +52,20 @@
margin: 10px;
}
-WeatherLocationRow {
- padding: 10px;
+.weather-popover {
+ margin-top: 10px;
}
-WeatherLocationRow #label {
- margin-bottom: 10px;
- font-size: 11pt;
+.weather-popover contents {
+ padding: 0;
}
-WeatherLocationRow #countryLabel {
- font-size: 9pt;
+WeatherLocationRow {
+ padding: 10px;
}
-.weather-popover contents {
- padding: 0;
+WeatherLocationRow #label {
+ margin-bottom: 10px;
}
#currentIcon {
@@ -89,19 +96,19 @@ WeatherLocationRow #countryLabel {
.forecast-temperature-label {
font-weight: bold;
font-size: 12pt;
- color: #c89009;
+ color: @weather_forecast_color;
}
WeatherThermometer > label.high {
font-weight: bold;
font-size: 13pt;
- color: #c89009;
+ color: @weather_thermometer_high_color;
}
WeatherThermometer > label.low {
font-weight: bold;
font-size: 13pt;
- color: #2174d9;
+ color: @weather_thermometer_low_color;
}
.day-label {
diff --git a/data/weather-widget.ui b/data/weather-widget.ui
index cc84729..2eb04c8 100644
--- a/data/weather-widget.ui
+++ b/data/weather-widget.ui
@@ -16,7 +16,6 @@
<property name="column_spacing">10</property>
<child>
<object class="GtkImage" id="conditionsImage">
- <!-- <property name="name">conditions-image</property> -->
<property name="halign">start</property>
<property name="valign">center</property>
<property name="pixel_size">84</property>
diff --git a/data/window.ui b/data/window.ui
index 44aa22a..39a906f 100644
--- a/data/window.ui
+++ b/data/window.ui
@@ -23,7 +23,6 @@
</section>
</menu>
<template class="Gjs_MainWindow">
-
<property name="default_width">760</property>
<property name="default_height">520</property>
<child>
@@ -102,19 +101,11 @@
<property name="title" translatable="yes">Welcome to Weather!</property>
<property name="description" translatable="yes">To get started, select a
location.</property>
<child>
- <object class="GtkBox">
- <property name="orientation">vertical</property>
- <child>
- <object class="Gjs_LocationSearchEntry" id="searchEntry">
- <!-- TODO: Our custom widget doesn't have these properties <property
name="hexpand">False</property>
- <property name="halign">center</property>
- <property name="width-request">246</property> -->
- <!-- <property name="placeholder_text" translatable="yes">Search for a city or
country</property> -->
- </object>
- </child>
- <child>
- <object class="GtkListView" id="searchListView"></object>
- </child>
+ <object class="GtkMenuButton" id="searchButton">
+ <property name="hexpand">False</property>
+ <property name="halign">center</property>
+ <property name="width-request">146</property>
+ <property name="label" translatable="yes">Search for a city or country</property>
</object>
</child>
diff --git a/src/app/application.js b/src/app/application.js
index 3d71dc8..06f608c 100644
--- a/src/app/application.js
+++ b/src/app/application.js
@@ -25,196 +25,206 @@ import GWeather from 'gi://GWeather';
// ensure the type before we call to GtkBuilder
import './entry.js';
-import * as Util from '../misc/util.js';
import * as Window from './window.js';
import * as World from '../shared/world.js';
import * as CurrentLocationController from './currentLocationController.js';
import { ShellIntegration } from './shell.js';
-export const Application = GObject.registerClass(
- class WeatherApplication extends Adw.Application {
+export class WeatherApplication extends Adw.Application {
- _init() {
- super._init({
- application_id: pkg.name,
- flags: Gio.ApplicationFlags.CAN_OVERRIDE_APP_ID,
- });
- let name_prefix = '';
+ constructor() {
+ super({
+ application_id: pkg.name,
+ flags: Gio.ApplicationFlags.CAN_OVERRIDE_APP_ID,
+ });
+ let name_prefix = '';
- GLib.set_application_name(name_prefix + _("Weather"));
- Gtk.Window.set_default_icon_name(pkg.name);
+ GLib.set_application_name(name_prefix + _("Weather"));
+ Gtk.Window.set_default_icon_name(pkg.name);
- this._mainWindow = undefined;
- }
+ this._mainWindow = undefined;
+ }
- get mainWindow() {
- return this._mainWindow;
- }
+ get mainWindow() {
+ return this._mainWindow;
+ }
- set mainWindow(value) {
- this._mainWindow = value;
- }
+ set mainWindow(value) {
+ this._mainWindow = value;
+ }
- _onQuit() {
- this.quit();
- }
+ _onQuit() {
+ this.quit();
+ }
- _onShowLocation(action, parameter) {
- let location = this.world.deserialize(parameter.deep_unpack());
- let win = this._createWindow();
+ _onShowLocation(action, parameter) {
+ let location = this.world.deserialize(parameter.deep_unpack());
+ let win = this._createWindow();
- let info = this.model.addNewLocation(location, false);
- win.showInfo(info, false);
- this._showWindowWhenReady(win);
- }
+ let info = this.model.addNewLocation(location, false);
+ win.showInfo(info, false);
+ this._showWindowWhenReady(win);
+ }
- _onShowSearch(action, parameter) {
- let text = parameter.deep_unpack();
- let win = this._createWindow();
+ _onShowSearch(action, parameter) {
+ let text = parameter.deep_unpack();
+ let win = this._createWindow();
- win.showSearch(text);
- this._showWindowWhenReady(win);
- }
+ win.showSearch(text);
+ this._showWindowWhenReady(win);
+ }
- vfunc_startup() {
- super.vfunc_startup();
+ vfunc_startup() {
+ super.vfunc_startup();
- this.world = GWeather.Location.get_world();
- this.model = new World.WorldModel(this.world, true);
- this.currentLocationController = new
CurrentLocationController.CurrentLocationController(this.model);
+ this.world = GWeather.Location.get_world();
+ this.model = new World.WorldModel(this.world, true);
+ this.currentLocationController = new CurrentLocationController.CurrentLocationController(this.model);
- this.model.load();
+ this.model.load();
- this.model.connect('notify::loading', () => {
- if (this.model.loading)
- this.mark_busy();
- else
- this.unmark_busy();
- });
+ this.model.connect('notify::loading', () => {
if (this.model.loading)
this.mark_busy();
-
- let quitAction = new Gio.SimpleAction({
- enabled: true,
- name: 'quit'
- });
- quitAction.connect('activate', () => this._onQuit());
- this.add_action(quitAction);
-
- let showLocationAction = new Gio.SimpleAction({
- enabled: true,
- name: 'show-location',
- parameter_type: new GLib.VariantType('v'),
- });
- showLocationAction.connect('activate', (action, parameter) => {
- this._onShowLocation(action, parameter);
- });
- this.add_action(showLocationAction);
-
- let showSearchAction = new Gio.SimpleAction({
- enabled: true,
- name: 'show-search',
- parameter_type: new GLib.VariantType('v'),
- })
- showSearchAction.connect('activate', (action, parameter) => {
- this._onShowSearch(action, parameter);
- });
- this.add_action(showSearchAction);
-
- let gwSettings = new Gio.Settings({ schema_id: 'org.gnome.GWeather4' });
- // we would like to use g_settings_create_action() here
- // but that does not handle correctly the case of 'default'
- // we would also like to use g_settings_bind_with_mapping(), but that
- // function is not introspectable (two callbacks, one destroy notify)
- // so we hand code the behavior we want
- function resolveDefaultTemperatureUnit(unit) {
- unit = GWeather.TemperatureUnit.to_real(unit);
- if (unit == GWeather.TemperatureUnit.CENTIGRADE)
- return new GLib.Variant('s', 'centigrade');
- else if (unit == GWeather.TemperatureUnit.FAHRENHEIT)
- return new GLib.Variant('s', 'fahrenheit');
- else
- return new GLib.Variant('s', 'default');
+ else
+ this.unmark_busy();
+ });
+ if (this.model.loading)
+ this.mark_busy();
+
+ let quitAction = new Gio.SimpleAction({
+ enabled: true,
+ name: 'quit'
+ });
+ quitAction.connect('activate', () => this._onQuit());
+ this.add_action(quitAction);
+
+ let showLocationAction = new Gio.SimpleAction({
+ enabled: true,
+ name: 'show-location',
+ parameter_type: new GLib.VariantType('v'),
+ });
+ showLocationAction.connect('activate', (action, parameter) => {
+ this._onShowLocation(action, parameter);
+ });
+ this.add_action(showLocationAction);
+
+ let showSearchAction = new Gio.SimpleAction({
+ enabled: true,
+ name: 'show-search',
+ parameter_type: new GLib.VariantType('v'),
+ })
+ showSearchAction.connect('activate', (action, parameter) => {
+ this._onShowSearch(action, parameter);
+ });
+ this.add_action(showSearchAction);
+
+ let gwSettings = new Gio.Settings({ schema_id: 'org.gnome.GWeather4' });
+ // Sync settings changes to the legacy GTK3 GWeather interface if it is
+ // available
+ let legacyGwSettings;
+ try {
+ legacyGwSettings = new Gio.Settings({ schema_id: 'org.gnome.GWeather' });
+ } catch {}
+
+ // we would like to use g_settings_create_action() here
+ // but that does not handle correctly the case of 'default'
+ // we would also like to use g_settings_bind_with_mapping(), but that
+ // function is not introspectable (two callbacks, one destroy notify)
+ // so we hand code the behavior we want
+ function resolveDefaultTemperatureUnit(unit) {
+ unit = GWeather.TemperatureUnit.to_real(unit);
+ if (unit == GWeather.TemperatureUnit.CENTIGRADE)
+ return new GLib.Variant('s', 'centigrade');
+ else if (unit == GWeather.TemperatureUnit.FAHRENHEIT)
+ return new GLib.Variant('s', 'fahrenheit');
+ else
+ return new GLib.Variant('s', 'default');
+ }
+ let temperatureAction = new Gio.SimpleAction({
+ enabled: true,
+ name: 'temperature-unit',
+ state: resolveDefaultTemperatureUnit(gwSettings.get_enum('temperature-unit')),
+ parameter_type: new GLib.VariantType('s')
+ });
+ temperatureAction.connect('activate', function (_, parameter) {
+ gwSettings.set_value('temperature-unit', parameter);
+ if (legacyGwSettings) {
+ legacyGwSettings.set_value('temperature-unit', parameter);
}
- let temperatureAction = new Gio.SimpleAction({
- enabled: true,
- name: 'temperature-unit',
- state: resolveDefaultTemperatureUnit(gwSettings.get_enum('temperature-unit')),
- parameter_type: new GLib.VariantType('s')
+ });
+ gwSettings.connect('changed::temperature-unit', function () {
+ temperatureAction.state = resolveDefaultTemperatureUnit(gwSettings.get_enum('temperature-unit'));
+ });
+ this.add_action(temperatureAction);
+
+ this.set_accels_for_action("win.selection-mode", ["Escape"]);
+ this.set_accels_for_action("win.select-all", ["<Control>a"]);
+ this.set_accels_for_action("app.quit", ["<Control>q"]);
+ }
+
+ vfunc_dbus_register(conn, path) {
+ this._shellIntegration = new ShellIntegration();
+ this._shellIntegration.export(conn, path);
+ return true;
+ }
+
+ vfunc_dbus_unregister(conn, path) {
+ this._shellIntegration.unexport(conn);
+ }
+
+ _createWindow() {
+ const window = new Window.MainWindow({ application: this });
+
+ // Store a weak reference to the window for cleanup...
+ this.mainWindow = window;
+
+ return window;
+ }
+
+ _showWindowWhenReady(win) {
+ let notifyId;
+ win.present();
+ if (this.model.loading) {
+ let timeoutId;
+ let model = this.model;
+
+ timeoutId = GLib.timeout_add(GLib.PRIORITY_DEFAULT, 1000, function () {
+ log('Timeout during model load, perhaps the network is not available?');
+ model.disconnect(notifyId);
+
+ return false;
});
- temperatureAction.connect('activate', function (_, parameter) {
- gwSettings.set_value('temperature-unit', parameter);
- });
- gwSettings.connect('changed::temperature-unit', function () {
- temperatureAction.state =
resolveDefaultTemperatureUnit(gwSettings.get_enum('temperature-unit'));
- });
- this.add_action(temperatureAction);
-
- this.set_accels_for_action("win.selection-mode", ["Escape"]);
- this.set_accels_for_action("win.select-all", ["<Primary>a"]);
- this.set_accels_for_action("app.quit", ["<Primary>q"]);
- }
-
- vfunc_dbus_register(conn, path) {
- this._shellIntegration = new ShellIntegration();
- this._shellIntegration.export(conn, path);
- return true;
- }
-
- vfunc_dbus_unregister(conn, path) {
- this._shellIntegration.unexport(conn);
- }
+ notifyId = this.model.connect('notify::loading', function (model) {
+ if (model.loading)
+ return;
- _createWindow() {
- const window = new Window.MainWindow({ application: this });
-
- // Store a weak reference to the window for cleanup...
- this.mainWindow = window;
-
- return window;
+ model.disconnect(notifyId);
+ GLib.source_remove(timeoutId);
+ });
}
- _showWindowWhenReady(win) {
- let notifyId;
- win.present();
- if (this.model.loading) {
- let timeoutId;
- let model = this.model;
-
- timeoutId = GLib.timeout_add(GLib.PRIORITY_DEFAULT, 1000, function () {
- log('Timeout during model load, perhaps the network is not available?');
- model.disconnect(notifyId);
-
- return false;
- });
- notifyId = this.model.connect('notify::loading', function (model) {
- if (model.loading)
- return;
-
- model.disconnect(notifyId);
- GLib.source_remove(timeoutId);
- });
- }
+ return win;
+ }
- return win;
- }
+ vfunc_activate() {
+ let win = this._createWindow();
+ win.showDefault();
+ this._showWindowWhenReady(win);
+ }
- vfunc_activate() {
- let win = this._createWindow();
- win.showDefault();
- this._showWindowWhenReady(win);
- }
+ vfunc_shutdown() {
+ GWeather.Info.store_cache();
+ this.model.saveSettingsNow();
- vfunc_shutdown() {
- GWeather.Info.store_cache();
- this.model.saveSettingsNow();
+ // Ensure our main window is cleaned up before we exit.
+ this.mainWindow?.run_dispose();
+ this.mainWindow = undefined;
- // Ensure our main window is cleaned up before we exit.
- this.mainWindow?.run_dispose();
- this.mainWindow = undefined;
+ super.vfunc_shutdown();
+ }
+};
- super.vfunc_shutdown();
- }
- });
+GObject.registerClass(WeatherApplication);
diff --git a/src/app/city.js b/src/app/city.js
index 8bcacb5..a219e4e 100644
--- a/src/app/city.js
+++ b/src/app/city.js
@@ -53,8 +53,8 @@ export const WeatherWidget = GObject.registerClass({
'attributionLabel'
],
}, class WeatherWidget extends Gtk.Widget {
- _init(application, window) {
- super._init({
+ constructor(application, window) {
+ super({
name: 'weather-page'
});
@@ -66,7 +66,9 @@ export const WeatherWidget = GObject.registerClass({
this._info = null;
- this._worldView = new WorldView.WorldContentView(application, window, {}, this);
+ this._worldView = new WorldView.WorldContentView(application, window, {
+ align: Gtk.Align.START,
+ });
this._placesButton.set_popover(this._worldView);
for (const adjustment of [this._forecastHourlyAdjustment, this._forecastDailyAdjustment]) {
@@ -188,26 +190,14 @@ export const WeatherWidget = GObject.registerClass({
update(info) {
this._info = info;
- let location = info.location;
- let city = location;
- if (location.get_level() == GWeather.LocationLevel.WEATHER_STATION)
- city = location.get_parent();
-
- let country = city.get_parent();
- while (country && country.get_level() > GWeather.LocationLevel.COUNTRY)
- country = country.get_parent();
-
- if (country)
- this._placesButton.set_label(city.get_name() + ', ' + country.get_name());
- else
- this._placesButton.set_label(city.get_name());
+ const label = Util.getNameAndCountry(info.location);
+ this._placesButton.set_label(label.join(', '));
this._worldView.refilter();
this._conditionsImage.iconName = `${info.get_icon_name()}-large`;
const [, tempValue] = info.get_value_temp(GWeather.TemperatureUnit.DEFAULT);
- console.log(tempValue);
this._temperatureLabel.label = '%d°'.format(Math.round(tempValue));
const [, apparentValue] = info.get_value_apparent(GWeather.TemperatureUnit.DEFAULT);
@@ -281,16 +271,14 @@ export const WeatherView = GObject.registerClass({
InternalChildren: ['spinner', 'stack']
}, class WeatherView extends Gtk.Widget {
- _init(application, window, params) {
- super._init(params);
+ constructor(application, window, params) {
+ super(params);
this._infoPage = new WeatherWidget(application, window);
this._stack.add_named(this._infoPage, 'info');
this._info = null;
this._updateId = 0;
-
- this._desktopSettings = new Gio.Settings({ schema_id: 'org.gnome.desktop.interface' });
}
vfunc_unroot() {
@@ -315,20 +303,34 @@ export const WeatherView = GObject.registerClass({
this._info = info;
if (info) {
+ this._stack.visible_child_name = 'loading';
+ this._spinner.start();
this._updateId = this._info.connect('updated', (info) => {
this._onUpdate(info)
});
- if (info.is_valid())
+
+ if (info.is_valid()) {
this._onUpdate(info);
+ } else {
+ info.update();
+ }
}
}
+ vfunc_map() {
+ super.vfunc_map();
+
+ this._spinner.start();
+ }
+
vfunc_unmap() {
if (this._updateId) {
this._info.disconnect(this._updateId);
this._updateId = 0;
}
+ this._spinner.stop();
+
super.vfunc_unmap();
}
diff --git a/src/app/dailyForecast.js b/src/app/dailyForecast.js
index aab4d00..0168c43 100644
--- a/src/app/dailyForecast.js
+++ b/src/app/dailyForecast.js
@@ -24,10 +24,10 @@ import GWeather from 'gi://GWeather';
import * as Thermometer from './thermometer.js';
import * as Util from '../misc/util.js';
-export const DailyForecastBox = GObject.registerClass(class DailyForecastBox extends Gtk.Box {
+export class DailyForecastBox extends Gtk.Box {
- _init() {
- super._init({
+ constructor() {
+ super({
orientation: Gtk.Orientation.HORIZONTAL,
spacing: 0,
name: 'daily-forecast-box',
@@ -160,7 +160,8 @@ export const DailyForecastBox = GObject.registerClass(class DailyForecastBox ext
entry.unparent();
}
}
-});
+};
+GObject.registerClass(DailyForecastBox);
export const DayEntry = GObject.registerClass({
Template: 'resource:///org/gnome/Weather/day-entry.ui',
@@ -176,7 +177,7 @@ export const DayEntry = GObject.registerClass({
'eveningHumidity', 'eveningWind'],
}, class DayEntry extends Gtk.Widget {
- _init(params) {
+ constructor(params) {
const {
datetime,
maxTemp,
@@ -190,7 +191,7 @@ export const DayEntry = GObject.registerClass({
evening
} = params;
- super._init();
+ super();
this.datetime = datetime;
@@ -243,6 +244,12 @@ export const DayEntry = GObject.registerClass({
this._setWindInfo(eveningInfo, this._eveningWind);
}
+ vfunc_unroot() {
+ [...this].forEach(child => child.unparent());
+
+ super.vfunc_unroot();
+ }
+
_setWindInfo(info, label) {
let [ok, speed, direction] = info.get_value_wind(GWeather.SpeedUnit.DEFAULT);
if (ok) {
diff --git a/src/app/entry.js b/src/app/entry.js
index c5ee54d..83d9a15 100644
--- a/src/app/entry.js
+++ b/src/app/entry.js
@@ -4,7 +4,7 @@ import GObject from 'gi://GObject';
import Gtk from 'gi://Gtk';
import GWeather from 'gi://GWeather';
-import * as World from './world.js';
+import * as Util from '../misc/util.js';
import { LocationRow } from './locationRow.js';
GWeather.Location.prototype[Symbol.iterator] = function* () {
@@ -16,33 +16,33 @@ GWeather.Location.prototype[Symbol.iterator] = function* () {
}
}
-/** @typedef {Gio.ListModel} ListModel */
-
function getAllCitiesAndWeatherStations() {
- const locations = [];
+ const locations = new Set();
for (const region of GWeather.Location.get_world()) {
for (const country of region) {
for (const location of country) {
const level = location.get_level();
if (level === GWeather.LocationLevel.ADM1) {
- locations.push(location);
-
for (const cityOrStation of location) {
- const level = cityOrStation.get_level()
+ const level = cityOrStation.get_level();
- if (level === GWeather.LocationLevel.WEATHER_STATION || level ===
GWeather.LocationLevel.CITY) {
- locations.push(cityOrStation);
+ if (level === GWeather.LocationLevel.CITY) {
+ locations.add(cityOrStation);
+ } else if (level === GWeather.LocationLevel.WEATHER_STATION) {
+ locations.add(cityOrStation);
}
}
+
} else if (level === GWeather.LocationLevel.CITY) {
- locations.push(location);
+ locations.add(location);
} else if (level === GWeather.LocationLevel.WEATHER_STATION) {
- locations.push(location);
+ locations.add(location);
}
}
}
}
- return locations;
+
+ return [...locations.values()];
}
const LocationListModel = GObject.registerClass(
@@ -50,8 +50,8 @@ const LocationListModel = GObject.registerClass(
Implements: [Gio.ListModel]
},
class LocationListModel extends GObject.Object {
- _init() {
- super._init();
+ constructor() {
+ super();
this._show_named_timezones = false;
@@ -61,8 +61,9 @@ const LocationListModel = GObject.registerClass(
/**
* @this {ListModel & this}
*/
- fill() {
- this._list.push(...getAllCitiesAndWeatherStations());
+ load() {
+ const items = getAllCitiesAndWeatherStations()
+ this._list.push(...items);
this.items_changed(0, 0, this._list.length);
}
@@ -84,18 +85,30 @@ const LocationListModel = GObject.registerClass(
}
);
+const locationListModel = new LocationListModel();
+imports.mainloop.idle_add(() => {
+ try {
+ locationListModel.load();
+ } catch (error) {
+ console.error(error);
+ }
+
+ return false;
+});
+
// Avoid the overhead of closures and Gtk.StringFilter
const LocationFilter = GObject.registerClass(
class LocationFilter extends Gtk.Filter {
- _init() {
- super._init();
+ constructor() {
+ super();
/** @type {WeakMap<GWeather.Location, string>} */
this._itemMap = new WeakMap();
this._filter = null;
this._filterLowerCase = null;
}
+
setFilterString(filter) {
if (filter !== this._filter) {
this._filter = filter;
@@ -120,7 +133,8 @@ const LocationFilter = GObject.registerClass(
export const LocationSearchEntry = GObject.registerClass(
{
Properties: {
- 'location': GObject.ParamSpec.object("location", "location", "location",
GObject.ParamFlags.READWRITE, GWeather.Location.$gtype)
+ 'placeholder-text': GObject.ParamSpec.string('placeholder-text', 'placeholder-text',
'placeholder-text', GObject.ParamFlags.READWRITE, ''),
+ 'location': GObject.ParamSpec.object('location', 'location', 'location',
GObject.ParamFlags.READWRITE, GWeather.Location.$gtype)
},
Signals: {
'search-started': { param_types: [] },
@@ -128,20 +142,20 @@ export const LocationSearchEntry = GObject.registerClass(
}
},
class LocationSearchEntry extends Gtk.Widget {
- _init() {
- super._init();
+ constructor() {
+ super();
- this._model = new LocationListModel();
this._location = null;
this._entry = new Gtk.SearchEntry();
-
this._entry.set_parent(this);
this._entry.set_hexpand(true);
this._popup = null;
this.text = '';
- this._entry.connect("search-changed", (source) => {
+
+ this.bind_property('placeholder-text', this._entry, 'placeholder-text',
GObject.BindingFlags.DEFAULT);
+ this._entry.connect('search-changed', (source) => {
const text = source.get_text();
if (text === null || text === '') {
@@ -153,20 +167,10 @@ export const LocationSearchEntry = GObject.registerClass(
if (!this.text) this.emit('search-started');
this.text = text;
+
this._filter?.setFilterString(text);
this.emit('search-updated', text);
});
-
- imports.mainloop.idle_add(() => {
- try {
- this._model.fill();
-
- } catch (error) {
- console.error(error);
- }
-
- return false;
- });
}
get searchText() {
@@ -190,13 +194,13 @@ export const LocationSearchEntry = GObject.registerClass(
* @param {Gtk.ListView} listview
*/
setListView(listview) {
- if (this._listview) {
- this._listview.set_factory(null);
- this._listview.set_model(null);
- this._listview.unparent();
+ if (this._listView) {
+ this._listView.set_factory(null);
+ this._listView.set_model(null);
+ this._listView.unparent();
}
- this._listview = listview;
+ this._listView = listview;
const factory = this._buildFactory();
listview.set_factory(factory);
const selection = this._populateModel();
@@ -208,8 +212,9 @@ export const LocationSearchEntry = GObject.registerClass(
this._filter = filter;
let filter_model = new Gtk.FilterListModel({
- model: this._model,
- filter: this._filter
+ model: locationListModel,
+ filter: this._filter,
+ incremental: true,
});
let selection = new Gtk.SingleSelection({
model: filter_model
@@ -228,20 +233,19 @@ export const LocationSearchEntry = GObject.registerClass(
_buildFactory() {
let factory = new Gtk.SignalListItemFactory();
- this._setupId = factory.connect("setup", (source, item) => {
-
+ this._setupId = factory.connect('setup', (_, item) => {
item.set_child(new LocationRow({ name: '', countryName: '' }));
});
- this._bindId = factory.connect("bind", (source, listitem) => {
+ this._bindId = factory.connect('bind', (_, listitem) => {
const row = listitem.get_child();
/** @type {GWeather.Location} */
const location = listitem.get_item();
if (row instanceof LocationRow) {
- const parentName = location.get_parent().get_name();
- const locationName = location.get_name();
- row.name = locationName;
- row.countryName = parentName ?? '';
+ const [name, countryName = ''] = Util.getNameAndCountry(location);
+
+ row.name = name;
+ row.countryName = countryName;
}
});
@@ -249,7 +253,7 @@ export const LocationSearchEntry = GObject.registerClass(
}
vfunc_unroot() {
- this._listview?.set_model(null);
+ this._listView?.set_model(null);
super.vfunc_unroot();
}
diff --git a/src/app/hourlyForecast.js b/src/app/hourlyForecast.js
index ca6e07e..5fd9d7b 100644
--- a/src/app/hourlyForecast.js
+++ b/src/app/hourlyForecast.js
@@ -29,9 +29,9 @@ import * as Util from '../misc/util.js';
// In microseconds
const TWENTY_FOUR_HOURS = 24 * 3600 * 1000 * 1000;
-export const HourlyForecastBox = GObject.registerClass(class HourlyForecastBox extends Gtk.Box {
- _init() {
- super._init({
+export class HourlyForecastBox extends Gtk.Box {
+ constructor() {
+ super({
orientation: Gtk.Orientation.HORIZONTAL,
spacing: 0,
name: 'hourly-forecast-box',
@@ -180,7 +180,7 @@ export const HourlyForecastBox = GObject.registerClass(class HourlyForecastBox e
const graphMaxY = height - lineWidth / 2 - spacing - entryTemperatureLabelHeight - spacing;
const graphHeight = graphMaxY - graphMinY;
- let [, strokeColor] = this.get_style_context().lookup_color('temp_chart_stroke_color');
+ let [, strokeColor] = this.get_style_context().lookup_color('weather_temp_chart_stroke_color');
Gdk.cairo_set_source_rgba(cr, strokeColor);
let x = 0;
@@ -199,7 +199,7 @@ export const HourlyForecastBox = GObject.registerClass(class HourlyForecastBox e
cr.setLineWidth(lineWidth);
cr.strokePreserve();
- let [, fillColor] = this.get_style_context().lookup_color('temp_chart_fill_color');
+ let [, fillColor] = this.get_style_context().lookup_color('weather_temp_chart_fill_color');
Gdk.cairo_set_source_rgba(cr, fillColor);
@@ -210,16 +210,16 @@ export const HourlyForecastBox = GObject.registerClass(class HourlyForecastBox e
super.vfunc_snapshot(snapshot);
cr.$dispose();
}
-});
+};
+GObject.registerClass(HourlyForecastBox);
export const HourEntry = GObject.registerClass({
Template: 'resource:///org/gnome/Weather/hour-entry.ui',
InternalChildren: ['timeLabel', 'image', 'temperatureLabel'],
}, class HourEntry extends Gtk.Widget {
-
- _init({ timeLabel, info, ...params }) {
- super._init({ ...params });
+ constructor({ timeLabel, info, ...params }) {
+ super({ ...params });
Object.assign(this.layoutManager, {
orientation: Gtk.Orientation.VERTICAL,
@@ -229,6 +229,12 @@ export const HourEntry = GObject.registerClass({
this._image.iconName = info.get_icon_name() + '-small';
this._temperatureLabel.label = Util.getTempString(info);
}
+
+ vfunc_unroot() {
+ [...this].forEach(child => child.unparent());
+
+ super.vfunc_unroot();
+ }
});
HourEntry.set_layout_manager_type(Gtk.BoxLayout);
\ No newline at end of file
diff --git a/src/app/locationRow.js b/src/app/locationRow.js
index 0fdf1c0..c0f4459 100644
--- a/src/app/locationRow.js
+++ b/src/app/locationRow.js
@@ -8,8 +8,8 @@ export const LocationRow = GObject.registerClass({
Template: GLib.Uri.resolve_relative(import.meta.url, './locationRow.ui', 0),
InternalChildren: ['label', 'countryLabel', 'locationIcon', 'currentIcon'],
}, class LocationRow extends Gtk.Widget {
- _init({ name, countryName, isSelected = false, isCurrentLocation = false }) {
- super._init({});
+ constructor({ name, countryName, isSelected = false, isCurrentLocation = false }) {
+ super();
Object.assign(this.layoutManager, {
orientation: Gtk.Orientation.HORIZONTAL,
@@ -36,5 +36,11 @@ export const LocationRow = GObject.registerClass({
set isSelected(is) {
this._currentIcon.visible = is;
}
+
+ vfunc_unroot() {
+ [...this].forEach(child => child.unparent());
+
+ super.vfunc_unroot();
+ }
});
LocationRow.set_layout_manager_type(Gtk.BoxLayout);
diff --git a/src/app/locationRow.ui b/src/app/locationRow.ui
index cea4e77..56b31fd 100644
--- a/src/app/locationRow.ui
+++ b/src/app/locationRow.ui
@@ -10,6 +10,9 @@
<property name="name">label</property>
<property name="justify">left</property>
<property name="halign">start</property>
+ <style>
+ <class name="title-4" />
+ </style>
</object>
</child>
<child>
@@ -17,6 +20,9 @@
<property name="name">countryLabel</property>
<property name="justify">left</property>
<property name="halign">start</property>
+ <style>
+ <class name="body" />
+ </style>
</object>
</child>
</object>
@@ -24,14 +30,17 @@
<child>
<object class="GtkImage" id="currentIcon">
<property name="name">currentIcon</property>
- <property name="hexpand">1</property>
+ <property name="visible">False</property>
<property name="icon-name">emblem-ok-symbolic</property>
+ <property name="margin-start">12</property>
<property name="halign">start</property>
</object>
</child>
<child>
<object class="GtkImage" id="locationIcon">
<property name="name">locationIcon</property>
+ <property name="visible">False</property>
+ <property name="hexpand">True</property>
<property name="icon-name">find-location-symbolic</property>
<property name="halign">end</property>
</object>
diff --git a/src/app/main.js b/src/app/main.js
index 22ba956..52adc9b 100644
--- a/src/app/main.js
+++ b/src/app/main.js
@@ -28,7 +28,7 @@ import * as system from 'system';
import Gio from 'gi://Gio';
-import {Application} from './application.js';
+import {WeatherApplication} from './application.js';
pkg.initFormat();
pkg.initGettext();
@@ -38,4 +38,4 @@ globalThis.getApp = function () {
return Gio.Application.get_default();
};
-new Application().run([system.programInvocationName, ...system.programArgs]);
+new WeatherApplication().run([system.programInvocationName, ...system.programArgs]);
diff --git a/src/app/thermometer.js b/src/app/thermometer.js
index 641622a..d040378 100644
--- a/src/app/thermometer.js
+++ b/src/app/thermometer.js
@@ -41,7 +41,7 @@ export class TemperatureRange {
}
}
-const ThermometerScale = GObject.registerClass({
+GObject.registerClass({
CssName: 'WeatherThermometerScale',
Properties: {
'range': GObject.ParamSpec.jsobject(
@@ -53,8 +53,8 @@ const ThermometerScale = GObject.registerClass({
},
}, class ThermometerScale extends Gtk.Widget {
- _init({ range = null, ...params }) {
- super._init({
+ constructor({ range = null, ...params }) {
+ super({
vexpand: true,
halign: Gtk.Align.FILL,
overflow: Gtk.Overflow.HIDDEN,
@@ -107,8 +107,8 @@ const ThermometerScale = GObject.registerClass({
snapshot.push_rounded_clip(outline);
- const [, warmColor] = this.get_style_context().lookup_color('thermometer_warm_color');
- const [, coolColor] = this.get_style_context().lookup_color('thermometer_cold_color');
+ const [, warmColor] = this.get_style_context().lookup_color('weather_thermometer_warm_color');
+ const [, coolColor] = this.get_style_context().lookup_color('weather_thermometer_cold_color');
snapshot.append_linear_gradient(
bounds,
@@ -137,8 +137,8 @@ export const Thermometer = GObject.registerClass({
),
},
}, class Thermometer extends Gtk.Widget {
- _init({ ...params }) {
- super._init(params);
+ constructor({ ...params }) {
+ super(params);
Object.assign(this.layoutManager, {
orientation: Gtk.Orientation.VERTICAL,
diff --git a/src/app/window.js b/src/app/window.js
index 32d1d77..fe8409d 100644
--- a/src/app/window.js
+++ b/src/app/window.js
@@ -22,7 +22,7 @@ import GObject from 'gi://GObject';
import Gtk from 'gi://Gtk';
import * as City from './city.js';
-import * as CurrentLocationController from './currentLocationController.js';
+import { WorldContentView } from './world.js';
const Page = {
SEARCH: 0,
@@ -32,10 +32,10 @@ const Page = {
export const MainWindow = GObject.registerClass({
Template: 'resource:///org/gnome/Weather/window.ui',
InternalChildren: ['header', 'refreshRevealer', 'refresh', 'forecastStackSwitcher', 'stack',
- 'titleStack', 'searchEntry', 'searchListView', 'searchView', 'forecastStackSwitcherBar']
+ 'titleStack', 'searchButton', 'searchView', 'forecastStackSwitcherBar']
}, class MainWindow extends Adw.ApplicationWindow {
- _init(params) {
- super._init(params);
+ constructor(params) {
+ super(params);
this._world = this.application.world;
this.currentInfo = null;
@@ -60,12 +60,10 @@ export const MainWindow = GObject.registerClass({
this._searchView.icon_name = pkg.name;
- this._searchEntry.connect('notify::location', (entry) => {
- if (entry.location) {
- let info = this._model.addNewLocation(entry.location);
- this._model.setSelectedLocation(info);
- }
+ this._worldView = new WorldContentView(this.application, this, {
+ align: Gtk.Align.CENTER,
});
+ this._searchButton.set_popover(this._worldView);
this._pageWidgets[Page.CITY].push(this._refresh);
@@ -88,6 +86,8 @@ export const MainWindow = GObject.registerClass({
vfunc_unroot() {
this._cityView.unparent();
this._cityView = null;
+ this._worldView.unparent();
+ this._worldView = null;
super.vfunc_unroot();
}
@@ -122,13 +122,8 @@ export const MainWindow = GObject.registerClass({
showSearch(text) {
this._showingDefault = false;
this._refreshRevealer.reveal_child = true;
- // this._cityView.setTimeVisible(false);
this._stack.set_visible_child(this._searchView);
this._goToPage(Page.SEARCH);
- this._searchEntry.text = text;
- this._searchEntry.setListView(this._searchListView);
- // if (text.length > 0)
- // this._searchEntry.get_completion().complete();
}
updateCurrentLocation(info) { }
diff --git a/src/app/world.js b/src/app/world.js
index 383213e..d757667 100644
--- a/src/app/world.js
+++ b/src/app/world.js
@@ -20,17 +20,17 @@
import GObject from 'gi://GObject';
import Gtk from 'gi://Gtk';
+import * as Util from '../misc/util.js';
import { LocationRow } from './locationRow.js';
-class _WorldContentView extends Gtk.Popover {
- static [GObject.TypeName] = 'WorldContentView';
-
- _init(application, window, params = {}) {
- super._init({
- // halign: Gtk.Align.START,
+export class WorldContentView extends Gtk.Popover {
+ constructor(application, window, { align, ...params } = {}) {
+ super({
+ ...params,
hexpand: false,
+ halign: align,
vexpand: false,
- ...params,
+ hasArrow: false,
});
this.add_css_class('weather-popover');
@@ -43,38 +43,44 @@ class _WorldContentView extends Gtk.Popover {
this._searchListView = builder.get_object('search-list-view');
this._searchListScrollWindow = builder.get_object('search-list-scroll-window');
- this._searchListScrollWindow.set_size_request(300, 400);
+ this._searchListScrollWindow.set_size_request(320, 200);
this.model = application.model;
this._window = window;
+ this._listboxScrollWindow = builder.get_object('locations-list-scroll-window');
this._listbox = builder.get_object('locations-list-box');
- this._listbox.set_header_func((row, previous) => {
- let hasHeader = row.get_header() != null;
- let shouldHaveHeader = previous != null;
- if (hasHeader != shouldHaveHeader) {
- if (shouldHaveHeader)
- row.set_header(new Gtk.Separator());
- else
- row.set_header(null);
- }
+ this._listbox.bind_model(this.model, (info) => {
+ return this._buildLocation(this.model, info);
});
this._locationEntry = builder.get_object('location-entry');
this._locationEntry.setListView(this._searchListView);
- this._locationEntry.connect('search-started', (entry, term) => {
- this._syncStackPopover();
+ this._locationEntry.connect('search-started', () => {
+ this._stackPopover.set_visible_child(this._searchListScrollWindow);
+ });
+ this._locationEntry.connect('search-updated', (entry) => {
+ if (!entry.searchText) {
+ this._stackPopover.set_visible_child(this._listboxScrollWindow);
+ return;
+ }
+
+ this._stackPopover.set_visible_child(this._searchListScrollWindow);
});
this._locationEntry.connect('notify::location', (entry) => {
- if (entry.searchText)
- entry.searchText = '';
+ const location = entry.location;
+ entry.searchText = '';
+
+ this._locationChanged(location);
- this._locationChanged(entry.location);
- this._syncStackPopover();
+ this._stackPopover.set_visible_child(this._listboxScrollWindow);
- if (!entry.searchText)
+ // Defer the popdown to allow the stack to re-render
+ imports.mainloop.idle_add(() => {
this.popdown();
+ return false;
+ });
});
this.connect('show', () => {
@@ -86,47 +92,29 @@ class _WorldContentView extends Gtk.Popover {
this._listbox.connect('row-activated', (listbox, row) => {
if (row._info)
this.model.setSelectedLocation(row._info);
- this.popdown();
- });
- this.model.connect('selected-location-changed', (model, info) => {
- console.log('selected')
- this._window.showInfo(info);
- this._onLocationAdded(model);
+ // Defer the popdown to allow the stack to re-render
+ imports.mainloop.idle_add(() => {
+ this.popdown();
+ return false;
+ });
});
- this.model.connect('current-location-changed', (model, info) => {
- this._onLocationAdded(model);
+ this.model.connect('selected-location-changed', (_, info) => {
+ this._window.showInfo(info);
});
-
this._stackPopover = builder.get_object('popover-stack');
- this._stackPopover.set_size_request(350, 400);
- this._listbox.set_filter_func((row) => this._filterListbox(row));
-
- this.model.connect('location-added', (model) => {
- this._onLocationAdded(model);
- });
-
- this.model.connect('location-removed', (model) => {
- this._onLocationAdded(model);
- });
+ this._stackPopover.set_visible_child(this._listboxScrollWindow);
this._currentLocationAdded = false;
- this._onLocationAdded(this.model);
}
vfunc_unroot() {
- this._listbox.set_header_func(null);
- this._window = null;
- // // TODO
- // [...this._listbox].forEach(row => {
+ this._listbox.bind_model(null, null);
- // row._info = null;
- // row.child.unparent();
- // });
- // this._listbox = null;
+ this._window = null;
super.vfunc_unroot();
}
@@ -135,19 +123,6 @@ class _WorldContentView extends Gtk.Popover {
this._listbox.invalidate_filter();
}
- _syncStackPopover() {
- if (this._locationEntry.searchText) {
- this._stackPopover.set_visible_child(this._searchListScrollWindow);
- } else {
- this._stackPopover.set_visible_child(this._listbox);
- }
- }
-
- _filterListbox(row) {
- return this._window.currentInfo == null ||
- row._info != this._window.currentInfo;
- }
-
_locationChanged(location) {
if (location) {
let info = this.model.addNewLocation(location);
@@ -155,32 +130,18 @@ class _WorldContentView extends Gtk.Popover {
}
}
- _onLocationAdded(model) {
- const infos = model.getAll();
-
- // TODO: This just re-populates the listbox on each updated.
- // Working on a model-backed version to re-use components but
- // the current "model" can't simply be converted as the code
- // is shared across the service and UI and it would complicate
- // the service
- [...this._listbox].forEach(row => this._listbox.remove(row));
+ _buildLocation(model, info) {
+ if (!info) return null;
- for (const info of infos) {
- let location = info.location;
+ let location = info.location;
- let name = location.get_city_name() ?? location.get_name();
- let countryName = location.get_country_name();
-
- const grid = new LocationRow({ name, countryName, isSelected: model.isSelectedLocation(info),
isCurrentLocation: model.isCurrentLocation(info) });
- const row = new Gtk.ListBoxRow({ child: grid });
- row._info = info;
-
- this._listbox.append(row);
-
- }
+ const [name, countryName = ''] = Util.getNameAndCountry(location);
- this._syncStackPopover();
+ const grid = new LocationRow({ name, countryName, isSelected: model.isSelectedLocation(info),
isCurrentLocation: model.isCurrentLocation(info) });
+ const row = new Gtk.ListBoxRow({ child: grid });
+ row._info = info;
+ return row;
}
};
-export const WorldContentView = GObject.registerClass(_WorldContentView);
+GObject.registerClass(WorldContentView);
diff --git a/src/misc/util.js b/src/misc/util.js
index 33bc7e2..973ec69 100644
--- a/src/misc/util.js
+++ b/src/misc/util.js
@@ -164,14 +164,30 @@ function getTemp(info) {
}
function formatTemperature(value) {
- return value ? `${Math.round(value).toFixed(0)}°` : undefined;
+ return typeof value === 'number' ? `${Math.round(value).toFixed(0)}°` : undefined;
};
function getTempString(info) {
- let [ok, temp] = info.get_value_temp(GWeather.TemperatureUnit.DEFAULT);
- if (!ok)
- return "--";
- return formatTemperature(temp);
+ try {
+ let [, temp] = info.get_value_temp(GWeather.TemperatureUnit.DEFAULT);
+ return formatTemperature(temp);
+ } catch {
+ return "";
+ }
+}
+
+/**
+ * @returns {[string] | [string, string]}
+ */
+function getNameAndCountry(location) {
+ let country = location.get_parent();
+ while (country && country.get_level() > GWeather.LocationLevel.COUNTRY)
+ country = country.get_parent();
+
+ if (country)
+ return [location.get_name(), country.get_name()];
+ else
+ return [location.get_name()];
}
export {
@@ -191,5 +207,6 @@ export {
getDay,
easeOutCubic,
getEnabledProviders,
- getWeatherConditions
+ getWeatherConditions,
+ getNameAndCountry
}
\ No newline at end of file
diff --git a/src/org.gnome.Weather.BackgroundService.in b/src/org.gnome.Weather.BackgroundService.in
index c7bda28..53c9ccc 100755
--- a/src/org.gnome.Weather.BackgroundService.in
+++ b/src/org.gnome.Weather.BackgroundService.in
@@ -7,10 +7,6 @@ imports.package.init({ name: "@APP_ID@",
import('resource:///org/gnome/Weather/js/service/main.js').then(({ main }) => {
main([imports.system.programInvocationName, ...imports.system.programArgs]);
}).catch(error => {
- logError(error);
+ console.error(error);
System.exit(1);
-}).finally(() => {
- imports.mainloop.quit();
});
-
-imports.mainloop.run();
\ No newline at end of file
diff --git a/src/service/main.js b/src/service/main.js
index db74c10..b0ace5e 100644
--- a/src/service/main.js
+++ b/src/service/main.js
@@ -40,8 +40,8 @@ function initEnvironment() {
const BackgroundService = GObject.registerClass(
class WeatherBackgroundService extends Gio.Application {
- _init() {
- super._init({ application_id: pkg.name,
+ constructor() {
+ super({ application_id: pkg.name,
flags: Gio.ApplicationFlags.IS_SERVICE,
inactivity_timeout: 60000 });
GLib.set_application_name(_("Weather"));
diff --git a/src/shared/world.js b/src/shared/world.js
index 7b96a2a..633f508 100644
--- a/src/shared/world.js
+++ b/src/shared/world.js
@@ -18,6 +18,7 @@
import GObject from 'gi://GObject';
import GLib from 'gi://GLib';
+import Gio from 'gi://Gio';
import GWeather from 'gi://GWeather';
import * as Util from '../misc/util.js';
@@ -25,17 +26,15 @@ import * as Util from '../misc/util.js';
export const WorldModel = GObject.registerClass({
Signals: {
'selected-location-changed': { param_types: [GWeather.Info] },
- 'current-location-changed': { param_types: [GWeather.Info] },
- 'location-added': { param_types: [GWeather.Info] },
- 'location-removed': { param_types: [GWeather.Info] }
},
Properties: {
'loading': GObject.ParamSpec.boolean('loading', '', '', GObject.ParamFlags.READABLE, false)
},
+ Implements: [Gio.ListModel]
}, class WorldModel extends GObject.Object {
- _init(world, enableGtk) {
- super._init();
+ constructor(world) {
+ super();
this._world = world;
@@ -45,31 +44,27 @@ export const WorldModel = GObject.registerClass({
this._loadingCount = 0;
this._currentLocationInfo = null;
+ this._selectedLocation = null;
this._infoList = [];
+ this.getAll();
}
get length() {
- return this.getAll().length
+ return this._allInfos.length
}
getAll() {
- // TODO: Clean this up
- const infos = [...this._infoList];
- const currentIndex = infos.findIndex(info => this._currentLocationInfo &&
info.get_location().equal(this._currentLocationInfo.get_location()))
- const selectIndex = infos.findIndex(info => this._selectedLocation &&
info.get_location().equal(this._selectedLocation.get_location()));
- if (this._currentLocationInfo && currentIndex > 0) {
- infos.splice(currentIndex, 1);
- infos.unshift(this._currentLocationInfo)
- }
+ // Ensure the current location and selected location are returned first...
+ const infos = [...this._infoList].filter(info => !this.isCurrentLocation(info) &&
!this.isSelectedLocation(info));
- if (this._selectedLocation && selectIndex > 0) {
- infos.splice(selectIndex, 1);
+ if (this._currentLocationInfo)
+ infos.unshift(this._currentLocationInfo);
- infos.unshift(this._selectedLocation)
+ if (this._selectedLocation && this._currentLocationInfo !== this._selectedLocation) {
+ infos.unshift(this._selectedLocation);
}
-
-
+ this._allInfos = infos;
return infos;
}
@@ -95,10 +90,11 @@ export const WorldModel = GObject.registerClass({
}
currentLocationChanged(location) {
- if (location)
+ if (location) {
this._currentLocationInfo = this.buildInfo(location);
- if (this._currentLocationInfo)
- this.emit('current-location-changed', this._currentLocationInfo);
+ this.addCurrentLocation(this._currentLocationInfo);
+ this.#invalidate();
+ }
}
getRecent() {
@@ -123,8 +119,17 @@ export const WorldModel = GObject.registerClass({
info = this._addLocationInternal(location);
}
- if (info)
- this.setSelectedLocation(info)
+
+ if (info) {
+ this.setSelectedLocation(info);
+ }
+
+ this.#invalidate();
+ }
+
+ #invalidate() {
+ this.getAll();
+ this.items_changed(0, this._allInfos.length, this._allInfos.length);
}
_updateLoadingCount(delta) {
@@ -162,18 +167,21 @@ export const WorldModel = GObject.registerClass({
}
isSelectedLocation(info) {
- return !!this._selectedLocation &&
((this._selectedLocation.get_location()?.equal(info.get_location())) ?? false);
+ return !!this._selectedLocation && this._selectedLocation === info;
}
isCurrentLocation(info) {
- return !!this._currentLocationInfo &&
(this._currentLocationInfo.get_location()?.equal(info.get_location()) ?? false);
+ return !!this._currentLocationInfo && this._currentLocationInfo === info;
}
addNewLocation(newLocation) {
let info = this._addLocationInternal(newLocation);
+ this._setSelectedLocation = info;
+ info._isCurrentLocation = false;
- this._queueSaveSettings();
+ this.#invalidate();
+ this._queueSaveSettings();
return info;
}
@@ -193,8 +201,17 @@ export const WorldModel = GObject.registerClass({
let locations = [];
for (let i = 0; i < this._infoList.length; i++) {
- if (!this._infoList[i]._isCurrentLocation)
- locations.push(this._infoList[i].location.serialize());
+ if (!this._infoList[i]._isCurrentLocation) {
+ let serialized = null;
+ try {
+ serialized = this._infoList[i].location.serialize();
+ } catch (error) {
+ console.error(error);
+ }
+
+ if (serialized)
+ locations.push(serialized);
+ }
}
this._settings.set_value('locations', new GLib.Variant('av', locations));
@@ -210,18 +227,18 @@ export const WorldModel = GObject.registerClass({
this._saveSettingsInternal();
}
- moveLocationToFront(info) {
- if (this._infoList.length == 0 || this._infoList[0] == info)
+ addCurrentLocation(info) {
+ if (this._infoList.includes(info))
return;
- this._removeLocationInternal(info, true);
- this._addInfoInternal(info, info._isCurrentLocation);
-
- // mark info as a manually chosen location so that we
- // save it
- info._isCurrentLocation = false;
+ const existingInfo = this._infoList.find(info => info.get_location().equal(info.location));
+ if (existingInfo) {
+ this._currentLocationInfo = existingInfo;
+ return;
+ }
- this._queueSaveSettings();
+ info._isCurrentLocation = true;
+ this._addInfoInternal(info);
}
_removeLocationInternal(oldInfo, skipDisconnect) {
@@ -243,7 +260,7 @@ export const WorldModel = GObject.registerClass({
}
}
- this.emit('location-removed', oldInfo);
+ this.#invalidate();
}
buildInfo(location) {
@@ -256,11 +273,9 @@ export const WorldModel = GObject.registerClass({
}
_addLocationInternal(newLocation) {
- for (let i = 0; i < this._infoList.length; i++) {
- let info = this._infoList[i];
- if (info.get_location().equal(newLocation))
- return info;
- }
+ const existingInfo = this._infoList.find(info => info.get_location().equal(newLocation));
+ if (existingInfo)
+ return existingInfo;
let info = this.buildInfo(newLocation);
this._addInfoInternal(info);
@@ -269,15 +284,24 @@ export const WorldModel = GObject.registerClass({
}
_addInfoInternal(info) {
-
this._infoList.unshift(info);
this.updateInfo(info);
- this.emit('location-added', info);
-
if (this._infoList.length > 10) {
let oldInfo = this._infoList.pop();
this._removeLocationInternal(oldInfo);
}
}
+
+ vfunc_get_item_type() {
+ return GWeather.Info.$gtype;
+ }
+
+ vfunc_get_n_items() {
+ return this._allInfos.length;
+ }
+
+ vfunc_get_item(n) {
+ return this._allInfos[n] ?? null;
+ }
});
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]