[gnome-weather/wip/ewlsh/gtk4] Start work on model updates, selection changes



commit b9ee873368df03323439beb0498c5638788f1d34
Author: Evan Welsh <contact evanwelsh com>
Date:   Sat Feb 5 19:07:31 2022 -0800

    Start work on model updates, selection changes

 data/application.css                       |  29 +++-
 data/org.gnome.Weather.gschema.xml         |   8 -
 data/places-popover.ui                     |  78 ++-------
 src/app/city.js                            |   3 +-
 src/app/currentLocationController.js       |  35 +---
 src/app/entry.js                           |  36 ++--
 src/app/locationRow.js                     |  40 +++++
 src/app/locationRow.ui                     |  40 +++++
 src/app/shell.js                           |   7 +-
 src/app/window.js                          |  51 ++----
 src/app/world.js                           | 254 ++++++++---------------------
 src/org.gnome.Weather.src.gresource.xml.in |   2 +
 src/shared/world.js                        | 122 ++++++++------
 tests/testutil.py                          |   3 -
 14 files changed, 304 insertions(+), 404 deletions(-)
---
diff --git a/data/application.css b/data/application.css
index 5403e46..a2b63f7 100644
--- a/data/application.css
+++ b/data/application.css
@@ -40,8 +40,33 @@
     font-weight: bold;
 }
 
