[gnome-weather/wip/ewlsh/gtk4] Initial implementation of location entry.



commit 077628da3ef6de860e4fb4657e462b3dd57efbd1
Author: Evan Welsh <contact evanwelsh com>
Date:   Tue Jan 5 13:37:43 2021 -0800

    Initial implementation of location entry.

 data/day-entry.ui                          | 101 +++++---
 data/hour-entry.ui                         |   8 -
 data/places-popover.ui                     |  29 ++-
 data/weather-widget.ui                     |   2 +-
 data/window.ui                             |   9 +-
 src/app/city.js                            |   8 +
 src/app/entry.js                           | 394 +++++++++++++++++++++++++++++
 src/app/main.js                            |  38 ++-
 src/app/window.js                          |  10 +-
 src/app/world.js                           |  25 +-
 src/org.gnome.Weather.src.gresource.xml.in |   1 +
 11 files changed, 543 insertions(+), 82 deletions(-)
---
diff --git a/data/day-entry.ui b/data/day-entry.ui
index e1ab7cf..393c488 100644
--- a/data/day-entry.ui
+++ b/data/day-entry.ui
@@ -247,53 +247,72 @@
   <template class="Gjs_DayEntry" parent="GtkBox">
     <property name="width_request">100</property>
     <property name="height_request">200</property>
-    <property name="hexpand">1</property>
-    <property name="vexpand">1</property>
+    <property name="hexpand">True</property>
+    <property name="vexpand">True</property>
+    <property name="margin_top">18</property>
+    <property name="margin_bottom">18</property>
     <property name="orientation">vertical</property>
     <property name="spacing">18</property>
     <child>
-      <object class="GtkLabel" id="nameLabel">
-        <property name="margin_top">8</property>
-        <property name="label">Tues</property>
-      </object>
-    </child>
-    <child>
-      <object class="GtkLabel" id="dateLabel">
-        <property name="margin_top">8</property>
-        <property name="label">7 June</property>
-      </object>
-    </child>
-    <child>
-      <object class="GtkImage" id="image">
-        <property name="valign">start</property>
-        <property name="vexpand">1</property>
-        <property name="pixel_size">32</property>
-        <property name="icon_name">weather-showers-symbolic</property>
-      </object>
-    </child>
-    <child>
-      <object class="GtkLabel" id="maxTemperatureLabel">
-        <property name="margin_top">8</property>
-        <property name="label">18°</property>
-      </object>
-    </child>
-    <child>
-      <object class="GtkLabel" id="minTemperatureLabel">
-        <property name="margin_top">8</property>
-        <property name="label">9°</property>
+      <object class="GtkBox">
+        <property name="orientation">vertical</property>
+        <property name="spacing">6</property>
+        <child>
+          <object class="GtkLabel" id="nameLabel">
+            <property name="label">Tues</property>
+          </object>
+        </child>
+        <child>
+          <object class="GtkLabel" id="dateLabel">
+            <property name="label">7 June</property>
+            <style>
+              <class name="small-label"/>
+            </style>
+          </object>
+        </child>
+        <child>
+          <object class="GtkImage" id="image">
+            <property name="valign">start</property>
+            <property name="vexpand">1</property>
+            <property name="pixel_size">32</property>
+            <property name="icon_name">weather-showers-symbolic</property>
+          </object>
+        </child>
       </object>
     </child>
     <child>
-      <object class="GtkMenuButton">
-        <property name="halign">center</property>
-        <property name="icon_name">view-more-symbolic</property>
-        <property name="valign">center</property>
-        <property name="popover">more_menu</property>
-        <style>
-          <class name="image-button"/>
-          <class name="circular"/>
-          <class name="flat"/>
-        </style>
+      <object class="GtkBox">
+        <property name="orientation">vertical</property>
+        <property name="spacing">12</property>
+        <child>
+          <object class="GtkLabel" id="maxTemperatureLabel">
+            <property name="label">18°</property>
+            <style>
+              <class name="forecast-temperature-label"/>
+            </style>
+          </object>
+        </child>
+        <child>
+          <object class="GtkLabel" id="minTemperatureLabel">
+            <property name="label">9°</property>
+            <style>
+              <class name="forecast-low-temperature-label"/>
+            </style>
+          </object>
+        </child>
+        <child>
+          <object class="GtkMenuButton">
+            <property name="halign">center</property>
+            <property name="icon_name">view-more-symbolic</property>
+            <property name="valign">center</property>
+            <property name="popover">more_menu</property>
+            <style>
+              <class name="image-button"/>
+              <class name="circular"/>
+              <class name="flat"/>
+            </style>
+          </object>
+        </child>
       </object>
     </child>
   </template>
diff --git a/data/hour-entry.ui b/data/hour-entry.ui
index 0c27049..436c336 100644
--- a/data/hour-entry.ui
+++ b/data/hour-entry.ui
@@ -5,8 +5,6 @@
   <template class="Gjs_HourEntry" parent="GtkBox">
     <property name="width_request">75</property>
     <property name="height_request">200</property>
-    <property name="visible">True</property>
-    <property name="can_focus">False</property>
     <property name="hexpand">True</property>
     <property name="vexpand">True</property>
     <property name="orientation">vertical</property>
@@ -15,15 +13,11 @@
     <property name="margin_bottom">18</property>
     <child>
       <object class="GtkLabel" id="timeLabel">
-        <property name="visible">True</property>
-        <property name="can_focus">False</property>
         <property name="label">Now</property>
       </object>
     </child>
     <child>
       <object class="GtkImage" id="image">
-        <property name="visible">True</property>
-        <property name="can_focus">False</property>
         <property name="valign">start</property>
         <property name="vexpand">True</property>
         <property name="pixel_size">32</property>
@@ -32,8 +26,6 @@
     </child>
     <child>
       <object class="GtkLabel" id="temperatureLabel">
-        <property name="visible">True</property>
-        <property name="can_focus">False</property>
         <property name="valign">end</property>
         <property name="label">13°</property>
         <style>
diff --git a/data/places-popover.ui b/data/places-popover.ui
index ed5adfb..d07db18 100644
--- a/data/places-popover.ui
+++ b/data/places-popover.ui
@@ -9,11 +9,8 @@
     <property name="margin-top">12</property>
     <property name="margin-bottom">12</property>
     <child>
-      <object class="GtkEntry" id="location-entry">
-        <property name="visible">True</property>
-        <property name="can_focus">True</property>
+      <object class="Gjs_LocationSearchEntry" id="location-entry">
         <property name="width-request">300</property>
-        <property name="activates_default">True</property>
         <layout>
           <property name="column">0</property>
           <property name="row">0</property>
@@ -88,7 +85,7 @@
         <property name="vhomogeneous">0</property>
         <property name="hhomogeneous">0</property>
         <child>
-          <object class="GtkGrid" id="search-grid">
+          <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>
@@ -126,6 +123,28 @@
             </child>
           </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>
+            <child>
+              <object class="GtkScrolledWindow" id="city-search-scroll">
+                <child>
+                  <object class="GtkListView" id="city-search-results">
+                    <property name="halign">center</property>
+                    <property name="valign">center</property>
+                    <property name="hexpand">True</property>
+                    <layout>
+                      <property name="column">0</property>
+                      <property name="row">0</property>
+                    </layout>
+                  </object>
+                </child>
+              </object>
+            </child>
+          </object>
+        </child>
         <child>
           <object class="GtkGrid" id="locations-grid">
             <property name="name">locations-grid</property>
diff --git a/data/weather-widget.ui b/data/weather-widget.ui
index 69a79ff..88d3db5 100644
--- a/data/weather-widget.ui
+++ b/data/weather-widget.ui
@@ -181,7 +181,7 @@
                 <child>
                   <object class="GtkLabel" id="attributionLabel">
                     <property name="name">attribution-label</property>
-                    <property name="use_markup">1</property>
+                    <property name="use_markup">True</property>
                     <property name="wrap">1</property>
                     <property name="xalign">0</property>
                     <layout>