-#locations-list-box {
-    border: 1px solid @borders;
+#locationEntry {
+    margin: 10px;
+}
+
+WeatherLocationRow {
+    padding: 10px;
+}
+
+WeatherLocationRow #label {
+    margin-bottom: 10px;
+    font-size: 11pt;
+}
+
+WeatherLocationRow #countryLabel {
+    font-size: 9pt;
+}
+
+.weather-popover contents {
+    padding: 0;
+}
+
+#currentIcon {
+    padding: 10px;
+}
+
+#locationIcon {
+    padding: 10px;
 }
 
 .forecast-frame {
diff --git a/data/org.gnome.Weather.gschema.xml b/data/org.gnome.Weather.gschema.xml
index f3c98f7..17b7d2d 100644
--- a/data/org.gnome.Weather.gschema.xml
+++ b/data/org.gnome.Weather.gschema.xml
@@ -9,13 +9,5 @@
         GVariant returned by gweather_location_serialize().
       </description>
     </key>
-    <key name="automatic-location" type="b">
-    <default>true</default>
-      <summary>Automatic location</summary>
-      <description>
-        The automatic location is the value of automatic-location switch which decides whether
-        to fetch current location or not.
-      </description>
-    </key>
   </schema>
 </schemalist>
diff --git a/data/places-popover.ui b/data/places-popover.ui
index f1fffaa..589969b 100644
--- a/data/places-popover.ui
+++ b/data/places-popover.ui
@@ -2,14 +2,12 @@
 <interface>
   <requires lib="gtk" version="4.0"/>
   <object class="GtkGrid" id="popover-grid">
+    <property name="name">popoverGrid</property>
+    <property name="hexpand">1</property>
     <property name="orientation">vertical</property>
-    <property name="row_spacing">10</property>
-    <property name="margin-start">12</property>
-    <property name="margin-end">12</property>
-    <property name="margin-top">12</property>
-    <property name="margin-bottom">12</property>
     <child>
       <object class="Gjs_LocationSearchEntry" id="location-entry">
+        <property name="name">locationEntry</property>
         <property name="focusable">1</property>
         <property name="width-request">300</property>
         <layout>
@@ -65,73 +63,21 @@
           </object>
         </child>
         <child>
-          <object class="GtkGrid" id="search-grid">
-            <property name="name">search-grid</property>
-            <property name="orientation">vertical</property>
-            <property name="row_spacing">10</property>
+          <object class="GtkScrolledWindow" id="search-list-scroll-window">
             <child>
-              <object class="GtkFrame">
-                <property name="name">search-frame</property>
-                <property name="child">
-                  <object class="GtkScrolledWindow" id="search-list-scroll-window">
-                    <child>
-                      <object class="GtkListView" id="search-list-view">
-                        <property name="name">search-list-view</property>
-                        <property name="hexpand">1</property>
-                      </object>
-                    </child>
-                  </object>
-                </property>
-                <layout>
-                  <property name="column">0</property>
-                  <property name="row">1</property>
-                </layout>
+              <object class="GtkListView" id="search-list-view">
+                <property name="name">search-list-view</property>
+                <property name="hexpand">1</property>
               </object>
             </child>
           </object>
         </child>
         <child>
-          <object class="GtkGrid" id="locations-grid">
-            <property name="name">locations-grid</property>
-            <property name="orientation">vertical</property>
-            <property name="row_spacing">10</property>
-            <child>
-              <object class="GtkLabel" id="recently-viewed-label">
-                <property name="label" translatable="yes">Viewed Recently</property>
-                <property name="halign">start</property>
-                <attributes>
-                  <attribute name="weight" value="PANGO_WEIGHT_BOLD"></attribute>
-                </attributes>
-                <style>
-                  <class name="dim-label"/>
-                </style>
-                <layout>
-                  <property name="column">0</property>
-                  <property name="row">0</property>
-                </layout>
-              </object>
-            </child>
-
-            <child>
-              <object class="GtkFrame" id="locations-frame">
-                <property name="name">locations-frame</property>
-                <property name="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>
-                  </object>
-                </property>
-                <layout>
-                  <property name="column">0</property>
-                  <property name="row">1</property>
-                </layout>
-              </object>
-            </child>
-            <layout>
-              <property name="column">0</property>
-              <property name="row">1</property>
-            </layout>
+          <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>
 
diff --git a/src/app/city.js b/src/app/city.js
index 4478a85..8bcacb5 100644
--- a/src/app/city.js
+++ b/src/app/city.js
@@ -66,7 +66,7 @@ export const WeatherWidget = GObject.registerClass({
 
         this._info = null;
 
-        this._worldView = new WorldView.WorldContentView(application, window);
+        this._worldView = new WorldView.WorldContentView(application, window, {}, this);
         this._placesButton.set_popover(this._worldView);
 
         for (const adjustment of [this._forecastHourlyAdjustment, this._forecastDailyAdjustment]) {
@@ -207,6 +207,7 @@ export const WeatherWidget = GObject.registerClass({
         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);
diff --git a/src/app/currentLocationController.js b/src/app/currentLocationController.js
index 8c891d6..fc66102 100644
--- a/src/app/currentLocationController.js
+++ b/src/app/currentLocationController.js
@@ -21,23 +21,14 @@ import GWeather from 'gi://GWeather';
 import Geoclue from 'gi://Geoclue';
 
 import * as Util from '../misc/util.js';
-
-/** @enum {number} */
-export const AutoLocation = {
-    DISABLED: 0,
-    ENABLED: 1,
-    NOT_AVAILABLE: 2
-};
-
 export class CurrentLocationController {
     constructor(world) {
         this._world = world;
         this._processStarted = false;
         this._settings = Util.getSettings('org.gnome.Weather');
-        let autoLocation = this._settings.get_value('automatic-location').deep_unpack();
-        this._syncAutoLocation(autoLocation);
-        if (this.autoLocation == AutoLocation.ENABLED)
-            this._startGeolocationService();
+      
+        this.autoLocationAvailable = false;
+        this._startGeolocationService();
         this.currentLocation = null;
     }
 
@@ -53,7 +44,7 @@ export class CurrentLocationController {
 
     _geoLocationFailed(e) {
         log ("Failed to connect to GeoClue2 service: " + e.message);
-        this.autoLocation = AutoLocation.NOT_AVAILABLE;
+        this.autoLocationAvailable = false;
         GLib.idle_add(GLib.PRIORITY_DEFAULT, () => {
             this._world.currentLocationChanged(null);
         });
@@ -72,6 +63,8 @@ export class CurrentLocationController {
         client.distance_threshold = 100;
 
         this._findLocation();
+
+        this.autoLocationAvailable = true;
     }
 
     _findLocation() {
@@ -94,22 +87,6 @@ export class CurrentLocationController {
         this._world.currentLocationChanged(this.currentLocation);
     }
 
-    setAutoLocation(active) {
-        this._settings.set_value('automatic-location', new GLib.Variant('b', active));
-
-        if (this.autoLocation == AutoLocation.NOT_AVAILABLE)
-            return;
-        this._autoLocationChanged(active);
-        this._syncAutoLocation(active);
-    }
-
-    _syncAutoLocation(autoLocation) {
-        if (autoLocation)
-            this.autoLocation = AutoLocation.ENABLED;
-        else
-            this.autoLocation = AutoLocation.DISABLED;
-    }
-
     _autoLocationChanged(active) {
         if (active) {
             if (!this._processStarted) {
diff --git a/src/app/entry.js b/src/app/entry.js
index 199b6e5..c5ee54d 100644
--- a/src/app/entry.js
+++ b/src/app/entry.js
@@ -5,7 +5,7 @@ import Gtk from 'gi://Gtk';
 import GWeather from 'gi://GWeather';
 
 import * as World from './world.js';
-
+import { LocationRow } from './locationRow.js';
 
 GWeather.Location.prototype[Symbol.iterator] = function* () {
     let child = this.next_child(null);
@@ -123,6 +123,7 @@ export const LocationSearchEntry = GObject.registerClass(
             'location': GObject.ParamSpec.object("location", "location", "location", 
GObject.ParamFlags.READWRITE, GWeather.Location.$gtype)
         },
         Signals: {
+            'search-started': { param_types: [] },
             'search-updated': { param_types: [GObject.String] },
         }
     },
@@ -139,25 +140,19 @@ export const LocationSearchEntry = GObject.registerClass(
             this._entry.set_hexpand(true);
             this._popup = null;
 
-
+            this.text = '';
             this._entry.connect("search-changed", (source) => {
                 const text = source.get_text();
 
                 if (text === null || text === '') {
+                    this.text = null;
                     this._filter?.setFilterString(text);
                     this.emit('search-updated', null);
-
-                    if (this._popup && this._popup.visible) {
-                        this._popup.visible = false;
-                    }
                     return;
                 }
 
-                if (this._popup && !this._popup.visible) {
-                    this._popup.visible = true;
-                    this._entry?.grab_focus();
-                }
-
+                if (!this.text) this.emit('search-started');
+                this.text = text;
                 this._filter?.setFilterString(text);
                 this.emit('search-updated', text);
             });
@@ -174,6 +169,14 @@ export const LocationSearchEntry = GObject.registerClass(
             });
         }
 
+        get searchText() {
+            return this._entry.text;
+        }
+
+        set searchText(text) {
+            this._entry.text = text;
+        }
+
         set location(loc) {
             this._location = loc;
             this.notify('location');
@@ -218,10 +221,6 @@ export const LocationSearchEntry = GObject.registerClass(
 
                 if (location instanceof GWeather.Location) {
                     this.location = location;
-
-                    if (this._popup) {
-                        this._popup.popdown();
-                    }
                 }
             });
             return selection;
@@ -231,17 +230,18 @@ export const LocationSearchEntry = GObject.registerClass(
             let factory = new Gtk.SignalListItemFactory();
             this._setupId = factory.connect("setup", (source, item) => {
 
-                item.set_child(new World.LocationRow('--', false));
+                item.set_child(new LocationRow({ name: '', countryName: '' }));
             });
             this._bindId = factory.connect("bind", (source, listitem) => {
                 const row = listitem.get_child();
                 /** @type {GWeather.Location} */
                 const location = listitem.get_item();
 
-                if (row instanceof World.LocationRow) {
+                if (row instanceof LocationRow) {
                     const parentName = location.get_parent().get_name();
                     const locationName = location.get_name();
-                    row.locationName = parentName ? [locationName, parentName].join(', ') : locationName;
+                    row.name = locationName;
+                    row.countryName = parentName ?? '';
                 }
             });
 
diff --git a/src/app/locationRow.js b/src/app/locationRow.js
new file mode 100644
index 0000000..0fdf1c0
--- /dev/null
+++ b/src/app/locationRow.js
@@ -0,0 +1,40 @@
+import GObject from 'gi://GObject';
+import Gtk from 'gi://Gtk';
+import GLib from 'gi://GLib';
+
+// TODO: Finish converting into a proper GObject w/ properties
+export const LocationRow = GObject.registerClass({
+    CssName: 'WeatherLocationRow',
+    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({});
+
+        Object.assign(this.layoutManager, {
+            orientation: Gtk.Orientation.HORIZONTAL,
+        });
+
+        this.name = name;
+        this.countryName = countryName ?? '';
+        this.isSelected = isSelected;
+        this.isCurrentLocation = isCurrentLocation;
+    }
+
+    set name(name) {
+        this._label.label = name;
+    }
+
+    set countryName(name) {
+        this._countryLabel.label = name;
+    }
+
+    set isCurrentLocation(is) {
+        this._locationIcon.visible = is;
+    }
+
+    set isSelected(is) {
+        this._currentIcon.visible = is;
+    }
+});
+LocationRow.set_layout_manager_type(Gtk.BoxLayout);
diff --git a/src/app/locationRow.ui b/src/app/locationRow.ui
new file mode 100644
index 0000000..b926a49
--- /dev/null
+++ b/src/app/locationRow.ui
@@ -0,0 +1,40 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+    <requires lib="gtk" version="4.0"/>
+    <template class="Gjs_LocationRow">
+        <child>
+            <object class="GtkBox">
+                <property name="orientation">vertical</property>
+                <child>
+                    <object class="GtkLabel" id="label">
+                        <property name="name">label</property>
+                        <property name="justify">left</property>
+                        <property name="halign">start</property>
+                    </object>
+                </child>
+                <child>
+                    <object class="GtkLabel" id="countryLabel">
+                        <property name="name">countryLabel</property>
+                        <property name="justify">left</property>
+                        <property name="halign">start</property>
+                    </object>
+                </child>
+            </object>
+        </child>
+        <child>
+            <object class="GtkImage" id="currentIcon">
+                <property name="name">currentIcon</property>
+                <property name="hexpand">1</property>
+                <property name="icon-name">emblem-ok-symbolic</property>
+                <property name="halign">start</property>
+            </object>
+        </child>
+        <child>
+            <object class="GtkImage" id="locationIcon">
+                <property name="name">locationIcon</property>
+                <property name="icon-name">find-location-symbolic</property>
+                <property name="halign">end</property>
+            </object>
+        </child>
+    </template>
+</interface>
\ No newline at end of file
diff --git a/src/app/shell.js b/src/app/shell.js
index eeab176..3335454 100644
--- a/src/app/shell.js
+++ b/src/app/shell.js
@@ -29,10 +29,6 @@ export class ShellIntegration {
 
         this._settings = new Gio.Settings({ schema_id: 'org.gnome.Weather' });
 
-        this._settings.connect('changed::automatic-location', () => {
-            this._impl.emit_property_changed('AutomaticLocation',
-                new GLib.Variant('b', this.AutomaticLocation));
-        });
         this._settings.connect('changed::locations', () => {
             this._impl.emit_property_changed('Locations',
                 new GLib.Variant('av', this.Locations));
@@ -48,7 +44,8 @@ export class ShellIntegration {
     }
 
     get AutomaticLocation() {
-        return this._settings.get_boolean('automatic-location');
+        // We follow whether the user has location services on.
+        return true;
     }
 
     get Locations() {
diff --git a/src/app/window.js b/src/app/window.js
index e1c4db2..486438d 100644
--- a/src/app/window.js
+++ b/src/app/window.js
@@ -60,9 +60,12 @@ export const MainWindow = GObject.registerClass({
 
         this._searchView.icon_name = pkg.name;
 
-        this._searchEntry.connect('notify::location', (entry) => {
-            this._searchLocationChanged(entry);
-        });
+        // this._searchEntry.connect('notify::location', (entry, location) => {
+        //     if (location) {
+        //         let info = this._model.addNewLocation(location);
+        //         this._model.setSelectedLocation(info);
+        //     }
+        // });
 
         this._pageWidgets[Page.CITY].push(this._refresh);
 
@@ -98,13 +101,6 @@ export const MainWindow = GObject.registerClass({
         this._cityView.update();
     }
 
-    _searchLocationChanged(entry) {
-        if (entry.location) {
-            let info = this._model.addNewLocation(entry.location, false);
-            this.showInfo(info, false);
-        }
-    }
-
     _goToPage(page) {
         for (let i = 0; i < this._pageWidgets[this._currentPage].length; i++)
             this._pageWidgets[this._currentPage][i].hide();
@@ -120,12 +116,12 @@ export const MainWindow = GObject.registerClass({
         this._showingDefault = true;
         this._refreshRevealer.reveal_child = false;
         let clc = this.application.currentLocationController;
-        let autoLocation = clc.autoLocation;
-        let currentLocation = clc.currentLocation;
-        if (currentLocation)
-            this.showInfo(this._model.getCurrentLocation(), false);
-        else if (autoLocation != CurrentLocationController.AutoLocation.ENABLED)
-            this.showInfo(this._model.getRecent(), false);
+       
+        let mostRecent = this._model.getRecent();
+        if (mostRecent)
+            this.showInfo(mostRecent);
+        else
+            this.showSearch();
     }
 
     showSearch(text) {
@@ -139,29 +135,14 @@ export const MainWindow = GObject.registerClass({
             this._searchEntry.get_completion().complete();
     }
 
-    showInfo(info, isCurrentLocation) {
+    updateCurrentLocation(info) { }
+
+    showInfo(info) {
         if (!info) {
-            if (isCurrentLocation && this._showingDefault)
-                this.showDefault();
+            this.showDefault();
             return;
         }
 
-        /*
-         * Only show location updates if we have no loaded info and no
-         * search text or if we are currently showing the previous
-         * current location.
-         */
-        if (isCurrentLocation) {
-            if (this._currentPage == Page.CITY) {
-                if (!this._cityView.info._isCurrentLocation)
-                    return;
-            } else if (this._currentPage == Page.SEARCH) {
-                // TODO
-                // if (this._searchEntry.text.length > 0)
-                //    return;
-            }
-        }
-
         this._showingDefault = false;
         this._refreshRevealer.reveal_child = true;
         this.currentInfo = info;
diff --git a/src/app/world.js b/src/app/world.js
index a493ccf..b45cf2f 100644
--- a/src/app/world.js
+++ b/src/app/world.js
@@ -20,123 +20,20 @@
 import GObject from 'gi://GObject';
 import Gtk from 'gi://Gtk';
 
-
-// TODO: Finish converting into a proper GObject w/ properties
-export const LocationRow = GObject.registerClass(class LocationRow extends Gtk.Widget {
-
-    _init(locationName, isCurrentLocation) {
-        super._init({});
-
-        this._locationName = locationName;
-        this._isCurrentLocation = isCurrentLocation;
-
-        this._build();
-    }
-
-    get locationName() { return this._locationName };
-
-    get isCurrentLocation() { return this._isCurrentLocation; }
-
-    set locationName(value) {
-        this._locationName = value;
-        this._build();
-    }
-
-    set isCurrentLocation(value) {
-        this._isCurrentLocation = value;
-        this._build();
-    }
-
-    _build() {
-
-        if (!this.grid) {
-            this.grid = new Gtk.Grid({
-                orientation: Gtk.Orientation.HORIZONTAL,
-                column_spacing: 12,
-                margin_start: 12,
-                margin_end: 12,
-                margin_top: 12,
-                margin_bottom: 12,
-                visible: true
-            });
-
-            let grid = this.grid;
-            let locationGrid = new Gtk.Grid({
-                orientation: Gtk.Orientation.HORIZONTAL,
-                column_spacing: 12,
-                halign: Gtk.Align.START,
-                hexpand: true,
-                visible: true
-            });
-            this.locationLabel = new Gtk.Label({
-                label: this.locationName,
-                use_markup: true,
-                halign: Gtk.Align.START,
-                visible: true
-            });
-
-            locationGrid.attach(this.locationLabel, 0, 0, 1, 1);
-            grid.attach(locationGrid, 0, 0, 1, 1);
-
-            let tempLabel = new Gtk.Label({
-                use_markup: true,
-                halign: Gtk.Align.END,
-                margin_start: 12,
-                visible: true
-            });
-            this.tempLabel = tempLabel;
-            grid.attach(tempLabel, 1, 0, 1, 1);
-
-            this.image = new Gtk.Image({
-                icon_size: Gtk.IconSize.LARGE,
-                use_fallback: true,
-                halign: Gtk.Align.END
-            });
-            grid.attach(this.image, 2, 0, 1, 1);
-        }
-
-        if (this.isCurrentLocation && !this.currentImage) {
-            this.currentImage = new Gtk.Image({
-                icon_size: Gtk.IconSize.LARGE,
-                icon_name: 'mark-location-symbolic',
-                use_fallback: true,
-                halign: Gtk.Align.START
-            });
-            this.grid.attach(this.currentImage, 1, 0, 1, 1);
-        } else if (!this.isCurrentLocation && this.currentImage) {
-            this.currentImage.unparent();
-            this.currentImage = null;
-        }
-
-        this.locationLabel.label = this.locationName;
-
-    }
-
-    vfunc_root() {
-        super.vfunc_root();
-
-        this.grid.set_parent(this);
-    }
-
-    vfunc_unroot() {
-        this.grid.unparent();
-        this.grid = null;
-
-        super.vfunc_unroot();
-    }
-});
-LocationRow.set_layout_manager_type(Gtk.BoxLayout);
+import { LocationRow } from './locationRow.js';
 
 class _WorldContentView extends Gtk.Popover {
     static [GObject.TypeName] = 'WorldContentView';
 
     _init(application, window, params = {}) {
         super._init({
+            // halign: Gtk.Align.START,
             hexpand: false,
             vexpand: false,
             ...params,
         });
 
+        this.add_css_class('weather-popover');
         this.update_property([Gtk.AccessibleProperty.LABEL], [_("World view")]);
         let builder = new Gtk.Builder();
         builder.add_from_resource('/org/gnome/Weather/places-popover.ui');
@@ -144,12 +41,10 @@ class _WorldContentView extends Gtk.Popover {
         let grid = builder.get_object('popover-grid');
         this.set_child(grid);
 
-        this._searchGrid = builder.get_object('empty-search-grid');
-        this._searchResultsGrid = builder.get_object('search-grid');
-        this._locationsGrid = builder.get_object('locations-grid');
         this._searchListView = builder.get_object('search-list-view');
         this._searchListScrollWindow = builder.get_object('search-list-scroll-window');
-        this._searchListScrollWindow.set_size_request(-1, 400);
+        this._searchListScrollWindow.set_size_request(300, 400);
+
         this.model = application.model;
         this._window = window;
 
@@ -166,56 +61,72 @@ class _WorldContentView extends Gtk.Popover {
         });
 
         this._locationEntry = builder.get_object('location-entry');
-       
+
         this._locationEntry.setListView(this._searchListView);
-        this._locationEntry.connect('search-updated', (_, term) => {
-            if (term) {
-                this._stackPopover.set_visible_child(this._searchResultsGrid);
-            } else {
-                this._syncStackPopover();
-            }
+        this._locationEntry.connect('search-started', (entry, term) => {
+            this._syncStackPopover();
+        });
+        this._locationEntry.connect('notify::location', (entry) => {
+            if (entry.searchText)
+                entry.searchText = '';
+             
+            this._locationChanged(entry.location);
+            this._syncStackPopover();
+
+            if (!entry.searchText)
+                this.popdown();
         });
-        this._locationEntry.connect('notify::location', (entry) => this._locationChanged(entry));
 
         this.connect('show', () => {
             this._locationEntry.grab_focus();
         });
 
-
-
         this._currentLocationController = application.currentLocationController;
 
-
-
         this._listbox.connect('row-activated', (listbox, row) => {
-            this._window.showInfo(row._info, false);
-            this.model.moveLocationToFront(row._info);
-            this.hide();
+            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);
         });
 
         this.model.connect('current-location-changed', (model, info) => {
-            this._window.showInfo(info, true);
+            this._onLocationAdded(model);
         });
 
+
         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, info, is_current) => {
-            this._onLocationAdded(model, info, is_current);
+        this.model.connect('location-added', (model) => {
+            this._onLocationAdded(model);
         });
 
-        this.model.connect('location-removed', (model, info) => {
-            this._onLocationRemoved(model, info);
+        this.model.connect('location-removed', (model) => {
+            this._onLocationAdded(model);
         });
 
         this._currentLocationAdded = false;
-        let list = this.model.getAll();
-        for (let i = list.length - 1; i >= 0; i--)
-            this._onLocationAdded(this.model, list[i], list[i]._isCurrentLocation);
+
+        this._onLocationAdded(this.model);
     }
 
     vfunc_unroot() {
         this._listbox.set_header_func(null);
+        this._window = null;
+        // TODO
+        [...this._listbox].forEach(row => {
+
+            row._info = null;
+            row.child.unparent();
+        });
+        this._listbox = null;
 
         super.vfunc_unroot();
     }
@@ -225,10 +136,11 @@ class _WorldContentView extends Gtk.Popover {
     }
 
     _syncStackPopover() {
-        if (this.model.length == 1)
-            this._stackPopover.set_visible_child(this._searchGrid);
-        else
-            this._stackPopover.set_visible_child(this._locationsGrid);
+        if (this._locationEntry.searchText) {
+            this._stackPopover.set_visible_child(this._searchListScrollWindow);
+        } else {
+            this._stackPopover.set_visible_child(this._listbox);
+        }
     }
 
     _filterListbox(row) {
@@ -236,70 +148,38 @@ class _WorldContentView extends Gtk.Popover {
             row._info != this._window.currentInfo;
     }
 
-    _locationChanged(entry) {
-        if (entry.location) {
-            let info = this.model.addNewLocation(entry.location, false);
-            this._window.showInfo(info, false);
-            this.hide();
-            entry.location = null;
+    _locationChanged(location) {
+        if (location) {
+            let info = this.model.addNewLocation(location);
+            this._window.showInfo(info);
         }
     }
 
-    _onLocationAdded(model, info, isCurrentLocation) {
-        let location = info.location;
 
+    // vfunc_unmap() {
 
-        let name = location.get_city_name();
-        const grid = new LocationRow(name, isCurrentLocation);
 
-        let row = new Gtk.ListBoxRow({ visible: true });
-        row.set_child(grid);
-        row._info = info;
-        row._isCurrentLocation = isCurrentLocation;
+    //     super.vfunc_unmap();
+    // }
 
-        if (isCurrentLocation) {
-            if (this._currentLocationAdded) {
-                let row0 = this._listbox.get_row_at_index(0);
-                if (row0) {
-                    // TODO: GTK4
-                    delete row0._info;
-                    this._listbox.remove(row0);
-                }
-            }
+    _onLocationAdded(model) {
+        const infos = model.getAll();
 
-            this._currentLocationAdded = true;
-            this._listbox.insert(row, 0);
-        } else {
-            if (this._currentLocationAdded)
-                this._listbox.insert(row, 1);
-            else
-                this._listbox.insert(row, 0);
-        }
+        [...this._listbox].forEach(row => this._listbox.remove(row));
 
-        if (info._updatedId)
-            return;
+        for (const info of infos) {
+            let location = info.location;
 
-        info._updatedId = info.connect('updated', (info) => {
-            grid.tempLabel.label = info.get_temp_summary();
-            grid.image.icon_name = info.get_symbolic_icon_name();
-        });
+            let name = location.get_city_name() ?? location.get_name();
+            let countryName = location.get_country_name();
 
-        this._syncStackPopover();
-        this._currentLocationController.currentLocation = info
-    }
+            const grid = new LocationRow({ name, countryName, isSelected: model.isSelectedLocation(info), 
isCurrentLocation: model.isCurrentLocation(info) });
+            const row = new Gtk.ListBoxRow({ child: grid });
+            row._info = info;
 
-    _onLocationRemoved(model, info) {
-        Array.from(this._listbox).forEach((row) => {
-            delete row._info;
-            this._listbox.remove(row);
-        });
+            this._listbox.append(row);
 
-        if (info._updatedId) {
-            info.disconnect(info._updatedId);
-            info._updatedId = 0;
         }
-        if (info._isCurrentLocation)
-            this._currentLocationAdded = false;
 
         this._syncStackPopover();
     }
diff --git a/src/org.gnome.Weather.src.gresource.xml.in b/src/org.gnome.Weather.src.gresource.xml.in
index 1383c50..8b06e1d 100644
--- a/src/org.gnome.Weather.src.gresource.xml.in
+++ b/src/org.gnome.Weather.src.gresource.xml.in
@@ -5,6 +5,8 @@
     <file>app/city.js</file>
     <file>app/currentLocationController.js</file>
     <file>app/hourlyForecast.js</file>
+    <file>app/locationRow.js</file>
+    <file>app/locationRow.ui</file>
     <file>app/thermometer.js</file>
     <file>app/thermometer.ui</file>
     <file>app/dailyForecast.js</file>
diff --git a/src/shared/world.js b/src/shared/world.js
index 3badf5b..b404264 100644
--- a/src/shared/world.js
+++ b/src/shared/world.js
@@ -24,9 +24,10 @@ import * as Util from '../misc/util.js';
 
 export const WorldModel = GObject.registerClass({
     Signals: {
-        'current-location-changed': { param_types: [ GWeather.Info ] },
-        'location-added': { param_types: [ GWeather.Info, GObject.Boolean ] },
-        'location-removed': { param_types: [ GWeather.Info ] }
+        '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)
@@ -48,19 +49,39 @@ export const WorldModel = GObject.registerClass({
     }
 
     get length() {
-        return this._infoList.length + (this._currentLocationInfo ? 1 : 0);
+        return this.getAll().length
     }
 
     getAll() {
-        if (this._currentLocationInfo)
-            return [this._currentLocationInfo].concat(this._infoList);
-        else
-            return [].concat(this._infoList);
+        // 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)
+        }
+
+        if (this._selectedLocation && selectIndex > 0) {
+            infos.splice(selectIndex, 1);
+
+            infos.unshift(this._selectedLocation)
+        }
+
+
+
+        return infos;
     }
 
     getAtIndex(index) {
-        if (this._currentLocationInfo) {
+        if (this._selectedLocation) {
             if (index == 0)
+                return this._selectedLocation;
+            else
+                index--;
+        }
+        if (this._currentLocationInfo) {
+            if (index == 1)
                 return this._currentLocationInfo;
             else
                 index--;
@@ -74,15 +95,10 @@ export const WorldModel = GObject.registerClass({
     }
 
     currentLocationChanged(location) {
-        if (this._currentLocationInfo)
-            this._removeLocationInternal(this._currentLocationInfo, false);
-
-        let info;
         if (location)
-            info = this.addNewLocation(location, true);
-        else
-            info = null;
-        this.emit('current-location-changed', info);
+            this._currentLocationInfo = this.buildInfo(location);
+        if (this._currentLocationInfo)
+            this.emit('current-location-changed', this._currentLocationInfo);
     }
 
     getRecent() {
@@ -92,11 +108,11 @@ export const WorldModel = GObject.registerClass({
             return null;
     }
 
-    load () {
+    load() {
         let locations = this._settings.get_value('locations').deep_unpack();
 
-        if (locations.length > 5) {
-            locations = locations.slice(0, 5);
+        if (locations.length > 10) {
+            locations = locations.slice(0, 10).filter(location => !!location);
             this._settings.set_value('locations', new GLib.Variant('av', locations));
         }
 
@@ -105,9 +121,9 @@ export const WorldModel = GObject.registerClass({
             let variant = locations[i];
             let location = this._world.deserialize(variant);
 
-            info = this._addLocationInternal(location, false);
+            info = this._addLocationInternal(location);
         }
-        this._currentLocationInfo = info
+        this.setSelectedLocation(info)
     }
 
     _updateLoadingCount(delta) {
@@ -138,21 +154,24 @@ export const WorldModel = GObject.registerClass({
         return this._loadingCount > 0;
     }
 
-    addNewLocation(newLocation, isCurrentLocation) {
-        if (!isCurrentLocation) {
-            for (let info of this._infoList) {
-                let location = info.location;
-                if (location.equal(newLocation)) {
-                    this.moveLocationToFront(info);
-                    return info;
-                }
-            }
-        }
+    setSelectedLocation(info) {
+        this._selectedLocation = info;
+        this.addNewLocation(info.get_location());
+        this.emit('selected-location-changed', info);
+    }
+
+    isSelectedLocation(info) {
+        return !!this._selectedLocation && 
((this._selectedLocation.get_location()?.equal(info.get_location())) ?? false);
+    }
+
+    isCurrentLocation(info) {
+        return !!this._currentLocationInfo && 
(this._currentLocationInfo.get_location()?.equal(info.get_location()) ?? false);
+    }
 
-        let info = this._addLocationInternal(newLocation, isCurrentLocation);
+    addNewLocation(newLocation) {
+        let info = this._addLocationInternal(newLocation);
 
-        if (!isCurrentLocation)
-            this._queueSaveSettings();
+        this._queueSaveSettings();
 
         return info;
     }
@@ -205,6 +224,8 @@ export const WorldModel = GObject.registerClass({
     }
 
     _removeLocationInternal(oldInfo, skipDisconnect) {
+        if (!oldInfo) return;
+
         if (oldInfo._loadingId && !skipDisconnect) {
             oldInfo.disconnect(oldInfo._loadingId);
             oldInfo._loadingId = 0;
@@ -224,35 +245,36 @@ export const WorldModel = GObject.registerClass({
         this.emit('location-removed', oldInfo);
     }
 
-    _addLocationInternal(newLocation, isCurrentLocation) {
+    buildInfo(location) {
+        return new GWeather.Info({
+            application_id: pkg.name,
+            contact_info: 'https://gitlab.gnome.org/GNOME/gnome-weather/-/raw/master/gnome-weather.doap',
+            location,
+            enabled_providers: this._providers
+        });
+    }
+
+    _addLocationInternal(newLocation) {
         for (let i = 0; i < this._infoList.length; i++) {
             let info = this._infoList[i];
             if (info.get_location().equal(newLocation))
                 return info;
         }
 
-        let info = new GWeather.Info({
-            application_id: pkg.name,
-            contact_info: 'https://gitlab.gnome.org/GNOME/gnome-weather/-/raw/master/gnome-weather.doap',
-            location: newLocation,
-            enabled_providers: this._providers
-        });
-        this._addInfoInternal(info, isCurrentLocation);
+        let info = this.buildInfo(newLocation);
+        this._addInfoInternal(info);
 
         return info;
     }
 
-    _addInfoInternal(info, isCurrentLocation) {
-        info._isCurrentLocation = isCurrentLocation;
+    _addInfoInternal(info) {
+
         this._infoList.unshift(info);
         this.updateInfo(info);
 
-        if (isCurrentLocation)
-            this._currentLocationInfo = info;
-
-        this.emit('location-added', info, isCurrentLocation);
+        this.emit('location-added', info);
 
-        if (this._infoList.length > 5) {
+        if (this._infoList.length > 10) {
             let oldInfo = this._infoList.pop();
             this._removeLocationInternal(oldInfo);
         }
diff --git a/tests/testutil.py b/tests/testutil.py
index 25233ad..43dc04c 100644
--- a/tests/testutil.py
+++ b/tests/testutil.py
@@ -70,7 +70,6 @@ def reset_settings():
                                 "(0.79354303905785273, "
                                 "0.16057029118347829))>)>]")
     settings.set_value("locations", parsed)
-    settings.set_value("automatic-location", GLib.Variant.new_boolean(False))
 
 
 def init():
@@ -78,12 +77,10 @@ def init():
 
     settings = Gio.Settings("org.gnome.Weather")
     _previous_locations = settings.get_value("locations")
-    _automatic_location = settings.get_value("automatic-location")
     reset_settings()
 
 
 def fini():
     settings.set_value("locations", _previous_locations)
-    settings.set_value("automatic-location", _automatic_location)
     _do_bus_call("ActivateAction",
                  GLib.Variant('(sava{sv})', ('quit', [], [])))


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