diff --git a/data/window.ui b/data/window.ui
index cb72882..6494fed 100644
--- a/data/window.ui
+++ b/data/window.ui
@@ -5,8 +5,6 @@
     <property name="orientation">vertical</property>
     <child>
       <object class="HdyHeaderBar" id="header-bar">
-        <property name="visible">True</property>
-        <property name="can_focus">False</property>
         <property name="vexpand">False</property>
         <property name="centering_policy">strict</property>
         <child>
@@ -21,7 +19,6 @@
         </child>
         <child type="title">
           <object class="HdyViewSwitcherTitle" id="switcher-title">
-            <property name="visible">True</property>
             <property name="title" translatable="yes">Weather</property>
           </object>
         </child>
@@ -89,10 +86,7 @@
                   </object>
                 </child>
                 <child>
-                  <object class="GtkEntry" id="initial-grid-location-entry">
-                    <property name="visible">True</property>
-                    <property name="can_focus">True</property>
-                    <property name="activates_default">True</property>
+                  <object class="Gjs_LocationSearchEntry" id="initial-grid-location-entry">
                     <layout>
                       <property name="column">0</property>
                       <property name="row">3</property>
@@ -111,7 +105,6 @@
     </child>
     <child>
       <object class="HdyViewSwitcherBar" id="switcher-bar">
-        <property name="visible">True</property>
         <property name="reveal" bind-source="switcher-title" bind-property="title-visible" 
bind-flags="sync-create"/>
       </object>
     </child>
diff --git a/src/app/city.js b/src/app/city.js
index d15dcc7..676ec39 100644
--- a/src/app/city.js
+++ b/src/app/city.js
@@ -116,6 +116,10 @@ var WeatherWidget = GObject.registerClass({
         this.connect('destroy', () => this._onDestroy());
     }
 
+    _cleanup() {
+        this._worldView._cleanup();
+    }
+
     _onDestroy() {
         if (this._updatedTimeTimeoutId) {
             GLib.Source.remove(this._updatedTimeTimeoutId);
@@ -291,6 +295,10 @@ var WeatherView = GObject.registerClass({
         this._desktopSettings = new Gio.Settings({ schema_id: 'org.gnome.desktop.interface' });
     }
 
+    _cleanup() {
+        this._infoPage._cleanup();
+    }
+
     get info() {
         return this._info;
     }
diff --git a/src/app/entry.js b/src/app/entry.js
new file mode 100644
index 0000000..c6a45ac
--- /dev/null
+++ b/src/app/entry.js
@@ -0,0 +1,394 @@
+const { Gio, GLib, GWeather, GObject, Gtk, Gdk } = imports.gi;
+
+const LocationItem = GObject.registerClass(
+    {
+        Properties: {
+            'display-name': GObject.ParamSpec.string("display-name", "Display name", "", 
GObject.ParamFlags.READWRITE, ''),
+            'location': GObject.ParamSpec.boxed("location", "Display name", "", 
GObject.ParamFlags.READWRITE, GWeather.Location.$gtype),
+            'local-compare-name': GObject.ParamSpec.string("local-compare-name", "Local compare name", "", 
GObject.ParamFlags.READWRITE, ''),
+            'english-compare-name': GObject.ParamSpec.string("english-compare-name", "English compare name", 
"", GObject.ParamFlags.READWRITE, ''),
+        }
+    },
+    class LocationItem extends GObject.Object { }
+);
+
+/**
+ * @param {string} displayName
+ * @param {import("@gi-types/gweather").Location} location
+ * @param {string} localCompareName
+ * @param {string} englishCompareName
+ */
+function buildLocationItem(displayName, location, localCompareName, englishCompareName) {
+    const item = new LocationItem();
+    item.displayName = displayName;
+    item.location = location;
+    item.localCompareName = localCompareName;
+    item.englishCompareName = englishCompareName;
+    return item;
+}
+
+/** @typedef {import("@gi-types/gio").ListModel} ListModel */
+
+const LocationListModel = GObject.registerClass(
+    {
+        Implements: [Gio.ListModel]
+    },
+    class LocationListModel extends GObject.Object {
+        /** @type {Array<typeof LocationItem.prototype>} */
+        _list = [];
+        _show_named_timezones = false;
+
+        /**
+         * @param {*} klass
+         */
+        _initClass(klass) {
+            const lm = new Gtk.BinLayout();
+            klass.set_layout_manager(lm);
+            klass.set_css_name('entry');
+        }
+
+        _init() {
+            super._init();
+
+            this._top = GWeather.Location.get_world();
+            this._show_named_timezones = false;
+
+            this._list = [];
+        }
+
+        // TODO
+        // Adapted from libgweather
+
+        /**
+         * @param {import("@gi-types/gweather").Location} loc
+         * @param {string | null} parent_display_name
+         * @param {string | null} parent_compare_local_name
+         * @param {string | null} parent_compare_english_name
+         * @param {boolean} show_named_timezones
+         */
+        _fill_location_list_model(
+            loc,
+            parent_display_name,
+            parent_compare_local_name,
+            parent_compare_english_name,
+            show_named_timezones
+        ) {
+            let children = loc.get_children();
+            /** @type {string | null} */
+            let display_name;
+            /** @type {string | null} */
+            let local_compare_name;
+            /** @type {string | null} */
+            let english_compare_name;
+
+            switch (loc.get_level()) {
+                case GWeather.LocationLevel.WORLD:
+                case GWeather.LocationLevel.REGION:
+                    /* Ignore these levels of hierarchy; just recurse, passing on
+                     * the names from the parent node.
+                     */
+                    children.forEach(child => {
+                        this._fill_location_list_model(child,
+                            parent_display_name,
+                            parent_compare_local_name,
+                            parent_compare_english_name,
+                            show_named_timezones);
+                    });
+                    break;
+
+                case GWeather.LocationLevel.COUNTRY:
+                    /* Recurse, initializing the names to the country name */
+                    children.forEach(child => {
+                        this._fill_location_list_model(child,
+                            loc.get_name(),
+                            loc.get_sort_name(),
+                            loc.get_english_name(),
+                            show_named_timezones);
+                    });
+                    break;
+
+                case GWeather.LocationLevel.ADM1:
+                    /* Recurse, adding the ADM1 name to the country name */
+                    /* Translators: this is the name of a location followed by a region, for example:
+                     * 'London, United Kingdom'
+                     * You shouldn't need to translate this string unless the language has a different comma.
+                     */
+                    display_name = _("%s, %s").format(loc.get_name(), parent_display_name);
+                    local_compare_name = _("%s, %s").format(loc.get_sort_name(), parent_compare_local_name);
+                    english_compare_name = _("%s, %s").format(loc.get_english_name(), 
parent_compare_english_name);
+
+                    children.forEach(child => {
+                        this._fill_location_list_model(child,
+                            display_name, local_compare_name, english_compare_name,
+                            show_named_timezones);
+                    });
+
+                    break;
+
+                case GWeather.LocationLevel.CITY:
+                /* If there are multiple (<location>) children, we use the one
+                 * closest to the city center.
+                 *
+                 * Locations are already sorted by increasing distance from
+                 * the city.
+                 */
+                case GWeather.LocationLevel.WEATHER_STATION:
+                    /* <location> with no parent <city> */
+                    /* Translators: this is the name of a location followed by a region, for example:
+                     * 'London, United Kingdom'
+                     * You shouldn't need to translate this string unless the language has a different comma.
+                     */
+                    display_name = _("%s, %s").format(
+                        loc.get_name(), parent_display_name);
+                    local_compare_name = _("%s, %s").format(
+                        loc.get_sort_name(), parent_compare_local_name);
+                    english_compare_name = _("%s, %s").format(
+                        loc.get_english_name(), parent_compare_english_name);
+
+                    this._list.push(buildLocationItem(display_name, loc, local_compare_name, 
english_compare_name));
+                    break;
+                case GWeather.LocationLevel.NAMED_TIMEZONE:
+                    if (show_named_timezones) {
+                        this._list.push(buildLocationItem(loc.get_name(), loc, loc.get_sort_name(), 
loc.get_english_sort_name()));
+                    }
+                    break;
+
+                case GWeather.LocationLevel.DETACHED:
+                    throw new Error('No detached locations!');
+            }
+        }
+
+        /**
+         * @this {ListModel & this}
+         */
+        fill() {
+            if (!this._top) {
+                throw new Error('Failed to load location data');
+            }
+
+            this._fill_location_list_model(this._top, null, null, null, this._show_named_timezones);
+            this.items_changed(0, 0, this._list.length);
+        }
+
+        vfunc_get_item_type() {
+            return LocationItem.$gtype;
+        }
+
+        vfunc_get_n_items() {
+            return this._list.length;
+        }
+
+        /**
+         * @param {number} n 
+         */
+        vfunc_get_item(n) {
+            return this._list[n] ?? null;
+        }
+    }
+);
+
+var LocationSearchEntry = GObject.registerClass(
+    {
+        Properties: {
+            'location': GObject.ParamSpec.boxed("location", "location", "location", 
GObject.ParamFlags.READWRITE, GWeather.Location.$gtype)
+        }
+    },
+    class LocationSearchEntry extends Gtk.Widget {
+        constructor() {
+            super();
+
+            /** @type {import("@gi-types/gtk").SearchEntry} */
+            this._entry;
+        }
+
+        set location(loc) {
+            this._location = loc;
+        }
+
+        get location() {
+            return this._location;
+        }
+
+        /**
+         * @param {import("@gi-types/gtk").Orientation} orientation
+         * @param {number} for_size
+         */
+        vfunc_measure(orientation, for_size) {
+            return this._entry.measure(orientation, for_size);
+        }
+
+        /**
+         * @param {number} width
+         * @param {number} height
+         * @param {number} baseline
+         */
+        vfunc_size_allocate(width, height, baseline) {
+            this._entry.size_allocate(new Gdk.Rectangle({
+                x: 0,
+                y: 0,
+                width,
+                height
+            }), baseline);
+
+            if (this._popup) {
+                this._popup.set_size_request(this.get_allocation().width, -1);
+                this._popup.queue_resize();
+                this._popup.present();
+            }
+        }
+
+        vfunc_grab_focus() {
+            return this._entry.grab_focus();
+        }
+
+        /**
+         * @param {import("@gi-types/gtk").ListView} listview 
+         */
+        set_list_view(listview) {
+            if (this._listview) {
+                this._listview.set_factory(null);
+                this._listview.set_model(null);
+                this._listview.unparent();
+            }
+
+            this._listview = listview;
+            const factory = this._build_factory();
+            listview.set_factory(factory);
+            const selection = this._build_model();
+            listview.set_model(selection);
+        }
+
+        onSearch(cb) {
+            this.cb = cb;
+        }
+
+        ensure_list_view() {
+            if (!this._listview) {
+                this._popup = new Gtk.Popover();
+                this._popup.set_parent(this);
+                this._popup.set_autohide(false);
+                this._popup.set_has_arrow(false);
+
+                this._listview = new Gtk.ListView();
+                const selection = this._build_model();
+                this._listview.set_model(selection);
+                const factory = this._build_factory();
+                this._listview.set_factory(factory);
+                this._sw = new Gtk.ScrolledWindow();
+                this._sw.set_child(this._listview);
+                this._sw.set_hexpand(true);
+                this._popup.set_child(this._sw);
+                return this._listview;
+            }
+
+            return this._listview;
+        }
+
+        _build_model() {
+            let filter = new Gtk.StringFilter();
+            this._filter = filter;
+            const expr = Gtk.ClosureExpression.new(GObject.TYPE_STRING, (self) => {
+                return self.displayName ?? "";
+            }, []);
+            filter.set_expression(expr);
+
+            let filter_model = new Gtk.FilterListModel({
+                model: this._model,
+                filter: this._filter
+            });
+            let selection = new Gtk.SingleSelection({
+                model: filter_model
+            });
+            selection.set_selected(GLib.MAXUINT32)
+            selection.set_autoselect(false);
+            selection.connect('notify::selected', (selection, position) => {
+                const model = selection.get_model();
+                const item = model.get_item(position);
+                if (item instanceof LocationItem) {
+                    this._set_location(item.location);
+
+                    if (this._popup) {
+                        this._popup.popdown();
+                    }
+                }
+            });
+            return selection;
+        }
+
+        _build_factory() {
+            let factory = new Gtk.SignalListItemFactory();
+            this._setupId = factory.connect("setup", (source, item) => {
+                const label = new Gtk.Label();
+                item.set_child(label);
+            });
+            this._bindId = factory.connect("bind", (source, listitem) => {
+                const label = listitem.get_child();
+                /** @type {typeof LocationItem.prototype} */
+                const item = listitem.get_item();
+
+                if (label instanceof Gtk.Label) {
+                    label.set_label(item.display_name);
+                }
+            });
+
+            return factory;
+        }
+
+        _init() {
+            super._init();
+
+            this._model = new LocationListModel();
+            this._location = null;
+
+            // Widgets
+            this._entry = new Gtk.SearchEntry();
+
+            this._entry.set_parent(this);
+            this._entry.set_hexpand(true);
+            this._popup = null;
+
+
+            this._entry.connect("search-changed", (source) => {
+                this.ensure_list_view();
+
+                const text = source.get_text();
+
+                if (text === null || text === '') {
+                    this._filter?.set_search(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();
+                }
+
+                this._filter?.set_search(text);
+                this.cb?.(text);
+            });
+
+            this._model.fill();
+        }
+
+        _cleanup() {
+            if (this._listview instanceof Gtk.ListView) {
+                this._listview.set_model(null);
+            }
+        }
+
+        get_location() {
+            if (this._location)
+                return this._location;
+            else
+                return null;
+        }
+
+        _set_location(location) {
+            this._location = location;
+            this.notify('location');
+        }
+    }
+);
+
diff --git a/src/app/main.js b/src/app/main.js
index da03caf..83c23ba 100644
--- a/src/app/main.js
+++ b/src/app/main.js
@@ -20,13 +20,15 @@ pkg.initFormat();
 pkg.initGettext();
 window.ngettext = imports.gettext.ngettext;
 
-pkg.require({ 'Gdk': '4.0',
-              'Gio': '2.0',
-              'GLib': '2.0',
-              'GObject': '2.0',
-              'Gtk': '4.0',
-              'Handy': '4',
-              'GWeather': '4.0' });
+pkg.require({
+    'Gdk': '4.0',
+    'Gio': '2.0',
+    'GLib': '2.0',
+    'GObject': '2.0',
+    'Gtk': '4.0',
+    'Handy': '4',
+    'GWeather': '4.0'
+});
 
 const ByteArray = imports.byteArray;
 const Handy = imports.gi.Handy;
@@ -42,6 +44,9 @@ const Window = imports.app.window;
 const World = imports.shared.world;
 const CurrentLocationController = imports.app.currentLocationController;
 
+// ensure the type before we call to GtkBuilder
+imports.app.entry;
+
 const ShellIntegrationInterface = ByteArray.toString(
     Gio.resources_lookup_data('/org/gnome/shell/ShellWeatherIntegration.xml', 0).get_data());
 
@@ -55,9 +60,10 @@ const Application = GObject.registerClass(
     class WeatherApplication extends Gtk.Application {
 
     _init() {
-        super._init({ application_id: pkg.name,
-                      flags: (Gio.ApplicationFlags.CAN_OVERRIDE_APP_ID |  Gio.ApplicationFlags.FLAGS_NONE) 
});
-
+        super._init({
+            application_id: pkg.name,
+            flags: (Gio.ApplicationFlags.CAN_OVERRIDE_APP_ID | Gio.ApplicationFlags.FLAGS_NONE)
+        });
         let name_prefix = '';
         if (pkg.name.endsWith('Devel')) {
             name_prefix = '(Development) ';
@@ -90,8 +96,6 @@ const Application = GObject.registerClass(
     vfunc_startup() {
         super.vfunc_startup();
         Handy.init();
-        // ensure the type before we call to GtkBuilder
-        Gtk.Entry;
 
         Util.loadStyleSheet('/org/gnome/Weather/application.css');
 
@@ -266,5 +270,13 @@ let ShellIntegration = class ShellIntegration {
 function main(argv) {
     initEnvironment();
 
-    return (new Application()).run(argv);
+    const application = new Application();
+
+    application.connect("window-removed", (_, window) => {
+        if (window instanceof Window.MainWindow) {
+            window._cleanup();
+        }
+    });
+
+    application.run(argv);
 }
diff --git a/src/app/window.js b/src/app/window.js
index a33cb4b..8ab2826 100644
--- a/src/app/window.js
+++ b/src/app/window.js
@@ -122,6 +122,11 @@ var MainWindow = GObject.registerClass(
         this._showingDefault = false;
     }
 
+    _cleanup() {
+        this._cityView._cleanup();
+        this._searchEntry._cleanup();
+    }
+
     update() {
         this._cityView.update();
     }
@@ -191,8 +196,9 @@ var MainWindow = GObject.registerClass(
                 if (!this._cityView.info._isCurrentLocation)
                     return;
             } else if (this._currentPage == Page.SEARCH) {
-                if (this._searchEntry.text.length > 0)
-                    return;
+                // TODO
+                // if (this._searchEntry.text.length > 0)
+                //    return;
             }
         }
 
diff --git a/src/app/world.js b/src/app/world.js
index 216a69a..7da0b43 100644
--- a/src/app/world.js
+++ b/src/app/world.js
@@ -42,7 +42,9 @@ var WorldContentView = GObject.registerClass(
         let grid = builder.get_object('popover-grid');
         this.set_child(grid);
 
-        this._searchGrid = builder.get_object('search-grid');
+        this._searchResults = builder.get_object('city-search-results');
+        this._searchGrid = builder.get_object('empty-search-grid');
+        this._searchResultsGrid = builder.get_object('search-grid');
         this._locationsGrid = builder.get_object('locations-grid');
 
         this.model = application.model;
@@ -60,11 +62,20 @@ var WorldContentView = GObject.registerClass(
             }
         });
 
-        let locationEntry = builder.get_object('location-entry');
-        locationEntry.connect('notify::location', (entry) => this._locationChanged(entry));
+        this._locationEntry = builder.get_object('location-entry');
+        this._locationEntry.set_list_view(this._searchResults);
+        // TODO: Signal-ify this
+        this._locationEntry.onSearch((term) => {
+            if (term) {
+                this._stackPopover.set_visible_child(this._searchResultsGrid);
+            } else {
+                this._syncStackPopover();
+            }
+        })
+        this._locationEntry.connect('notify::location', (entry) => this._locationChanged(entry));
 
         this.connect('show', () => {
-            locationEntry.grab_focus();
+            this._locationEntry.grab_focus();
         });
 
         let autoLocStack = builder.get_object('auto-location-stack');
@@ -121,6 +132,12 @@ var WorldContentView = GObject.registerClass(
             this._onLocationAdded(this.model, list[i], list[i]._isCurrentLocation);
     }
 
+    _cleanup() {
+        this._locationEntry._cleanup();
+
+        this._listbox.set_header_func(null);
+    }
+
     refilter() {
         this._listbox.invalidate_filter();
     }
diff --git a/src/org.gnome.Weather.src.gresource.xml.in b/src/org.gnome.Weather.src.gresource.xml.in
index 17b7432..7d547c7 100644
--- a/src/org.gnome.Weather.src.gresource.xml.in
+++ b/src/org.gnome.Weather.src.gresource.xml.in
@@ -5,6 +5,7 @@
     <file>app/currentLocationController.js</file>
     <file>app/hourlyForecast.js</file>
     <file>app/dailyForecast.js</file>
+    <file>app/entry.js</file>
     <file>app/main.js</file>
     <file>app/window.js</file>
     <file>app/world.js</file>


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