[gnome-weather/wip/ewlsh/gtk4] WIP: Porting to GTK4




commit 1b8391dbeae7113f7c617fde5cbcbc98a2dd0a91
Author: Evan Welsh <contact evanwelsh com>
Date:   Fri Dec 31 12:01:32 2021 -0800

    WIP: Porting to GTK4

 .editorconfig                              |   6 +
 data/application.css                       |   4 -
 data/city.ui                               |  59 ++--
 data/day-entry.ui                          | 315 ++++++++------------
 data/hour-entry.ui                         |  32 +--
 data/org.gnome.Weather.data.gresource.xml  |   1 -
 data/places-popover.ui                     | 269 ++++++++---------
 data/primary-menu.ui                       |   2 +
 data/weather-widget.ui                     | 447 ++++++++++++-----------------
 data/window.ui                             | 134 ++++-----
 meson.build                                |   8 +-
 org.gnome.Weather.json                     |  21 +-
 src/app/city.js                            | 140 +++++----
 src/app/dailyForecast.js                   |  74 +++--
 src/app/entry.js                           | 360 +++++++++++++++++++++++
 src/app/hourlyForecast.js                  |  43 +--
 src/app/main.js                            |  56 ++--
 src/app/thermometer.js                     | 129 +++++----
 src/app/window.js                          |  72 ++---
 src/app/world.js                           | 140 +++++----
 src/misc/util.js                           |  14 +-
 src/org.gnome.Weather.src.gresource.xml.in |   1 +
 src/service/main.js                        |   2 +-
 23 files changed, 1285 insertions(+), 1044 deletions(-)
---
diff --git a/.editorconfig b/.editorconfig
new file mode 100644
index 0000000..8400d6f
--- /dev/null
+++ b/.editorconfig
@@ -0,0 +1,6 @@
+[*]
+indent_style = space
+indent_size = 4
+
+[javascript]
+quote_style = single
\ No newline at end of file
diff --git a/data/application.css b/data/application.css
index da39c9e..12dde99 100644
--- a/data/application.css
+++ b/data/application.css
@@ -83,7 +83,6 @@ viewswitchertitle viewswitcher {
 
 button.osd.circular {
   border-radius: 9999px;
-  -gtk-outline-radius: 9999px;
   min-width: 24px;
   min-height: 24px;
 }
@@ -92,9 +91,6 @@ button.osd.circular > image {
   padding: 12px;
 }
 
-.day-popover {
-  background-color: @theme_base_color;
-}
 
 .small-label {
   font-size: 9pt;
diff --git a/data/city.ui b/data/city.ui
index 8d6c66b..5bc7e55 100644
--- a/data/city.ui
+++ b/data/city.ui
@@ -1,44 +1,33 @@
 <?xml version="1.0" encoding="UTF-8"?>
-<!-- Generated with glade 3.16.1 -->
 <interface>
-  <requires lib="gtk+" version="3.0"/>
-  <template class="Gjs_WeatherView" parent="GtkStack">
-    <property name="transition_type">crossfade</property>
-    <child internal-child="accessible">
-      <object class="AtkObject" id="weather-view-accessible">
-        <property name="accessible-name" translatable="yes">City view</property>
-      </object>
-    </child>
+  <requires lib="gtk" version="4.0"/>
+  <template class="Gjs_WeatherView">
     <child>
-      <object class="GtkBox" id="loading-box">
-        <property name="visible">True</property>
-        <property name="can_focus">False</property>
-        <property name="valign">center</property>
-        <property name="orientation">vertical</property>
-        <child>
-          <object class="GtkSpinner" id="spinner">
-            <property name="visible">True</property>
-            <property name="can_focus">False</property>
-            <property name="height_request">128</property>
-            <property name="width_request">128</property>
-            <property name="margin_bottom">18</property>
-            <property name="active">True</property>
-          </object>
-        </child>
+      <object class="GtkStack" id="stack">
         <child>
-          <object class="GtkLabel">
-            <property name="visible">True</property>
-            <property name="can_focus">True</property>
-            <property name="label" translatable="yes">Loading…</property>
-            <style>
-              <class name="large-title"/>
-            </style>
+          <object class="GtkStackPage">
+            <property name="name">loading</property>
+            <property name="child">
+              <object class="GtkGrid" id="loading-grid">
+                <property name="orientation">vertical</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>
+                  </object>
+                </child>
+                <child>
+                  <object class="GtkLabel">
+                    <property name="label" translatable="yes">Loading…</property>
+                  </object>
+                </child>
+              </object>
+            </property>
           </object>
         </child>
       </object>
-      <packing>
-        <property name="name">loading</property>
-      </packing>
     </child>
   </template>
-</interface>
+</interface>
\ No newline at end of file
diff --git a/data/day-entry.ui b/data/day-entry.ui
index d6648be..cb8f905 100644
--- a/data/day-entry.ui
+++ b/data/day-entry.ui
@@ -1,356 +1,279 @@
 <?xml version="1.0" encoding="UTF-8"?>
-<!-- Generated with glade 3.22.0 -->
 <interface>
-  <requires lib="gtk+" version="3.20"/>
-  <object class="GtkPopoverMenu" id="more_menu">
-    <property name="can_focus">False</property>
+  <requires lib="gtk" version="4.0"/>
+  <object class="GtkPopover" id="more_menu">
     <style>
       <class name="day-popover"/>
     </style>
-    <child>
+    <property name="child">
       <object class="GtkGrid">
-        <property name="visible">True</property>
-        <property name="can_focus">False</property>
         <property name="row_spacing">8</property>
         <property name="column_spacing">16</property>
-        <property name="row_homogeneous">True</property>
+
         <property name="margin_top">16</property>
         <property name="margin_bottom">16</property>
         <property name="margin_start">16</property>
         <property name="margin_end">16</property>
         <child>
           <object class="GtkLabel">
-            <property name="visible">True</property>
-            <property name="can_focus">False</property>
             <property name="halign">end</property>
             <property name="label" translatable="yes">Night</property>
+            <layout>
+              <property name="column">0</property>
+              <property name="row">1</property>
+            </layout>
             <style>
               <class name="small-label"/>
             </style>
           </object>
-          <packing>
-            <property name="left_attach">0</property>
-            <property name="top_attach">1</property>
-          </packing>
         </child>
         <child>
           <object class="GtkLabel">
-            <property name="visible">True</property>
-            <property name="can_focus">False</property>
             <property name="halign">end</property>
             <property name="label" translatable="yes">Morning</property>
+            <layout>
+              <property name="column">0</property>
+              <property name="row">2</property>
+            </layout>
             <style>
               <class name="small-label"/>
             </style>
           </object>
-          <packing>
-            <property name="left_attach">0</property>
-            <property name="top_attach">2</property>
-          </packing>
         </child>
         <child>
           <object class="GtkLabel">
-            <property name="visible">True</property>
-            <property name="can_focus">False</property>
             <property name="halign">end</property>
             <property name="label" translatable="yes">Afternoon</property>
+            <layout>
+              <property name="column">0</property>
+              <property name="row">3</property>
+            </layout>
             <style>
               <class name="small-label"/>
             </style>
           </object>
-          <packing>
-            <property name="left_attach">0</property>
-            <property name="top_attach">3</property>
-          </packing>
         </child>
         <child>
           <object class="GtkLabel">
-            <property name="visible">True</property>
-            <property name="can_focus">False</property>
             <property name="halign">end</property>
             <property name="label" translatable="yes">Evening</property>
+            <layout>
+              <property name="column">0</property>
+              <property name="row">4</property>
+            </layout>
             <style>
               <class name="small-label"/>
             </style>
           </object>
-          <packing>
-            <property name="left_attach">0</property>
-            <property name="top_attach">4</property>
-          </packing>
         </child>
         <child>
           <object class="GtkImage" id="nightImage">
-            <property name="visible">True</property>
-            <property name="can_focus">False</property>
             <property name="icon_name">weather-showers-symbolic</property>
+            <layout>
+              <property name="column">1</property>
+              <property name="row">1</property>
+            </layout>
           </object>
-          <packing>
-            <property name="left_attach">1</property>
-            <property name="top_attach">1</property>
-          </packing>
         </child>
         <child>
           <object class="GtkImage" id="morningImage">
-            <property name="visible">True</property>
-            <property name="can_focus">False</property>
             <property name="icon_name">weather-showers-symbolic</property>
+            <layout>
+              <property name="column">1</property>
+              <property name="row">2</property>
+            </layout>
           </object>
-          <packing>
-            <property name="left_attach">1</property>
-            <property name="top_attach">2</property>
-          </packing>
         </child>
         <child>
           <object class="GtkImage" id="afternoonImage">
-            <property name="visible">True</property>
-            <property name="can_focus">False</property>
             <property name="icon_name">weather-showers-symbolic</property>
+            <layout>
+              <property name="column">1</property>
+              <property name="row">3</property>
+            </layout>
           </object>
-          <packing>
-            <property name="left_attach">1</property>
-            <property name="top_attach">3</property>
-          </packing>
         </child>
         <child>
           <object class="GtkImage" id="eveningImage">
-            <property name="visible">True</property>
-            <property name="can_focus">False</property>
             <property name="icon_name">weather-showers-symbolic</property>
+            <layout>
+              <property name="column">1</property>
+              <property name="row">4</property>
+            </layout>
           </object>
-          <packing>
-            <property name="left_attach">1</property>
-            <property name="top_attach">4</property>
-          </packing>
         </child>
         <child>
           <object class="GtkGrid">
-            <property name="visible">True</property>
-            <property name="can_focus">False</property>
             <property name="vexpand">True</property>
             <property name="row_spacing">8</property>
-            <property name="row_homogeneous">True</property>
-            <property name="column_homogeneous">True</property>
+            <property name="row_homogeneous">1</property>
+            <property name="column_homogeneous">1</property>
             <child>
               <object class="GtkLabel" id="nightTemperatureLabel">
-                <property name="visible">True</property>
-                <property name="can_focus">False</property>
                 <property name="label">16°</property>
+                <layout>
+                  <property name="column">0</property>
+                  <property name="row">1</property>
+                </layout>
               </object>
-              <packing>
-                <property name="left_attach">0</property>
-                <property name="top_attach">1</property>
-              </packing>
             </child>
             <child>
               <object class="GtkLabel" id="morningTemperatureLabel">
-                <property name="visible">True</property>
-                <property name="can_focus">False</property>
                 <property name="label">16°</property>
+                <layout>
+                  <property name="column">0</property>
+                  <property name="row">2</property>
+                </layout>
               </object>
-              <packing>
-                <property name="left_attach">0</property>
-                <property name="top_attach">2</property>
-              </packing>
             </child>
             <child>
               <object class="GtkLabel" id="afternoonTemperatureLabel">
-                <property name="visible">True</property>
-                <property name="can_focus">False</property>
                 <property name="label">16°</property>
+                <layout>
+                  <property name="column">0</property>
+                  <property name="row">3</property>
+                </layout>
               </object>
-              <packing>
-                <property name="left_attach">0</property>
-                <property name="top_attach">3</property>
-              </packing>
             </child>
             <child>
               <object class="GtkLabel" id="eveningTemperatureLabel">
-                <property name="visible">True</property>
-                <property name="can_focus">False</property>
                 <property name="label">16°</property>
+                <layout>
+                  <property name="column">0</property>
+                  <property name="row">4</property>
+                </layout>
               </object>
-              <packing>
-                <property name="left_attach">0</property>
-                <property name="top_attach">4</property>
-              </packing>
             </child>
             <child>
               <object class="GtkImage">
-                <property name="visible">True</property>
-                <property name="can_focus">False</property>
                 <property name="icon_name">temperature-symbolic</property>
+                <layout>
+                  <property name="column">0</property>
+                  <property name="row">0</property>
+                </layout>
               </object>
-              <packing>
-                <property name="left_attach">0</property>
-                <property name="top_attach">0</property>
-              </packing>
             </child>
             <child>
               <object class="GtkImage">
-                <property name="visible">True</property>
-                <property name="can_focus">False</property>
                 <property name="icon_name">weather-showers-scattered-symbolic</property>
+                <layout>
+                  <property name="column">1</property>
+                  <property name="row">0</property>
+                </layout>
               </object>
-              <packing>
-                <property name="left_attach">1</property>
-                <property name="top_attach">0</property>
-              </packing>
             </child>
             <child>
               <object class="GtkImage">
-                <property name="visible">True</property>
-                <property name="can_focus">False</property>
                 <property name="icon_name">weather-windy</property>
+                <layout>
+                  <property name="column">2</property>
+                  <property name="row">0</property>
+                </layout>
               </object>
-              <packing>
-                <property name="left_attach">2</property>
-                <property name="top_attach">0</property>
-              </packing>
             </child>
             <child>
               <object class="GtkLabel" id="nightHumidity">
-                <property name="visible">True</property>
-                <property name="can_focus">False</property>
                 <property name="label">2.1 mm</property>
+                <layout>
+                  <property name="column">1</property>
+                  <property name="row">1</property>
+                </layout>
               </object>
-              <packing>
-                <property name="left_attach">1</property>
-                <property name="top_attach">1</property>
-              </packing>
             </child>
             <child>
               <object class="GtkLabel" id="morningHumidity">
-                <property name="visible">True</property>
-                <property name="can_focus">False</property>
                 <property name="label">2.1 mm</property>
+                <layout>
+                  <property name="column">1</property>
+                  <property name="row">2</property>
+                </layout>
               </object>
-              <packing>
-                <property name="left_attach">1</property>
-                <property name="top_attach">2</property>
-              </packing>
             </child>
             <child>
               <object class="GtkLabel" id="afternoonHumidity">
-                <property name="visible">True</property>
-                <property name="can_focus">False</property>
                 <property name="label">2.1 mm</property>
+                <layout>
+                  <property name="column">1</property>
+                  <property name="row">3</property>
+                </layout>
               </object>
-              <packing>
-                <property name="left_attach">1</property>
-                <property name="top_attach">3</property>
-              </packing>
             </child>
             <child>
               <object class="GtkLabel" id="eveningHumidity">
-                <property name="visible">True</property>
-                <property name="can_focus">False</property>
                 <property name="label">2.1 mm</property>
+                <layout>
+                  <property name="column">1</property>
+                  <property name="row">4</property>
+                </layout>
               </object>
-              <packing>
-                <property name="left_attach">1</property>
-                <property name="top_attach">4</property>
-              </packing>
             </child>
             <child>
               <object class="GtkLabel" id="nightWind">
-                <property name="visible">True</property>
-                <property name="can_focus">False</property>
                 <property name="label">5 m/s</property>
+                <layout>
+                  <property name="column">2</property>
+                  <property name="row">1</property>
+                </layout>
               </object>
-              <packing>
-                <property name="left_attach">2</property>
-                <property name="top_attach">1</property>
-              </packing>
             </child>
             <child>
               <object class="GtkLabel" id="morningWind">
-                <property name="visible">True</property>
-                <property name="can_focus">False</property>
                 <property name="label">5 m/s</property>
+                <layout>
+                  <property name="column">2</property>
+                  <property name="row">2</property>
+                </layout>
               </object>
-              <packing>
-                <property name="left_attach">2</property>
-                <property name="top_attach">2</property>
-              </packing>
             </child>
             <child>
               <object class="GtkLabel" id="afternoonWind">
-                <property name="visible">True</property>
-                <property name="can_focus">False</property>
                 <property name="label">5 m/s</property>
+                <layout>
+                  <property name="column">2</property>
+                  <property name="row">3</property>
+                </layout>
               </object>
-              <packing>
-                <property name="left_attach">2</property>
-                <property name="top_attach">3</property>
-              </packing>
             </child>
             <child>
               <object class="GtkLabel" id="eveningWind">
-                <property name="visible">True</property>
-                <property name="can_focus">False</property>
                 <property name="label">5 m/s</property>
+                <layout>
+                  <property name="column">2</property>
+                  <property name="row">4</property>
+                </layout>
               </object>
-              <packing>
-                <property name="left_attach">2</property>
-                <property name="top_attach">4</property>
-              </packing>
             </child>
+            <layout>
+              <property name="column">2</property>
+              <property name="row">0</property>
+              <property name="row-span">5</property>
+            </layout>
           </object>
-          <packing>
-            <property name="left_attach">2</property>
-            <property name="top_attach">0</property>
-            <property name="height">5</property>
-          </packing>
-        </child>
-        <child>
-          <placeholder/>
-        </child>
-        <child>
-          <placeholder/>
         </child>
       </object>
-    </child>
+    </property>
   </object>
-  <template class="Gjs_DayEntry" parent="GtkBox">
+  <template class="Gjs_DayEntry">
     <property name="width_request">100</property>
     <property name="height_request">200</property>
-    <property name="visible">True</property>
-    <property name="can_focus">False</property>
     <property name="margin_top">18</property>
     <property name="margin_bottom">18</property>
     <property name="hexpand">True</property>
     <property name="vexpand">True</property>
-    <property name="orientation">vertical</property>
-    <property name="spacing">18</property>
     <child>
-      <object class="GtkBox">
-        <property name="visible">True</property>
-        <property name="can_focus">False</property>
-        <property name="orientation">vertical</property>
-        <property name="spacing">6</property>
-        <child>
-          <object class="GtkLabel" id="nameLabel">
-            <property name="visible">True</property>
-            <property name="can_focus">False</property>
-            <property name="label">Tues</property>
-          </object>
-        </child>
-        <child>
-          <object class="GtkLabel" id="dateLabel">
-            <property name="visible">True</property>
-            <property name="can_focus">False</property>
-            <property name="label">7 June</property>
-            <style>
-              <class name="small-label"/>
-            </style>
-          </object>
-        </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="visible">True</property>
-        <property name="can_focus">False</property>
         <property name="valign">start</property>
         <property name="pixel_size">32</property>
         <property name="icon_name">weather-showers-symbolic</property>
@@ -358,24 +281,19 @@
     </child>
     <child>
       <object class="Gjs_Thermometer" id="thermometer">
-        <property name="visible">True</property>
-        <property name="can_focus">False</property>
+
         <property name="vexpand">True</property>
         <property name="hexpand">True</property>
       </object>
     </child>
     <child>
       <object class="GtkMenuButton">
-        <property name="visible">True</property>
-        <property name="can_focus">True</property>
-        <property name="receives_default">False</property>
         <property name="halign">center</property>
+        <property name="icon_name">view-more-symbolic</property>
         <property name="valign">center</property>
         <property name="popover">more_menu</property>
         <child>
           <object class="GtkImage">
-            <property name="visible">True</property>
-            <property name="can_focus">False</property>
             <property name="icon_name">view-more-symbolic</property>
           </object>
         </child>
@@ -386,5 +304,6 @@
         </style>
       </object>
     </child>
+
   </template>
 </interface>
diff --git a/data/hour-entry.ui b/data/hour-entry.ui
index 0c27049..be5d9d3 100644
--- a/data/hour-entry.ui
+++ b/data/hour-entry.ui
@@ -1,40 +1,36 @@
 <?xml version="1.0" encoding="UTF-8"?>
-<!-- Generated with glade 3.22.0 -->
 <interface>
-  <requires lib="gtk+" version="3.20"/>
-  <template class="Gjs_HourEntry" parent="GtkBox">
+  <requires lib="gtk" version="4.0"/>
+  <template class="Gjs_HourEntry">
     <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>
-    <property name="spacing">18</property>
+    <property name="hexpand">1</property>
+    <property name="vexpand">1</property>
+
     <property name="margin_top">18</property>
     <property name="margin_bottom">18</property>
+    <layout>
+        <property name="orientation">vertical</property>
+    <property name="spacing">18</property>
+    </layout>
     <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="valign">1</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="temperatureLabel">
-        <property name="visible">True</property>
-        <property name="can_focus">False</property>
-        <property name="valign">end</property>
+
+        <property name="valign">2</property>
         <property name="label">13°</property>
         <style>
           <class name="forecast-temperature-label"/>
diff --git a/data/org.gnome.Weather.data.gresource.xml b/data/org.gnome.Weather.data.gresource.xml
index eb357d3..22f274b 100644
--- a/data/org.gnome.Weather.data.gresource.xml
+++ b/data/org.gnome.Weather.data.gresource.xml
@@ -3,7 +3,6 @@
   <gresource prefix="/org/gnome/Weather">
     <file preprocess="xml-stripblanks">city.ui</file>
     <file preprocess="xml-stripblanks">places-popover.ui</file>
-    <file preprocess="xml-stripblanks">primary-menu.ui</file>
     <file preprocess="xml-stripblanks">weather-widget.ui</file>
     <file preprocess="xml-stripblanks">window.ui</file>
     <file preprocess="xml-stripblanks">hour-entry.ui</file>
diff --git a/data/places-popover.ui b/data/places-popover.ui
index c96d16f..ea1e846 100644
--- a/data/places-popover.ui
+++ b/data/places-popover.ui
@@ -1,228 +1,201 @@
 <?xml version="1.0" encoding="UTF-8"?>
-<!-- Generated with glade 3.16.1 -->
 <interface>
-  <requires lib="gtk+" version="3.0"/>
+  <requires lib="gtk" version="4.0"/>
   <object class="GtkGrid" id="popover-grid">
-    <property name="visible">True</property>
-    <property name="can_focus">False</property>
     <property name="orientation">vertical</property>
     <property name="row_spacing">10</property>
-    <property name="margin">12</property>
-    <property name="vexpand">False</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="GWeatherLocationEntry" id="location-entry">
-        <property name="visible">True</property>
-        <property name="can_focus">True</property>
+      <object class="Gjs_LocationSearchEntry" id="location-entry">
+        <property name="focusable">1</property>
         <property name="width-request">300</property>
-        <property name="activates_default">True</property>
+        <layout>
+          <property name="column">0</property>
+          <property name="row">0</property>
+        </layout>
       </object>
-      <packing>
-        <property name="left_attach">0</property>
-        <property name="top_attach">0</property>
-        <property name="width">1</property>
-        <property name="height">1</property>
-      </packing>
     </child>
     <child>
       <object class="GtkStack" id="auto-location-stack">
-        <property name="visible">True</property>
-        <property name="can-focus">False</property>
-        <property name="vexpand">False</property>
-        <property name="homogeneous">False</property>
+        <property name="vhomogeneous">0</property>
+        <property name="hhomogeneous">0</property>
         <property name="transition_type">crossfade</property>
         <child>
-          <object class="GtkGrid" id="auto-location-grid">
-            <property name="visible">True</property>
-            <property name="can_focus">False</property>
-            <property name="orientation">horizontal</property>
-            <property name="column_homogeneous">True</property>
-            <property name="margin_top">6</property>
-            <property name="margin_bottom">6</property>
-            <property name="vexpand">False</property>
-            <child>
-              <object class="GtkLabel" id="auto-location-label">
-                <property name="visible">True</property>
-                <property name="can-focus">False</property>
-                <property name="label" translatable="yes">Automatic Location</property>
-                <property name="halign">start</property>
-                <property name="vexpand">False</property>
-                <attributes>
-                  <attribute name="weight" value="PANGO_WEIGHT_BOLD"/>
-                </attributes>
-                <style>
-                  <class name="dim-label"/>
-                </style>
-              </object>
-              <packing>
-                <property name="left_attach">0</property>
-                <property name="top_attach">0</property>
-                <property name="width">1</property>
-                <property name="height">1</property>
-              </packing>
-            </child>
-            <child>
-              <object class="GtkSwitch" id="auto-location-switch">
-                <property name="visible">True</property>
-                <property name="halign">end</property>
-                <property name="vexpand">False</property>
+          <object class="GtkStackPage">
+            <property name="name">auto-location-switch-grid</property>
+            <property name="child">
+              <object class="GtkGrid" id="auto-location-grid">
+                <property name="column_homogeneous">1</property>
+                <property name="margin_top">6</property>
+                <property name="margin_bottom">6</property>
+                <child>
+                  <object class="GtkLabel" id="auto-location-label">
+                    <property name="label" translatable="yes">Automatic Location</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="GtkSwitch" id="auto-location-switch">
+                    <property name="halign">end</property>
+                    <layout>
+                      <property name="column">1</property>
+                      <property name="row">0</property>
+                    </layout>
+                  </object>
+                </child>
               </object>
-              <packing>
-                <property name="left_attach">1</property>
-                <property name="top_attach">0</property>
-                <property name="width">1</property>
-                <property name="height">1</property>
-              </packing>
-            </child>
+            </property>
           </object>
-          <packing>
-            <property name="name">auto-location-switch-grid</property>
-          </packing>
         </child>
         <child>
-          <object class="GtkLabel" id="locating-label">
-            <property name="visible">True</property>
-            <property name="can-focus">False</property>
-            <property name="label" translatable="yes">Locating…</property>
-            <property name="halign">center</property>
-            <property name="valign">center</property>
-            <property name="vexpand">False</property>
-          </object>
-          <packing>
+          <object class="GtkStackPage">
             <property name="name">locating-label</property>
-          </packing>
+            <property name="child">
+              <object class="GtkLabel" id="locating-label">
+                <property name="label" translatable="yes">Locating…</property>
+                <property name="halign">center</property>
+                <property name="valign">center</property>
+              </object>
+            </property>
+          </object>
         </child>
-        </object>
-        <packing>
-          <property name="left_attach">0</property>
-          <property name="top_attach">1</property>
-          <property name="width">1</property>
-          <property name="height">1</property>
-        </packing>
+        <layout>
+          <property name="column">0</property>
+          <property name="row">1</property>
+        </layout>
+      </object>
     </child>
     <child>
       <object class="GtkStack" id="popover-stack">
-        <property name="visible">True</property>
-        <property name="can-focus">False</property>
-        <property name="vexpand">False</property>
-        <property name="homogeneous">False</property>
+        <property name="vhomogeneous">0</property>
+        <property name="hhomogeneous">0</property>
         <child>
-          <object class="GtkGrid" id="search-grid">
-            <property name="visible">True</property>
+          <object class="GtkGrid" id="empty-search-grid">
             <property name="name">search-city-grid</property>
-            <property name="can_focus">False</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">True</property>
-            <property name="vexpand">False</property>
+            <property name="row_homogeneous">1</property>
+
+
             <child>
               <object class="GtkImage" id="search-image">
-                <property name="visible">True</property>
-                <property name="can_focus">False</property>
                 <property name="icon_name">edit-find-symbolic</property>
-                <property name="icon_size">6</property>
-                <property name="use_fallback">True</property>
+                <property name="icon_size">2</property>
+                <property name="use_fallback">1</property>
                 <property name="halign">center</property>
                 <property name="valign">center</property>
-                <property name="vexpand">False</property>
+                <layout>
+                  <property name="column">0</property>
+                  <property name="row">0</property>
+                </layout>
               </object>
-              <packing>
-                <property name="left_attach">0</property>
-                <property name="top_attach">0</property>
-                <property name="width">1</property>
-                <property name="height">1</property>
-              </packing>
             </child>
             <child>
               <object class="GtkLabel" id="search-label">
-                <property name="visible">True</property>
-                <property name="can-focus">False</property>
                 <property name="label" translatable="yes">Search for a city</property>
                 <property name="halign">center</property>
                 <property name="valign">center</property>
-                <property name="vexpand">False</property>
                 <style>
                   <class name="dim-label"/>
                 </style>
+                <layout>
+                  <property name="column">0</property>
+                  <property name="row">1</property>
+                </layout>
               </object>
-              <packing>
-                <property name="left_attach">0</property>
-                <property name="top_attach">1</property>
-                <property name="width">1</property>
-                <property name="height">1</property>
-              </packing>
+
             </child>
+
           </object>
-          <packing>
+        </child>
+        <child>
+          <object class="GtkGrid" id="search-grid">
             <property name="name">search-grid</property>
-          </packing>
+            <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="visible">True</property>
             <property name="name">locations-grid</property>
-            <property name="can_focus">False</property>
             <property name="orientation">vertical</property>
             <property name="row_spacing">10</property>
-            <property name="vexpand">False</property>
             <child>
               <object class="GtkLabel" id="recently-viewed-label">
-                <property name="visible">True</property>
-                <property name="can-focus">False</property>
                 <property name="label" translatable="yes">Viewed Recently</property>
                 <property name="halign">start</property>
-                <property name="vexpand">False</property>
                 <attributes>
-                  <attribute name="weight" value="PANGO_WEIGHT_BOLD"/>
+                  <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>
-              <packing>
-                <property name="left_attach">0</property>
-                <property name="top_attach">0</property>
-                <property name="width">1</property>
-                <property name="height">1</property>
-              </packing>
             </child>
+
             <child>
               <object class="GtkFrame" id="locations-frame">
                 <property name="name">locations-frame</property>
-                <property name="visible">True</property>
-                <property name="can-focus">False</property>
-                <child>
+                <property name="child">
                   <object class="GtkListBox" id="locations-list-box">
                     <property name="name">locations-list-box</property>
-                    <property name="visible">True</property>
-                    <property name="can-focus">False</property>
-                    <property name="hexpand">True</property>
-                    <property name="vexpand">False</property>
+                    <property name="hexpand">1</property>
                     <property name="selection-mode">none</property>
                   </object>
-                </child>
+                </property>
+                <layout>
+                  <property name="column">0</property>
+                  <property name="row">1</property>
+                </layout>
               </object>
-              <packing>
-                <property name="left_attach">0</property>
-                <property name="top_attach">1</property>
-                <property name="width">1</property>
-                <property name="height">1</property>
-              </packing>
             </child>
+            <layout>
+              <property name="column">0</property>
+              <property name="row">1</property>
+            </layout>
           </object>
-          <packing>
-            <property name="name">locations-grid</property>
-          </packing>
         </child>
+
+        <layout>
+          <property name="column">0</property>
+          <property name="row">2</property>
+        </layout>
       </object>
-      <packing>
-        <property name="left-attach">0</property>
-        <property name="top-attach">2</property>
-        <property name="width">1</property>
-        <property name="height">1</property>
-      </packing>
     </child>
   </object>
 </interface>
diff --git a/data/primary-menu.ui b/data/primary-menu.ui
index c6d55f0..67f0b35 100644
--- a/data/primary-menu.ui
+++ b/data/primary-menu.ui
@@ -1,4 +1,6 @@
+<?xml version="1.0" encoding="UTF-8"?>
 <interface>
+  <requires lib="gtk" version="4.0"/>
   <menu id="primary-menu">
     <submenu>
       <attribute translatable="yes" name="label">_Temperature Unit</attribute>
diff --git a/data/weather-widget.ui b/data/weather-widget.ui
index a2d7753..2acc50f 100644
--- a/data/weather-widget.ui
+++ b/data/weather-widget.ui
@@ -1,307 +1,230 @@
 <?xml version="1.0" encoding="UTF-8"?>
-<!-- Generated with glade 3.22.0 -->
 <interface>
-  <requires lib="gtk+" version="3.0"/>
-  <template class="Gjs_WeatherWidget" parent="GtkFrame">
-    <property name="visible">True</property>
-    <property name="can_focus">False</property>
-    <property name="label_xalign">0</property>
+  <requires lib="gtk" version="4.0"/>
+  <template class="Gjs_WeatherWidget">
+
+
     <child>
-      <object class="GtkFrame" id="contentFrame">
-        <property name="name">weather-page-content-view</property>
-        <property name="visible">True</property>
-        <property name="can_focus">False</property>
-        <property name="label_xalign">0</property>
-        <property name="shadow_type">none</property>
+      <object class="AdwClamp" id="clamp">
+        <property name="maximum_size">1010</property>
+        <property name="tightening_threshold">600</property>
         <child>
-          <object class="HdyClamp">
-            <property name="visible">True</property>
-            <property name="can_focus">False</property>
-            <property name="maximum_size">1010</property>
-            <property name="tightening_threshold">600</property>
+          <object class="GtkBox" id="outerBox">
+            <property name="orientation">vertical</property>
+            <property name="margin-start">18</property>
+            <property name="margin-end">18</property>
+            <property name="margin-top">18</property>
+            <property name="margin-bottom">18</property>
+            <!-- <property name="spacing">18</property> -->
             <child>
-              <object class="GtkBox" id="outerBox">
-                <property name="visible">True</property>
-                <property name="can_focus">False</property>
-                <property name="orientation">vertical</property>
-                <property name="margin">18</property>
-                <property name="spacing">18</property>
+              <object class="GtkGrid">
+                <property name="name">conditions-grid</property>
+                <property name="column_spacing">10</property>
                 <child>
-                  <object class="GtkGrid" id="inner-grid">
-                    <property name="name">conditions-grid</property>
-                    <property name="visible">True</property>
-                    <property name="can_focus">False</property>
-                    <property name="column_spacing">10</property>
-                    <child>
-                      <object class="GtkImage" id="conditionsImage">
-                        <property name="name">conditions-image</property>
-                        <property name="visible">True</property>
-                        <property name="can_focus">False</property>
-                        <property name="halign">start</property>
-                        <property name="valign">center</property>
-                        <property name="pixel_size">84</property>
-                        <style>
-                          <class name="icon-dropshadow"/>
-                        </style>
-                      </object>
-                      <packing>
-                        <property name="left_attach">0</property>
-                        <property name="top_attach">0</property>
-                        <property name="height">2</property>
-                      </packing>
-                    </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>
+                    <style>
+                      <class name="icon-dropshadow"/>
+                    </style>
+                    <layout>
+                      <property name="column">0</property>
+                      <property name="row">0</property>
+                      <property name="row-span">2</property>
+                    </layout>
+                  </object>
+                </child>
+                <child>
+                  <object class="GtkMenuButton" id="placesButton">
+                    <property name="receives_default">1</property>
+                    <property name="halign">start</property>
+                    <property name="valign">start</property>
                     <child>
-                      <object class="GtkMenuButton" id="placesButton">
-                        <property name="visible">True</property>
-                        <property name="can_focus">True</property>
-                        <property name="focus_on_click">False</property>
-                        <property name="receives_default">True</property>
-                        <property name="halign">start</property>
-                        <property name="valign">start</property>
+                      <object class="GtkBox" id="placesBox">
+                        <property name="spacing">12</property>
                         <child>
-                          <object class="GtkBox" id="placesBox">
-                            <property name="visible">True</property>
-                            <property name="can_focus">False</property>
-                            <property name="spacing">12</property>
-                            <child>
-                              <object class="GtkLabel" id="placesLabel">
-                                <property name="name">places-label</property>
-                                <property name="wrap">True</property>
-                                <property name="wrap-mode">word-char</property>
-                                <property name="visible">True</property>
-                                <property name="can_focus">False</property>
-                                <property name="label" translatable="yes">Places</property>
-                              </object>
-                              <packing>
-                                <property name="expand">False</property>
-                                <property name="fill">True</property>
-                                <property name="position">0</property>
-                              </packing>
-                            </child>
-                            <child>
-                              <object class="GtkImage" id="placesImage">
-                                <property name="visible">True</property>
-                                <property name="can_focus">False</property>
-                                <property name="icon_name">pan-down-symbolic</property>
-                              </object>
-                              <packing>
-                                <property name="expand">False</property>
-                                <property name="fill">True</property>
-                                <property name="position">1</property>
-                              </packing>
-                            </child>
+                          <object class="GtkLabel" id="placesLabel">
+                            <property name="name">places-label</property>
+                            <property name="wrap">1</property>
+                            <property name="wrap-mode">word-char</property>
+                            <property name="label" translatable="yes">Places</property>
+                          </object>
+                        </child>
+                        <child>
+                          <object class="GtkImage">
+                            <property name="icon_name">pan-down-symbolic</property>
                           </object>
                         </child>
-                        <style>
-                          <class name="text-button"/>
-                          <class name="flat"/>
-                        </style>
                       </object>
-                      <packing>
-                        <property name="left_attach">1</property>
-                        <property name="top_attach">0</property>
-                      </packing>
                     </child>
+                    <style>
+                      <class name="text-button"/>
+                      <class name="flat"/>
+                    </style>
+                    <layout>
+                      <property name="column">1</property>
+                      <property name="row">0</property>
+                    </layout>
+                  </object>
+                </child>
+                <child>
+                  <object class="GtkBox" id="temperatureBox">
+                    <property name="halign">start</property>
+                    <property name="valign">start</property>
+                    <property name="spacing">8</property> -->
+                    <property name="baseline_position">bottom</property>
                     <child>
-                      <object class="GtkBox" id="temperatureBox">
-                        <property name="visible">True</property>
-                        <property name="can_focus">False</property>
+                      <object class="GtkLabel" id="temperatureLabel">
+                        <property name="name">temperature-label</property>
                         <property name="halign">start</property>
-                        <property name="valign">start</property>
-                        <property name="spacing">8</property>
-                        <property name="baseline_position">bottom</property>
-                        <child>
-                          <object class="GtkLabel" id="temperatureLabel">
-                            <property name="name">temperature-label</property>
-                            <property name="visible">True</property>
-                            <property name="can_focus">False</property>
-                            <property name="halign">start</property>
-                            <property name="valign">baseline</property>
-                          </object>
-                          <packing>
-                            <property name="expand">False</property>
-                            <property name="fill">True</property>
-                            <property name="position">0</property>
-                          </packing>
-                        </child>
-                        <child>
-                          <object class="GtkLabel" id="apparentLabel">
-                            <property name="name">apparent-label</property>
-                            <property name="visible">True</property>
-                            <property name="can_focus">False</property>
-                            <property name="halign">start</property>
-                            <property name="valign">baseline</property>
-                          </object>
-                          <packing>
-                            <property name="expand">False</property>
-                            <property name="fill">True</property>
-                            <property name="position">1</property>
-                          </packing>
-                        </child>
+                        <property name="valign">baseline</property>
                       </object>
-                      <packing>
-                        <property name="left_attach">1</property>
-                        <property name="top_attach">1</property>
-                      </packing>
                     </child>
-                    <child internal-child="accessible">
-                      <object class="AtkObject" id="inner-grid-atkobject">
-                        <property name="AtkObject::accessible-name" translatable="yes">Current 
conditions</property>
+                    <child>
+                      <object class="GtkLabel" id="apparentLabel">
+                        <property name="name">apparent-label</property>
+                        <property name="halign">start</property>
+                        <property name="valign">baseline</property>
                       </object>
                     </child>
+                    <layout>
+                      <property name="column">1</property>
+                      <property name="row">1</property>
+                    </layout>
                   </object>
                 </child>
-                <child>
-                  <object class="GtkOverlay" id="forecast-overlay">
-                    <property name="visible">True</property>
-                    <property name="can_focus">False</property>
-                    <child>
-                      <object class="GtkFrame" id="forecastFrame">
+              </object>
+            </child>
+            <child>
+              <object class="GtkOverlay">
+                <property name="child">
+                  <!-- <object class="GtkFrame" id="forecastFrame">
                         <property name="name">forecast-frame</property>
-                        <property name="visible">True</property>
-                        <property name="can_focus">False</property>
-                        <property name="shadow-type">GTK_SHADOW_IN</property>
-                        <child>
-                          <object class="GtkStack" id="forecastStack">
-                            <property name="visible">True</property>
-                            <property name="can_focus">False</property>
-                            <property name="transition_type">crossfade</property>
-                            <child>
-                              <object class="GtkScrolledWindow" id="forecast-hourly">
-                                <property name="visible">True</property>
-                                <property name="can_focus">True</property>
-                                <property name="vscrollbar_policy">never</property>
-                                <property name="min_content_width">308</property>
-                                <child>
-                                  <object class="GtkViewport" id="forecast-hourly-viewport">
-                                    <property name="visible">True</property>
-                                    <property name="can_focus">False</property>
-                                    <property name="hscroll_policy">natural</property>
-                                    <property name="vscroll_policy">natural</property>
-                                  </object>
-                                </child>
-                              </object>
-                              <packing>
-                                <property name="name">hourly</property>
-                                <property name="title" translatable="yes">Hourly</property>
-                                <property name="icon-name">preferences-system-time-symbolic</property>
-                              </packing>
-                            </child>
-                            <child>
-                              <object class="GtkScrolledWindow" id="forecast-daily">
-                                <property name="visible">True</property>
-                                <property name="can_focus">True</property>
-                                <property name="vscrollbar_policy">never</property>
-                                <property name="min_content_width">308</property>
-                                <child>
-                                  <object class="GtkViewport" id="forecast-daily-viewport">
-                                    <property name="visible">True</property>
-                                    <property name="can_focus">False</property>
-                                    <property name="hscroll_policy">natural</property>
-                                    <property name="vscroll_policy">natural</property>
-                                  </object>
-                                </child>
+                        <property name="child"> -->
+                  <object class="AdwViewStack" id="forecastStack">
+                    <!-- <property name="transition_type">crossfade</property> -->
+                    <child>
+                      <object class="AdwViewStackPage">
+                        <property name="name">hourly</property>
+                        <property name="title" translatable="yes">Hourly</property>
+                        <property name="icon-name">preferences-system-time-symbolic</property>
+                        <property name="child">
+                          <object class="GtkScrolledWindow" id="forecastHourly">
+                            <property name="focusable">1</property>
+                            <property name="vscrollbar_policy">never</property>
+                            <property name="min_content_width">308</property>
+                            <property name="child">
+                              <object class="GtkViewport" id="forecastHourlyViewport">
+                                <property name="hscroll_policy">natural</property>
+                                <property name="vscroll_policy">natural</property>
                               </object>
-                              <packing>
-                                <property name="name">daily</property>
-                                <property name="title" translatable="yes">Daily</property>
-                                <property name="icon-name">x-office-calendar-symbolic</property>
-                              </packing>
-                            </child>
-                          </object>
-                        </child>
-                      </object>
-                      <packing>
-                        <property name="index">-1</property>
-                      </packing>
-                    </child>
-                    <child type="overlay">
-                      <object class="GtkButton" id="rightButton">
-                        <property name="visible">True</property>
-                        <property name="can_focus">True</property>
-                        <property name="receives_default">True</property>
-                        <property name="halign">end</property>
-                        <property name="valign">center</property>
-                        <property name="margin_end">28</property>
-                        <child>
-                          <object class="GtkImage" id="right-image">
-                            <property name="visible">True</property>
-                            <property name="can_focus">False</property>
-                            <property name="icon_name">go-next-symbolic</property>
+                            </property>
                           </object>
-                        </child>
-                        <style>
-                          <class name="osd"/>
-                          <class name="circular"/>
-                        </style>
+                        </property>
                       </object>
                     </child>
-                    <child type="overlay">
-                      <object class="GtkButton" id="leftButton">
-                        <property name="visible">True</property>
-                        <property name="can_focus">True</property>
-                        <property name="receives_default">True</property>
-                        <property name="halign">start</property>
-                        <property name="valign">center</property>
-                        <property name="margin_start">28</property>
-                        <child>
-                          <object class="GtkImage" id="left-image">
-                            <property name="visible">True</property>
-                            <property name="can_focus">False</property>
-                            <property name="icon_name">go-previous-symbolic</property>
-                            <property name="icon_size">1</property>
+                    <child>
+                      <object class="AdwViewStackPage">
+                        <property name="name">daily</property>
+                        <property name="title" translatable="yes">Daily</property>
+                        <property name="icon-name">x-office-calendar-symbolic</property>
+                        <property name="child">
+                          <object class="GtkScrolledWindow" id="forecastDaily">
+                            <property name="focusable">1</property>
+                            <property name="vscrollbar_policy">never</property>
+                            <property name="min_content_width">308</property>
+                            <property name="child">
+                              <object class="GtkViewport" id="forecastDailyViewport">
+                                <property name="hscroll_policy">natural</property>
+                                <property name="vscroll_policy">natural</property>
+                              </object>
+                            </property>
                           </object>
-                        </child>
-                        <style>
-                          <class name="osd"/>
-                          <class name="circular"/>
-                        </style>
+                        </property>
                       </object>
-                      <packing>
-                        <property name="index">1</property>
-                      </packing>
                     </child>
                   </object>
-                </child>
-                <child>
-                  <object class="GtkGrid">
-                    <property name="visible">True</property>
-                    <property name="can_focus">False</property>
-                    <property name="row_spacing">8</property>
+                  <!-- </property>
+                      </object> -->
+                </property>
+                <child type="overlay">
+                  <object class="GtkButton" id="rightButton">
+                    <property name="focusable">1</property>
+                    <property name="receives_default">1</property>
+                    <property name="halign">end</property>
+                    <property name="valign">center</property>
+                    <property name="margin_end">28</property>
                     <child>
-                      <object class="GtkLabel" id="updatedTimeLabel">
-                        <property name="name">updated-time-label</property>
-                        <property name="visible">True</property>
-                        <property name="can_focus">False</property>
-                        <property name="halign">start</property>
+                      <object class="GtkImage" id="right-image">
+                        <property name="icon_name">go-next-symbolic</property>
                       </object>
-                      <packing>
-                        <property name="left_attach">0</property>
-                        <property name="top_attach">0</property>
-                      </packing>
                     </child>
+                    <style>
+                      <class name="osd"/>
+                      <class name="circular"/>
+                    </style>
+                  </object>
+                </child>
+                <child type="overlay">
+                  <object class="GtkButton" id="leftButton">
+                    <property name="focusable">1</property>
+                    <property name="receives_default">1</property>
+                    <property name="halign">start</property>
+                    <property name="valign">center</property>
+                    <property name="margin_start">28</property>
                     <child>
-                      <object class="GtkLabel" id="attributionLabel">
-                        <property name="name">attribution-label</property>
-                        <property name="visible">True</property>
-                        <property name="can_focus">False</property>
-                        <property name="use_markup">True</property>
-                        <property name="wrap">True</property>
-                        <property name="track_visited_links">False</property>
-                        <property name="xalign">0</property>
+                      <object class="GtkImage" id="left-image">
+                        <property name="icon_name">go-previous-symbolic</property>
+                        <property name="icon_size">1</property>
                       </object>
-                      <packing>
-                        <property name="left_attach">0</property>
-                        <property name="top_attach">1</property>
-                      </packing>
                     </child>
+                    <style>
+                      <class name="osd"/>
+                      <class name="circular"/>
+                    </style>
+                  </object>
+                </child>
+              </object>
+            </child>
+            <child>
+              <object class="GtkGrid">
+                <property name="row_spacing">8</property>
+                <child>
+                  <object class="GtkLabel" id="updatedTimeLabel">
+                    <property name="name">updated-time-label</property>
+                    <property name="halign">start</property>
+                    <layout>
+                      <property name="column">0</property>
+                      <property name="row">0</property>
+                    </layout>
+                  </object>
+                </child>
+                <child>
+                  <object class="GtkLabel" id="attributionLabel">
+                    <property name="name">attribution-label</property>
+                    <property name="use_markup">1</property>
+                    <property name="wrap">1</property>
+                    <!-- <property name="track_visited_links">False</property> -->
+                    <property name="xalign">0</property>
+                    <layout>
+                      <property name="column">0</property>
+                      <property name="row">1</property>
+                    </layout>
                   </object>
                 </child>
               </object>
             </child>
+            <layout>
+              <property name="column">0</property>
+              <property name="row">2</property>
+            </layout>
           </object>
         </child>
       </object>
     </child>
+
+
   </template>
 </interface>
diff --git a/data/window.ui b/data/window.ui
index b16a804..a46ec83 100644
--- a/data/window.ui
+++ b/data/window.ui
@@ -1,7 +1,6 @@
 <?xml version="1.0" encoding="UTF-8"?>
-<!-- Generated with glade 3.22.0 -->
 <interface>
-  <requires lib="gtk+" version="3.10"/>
+  <requires lib="gtk" version="4.0"/>
   <menu id="primary-menu">
     <submenu>
       <attribute translatable="yes" name="label">_Temperature Unit</attribute>
@@ -23,133 +22,118 @@
       </item>
     </section>
   </menu>
-  <template class="Gjs_MainWindow" parent="HdyApplicationWindow">
-    <property name="visible">True</property>
+  <template class="Gjs_MainWindow">
+
     <property name="default_width">760</property>
     <property name="default_height">520</property>
     <child>
       <object class="GtkBox">
-        <property name="visible">True</property>
         <property name="orientation">vertical</property>
         <child>
-          <object class="HdyHeaderBar" id="header">
-            <property name="visible">True</property>
-            <property name="can_focus">False</property>
-            <property name="vexpand">False</property>
-            <property name="show_close_button">True</property>
-            <property name="centering_policy">strict</property>
+          <object class="AdwHeaderBar" id="header">
+            <!-- <property name="show_close_button">True</property> -->
+            <!-- <property name="centering_policy">strict</property> -->
             <child>
               <object class="GtkRevealer" id="refreshRevealer">
-                <property name="visible">True</property>
                 <property name="transition_type">crossfade</property>
-                <child>
+                <property name="child">
                   <object class="GtkButton" id="refresh">
-                    <property name="visible">True</property>
-                    <property name="can_focus">True</property>
-                    <property name="receives_default">False</property>
+                    <property name="focusable">1</property>
                     <property name="valign">center</property>
                     <property name="tooltip-text" translatable="yes">Refresh</property>
                     <property name="action_name">win.refresh</property>
                     <child>
                       <object class="GtkImage" id="refresh-button-image">
-                        <property name="visible">True</property>
-                        <property name="can_focus">False</property>
                         <property name="icon_name">view-refresh-symbolic</property>
                       </object>
                     </child>
                   </object>
-                </child>
+                </property>
               </object>
             </child>
             <child type="title">
               <object class="GtkStack" id="titleStack">
-                <property name="visible">True</property>
                 <property name="visible-child-name" bind-source="stack" bind-property="visible-child-name" 
bind-flags="bidirectional|sync-create"/>
                 <property name="transition_type">crossfade</property>
                 <child>
-                  <object class="GtkLabel">
-                    <property name="visible">True</property>
-                    <property name="ellipsize">end</property>
-                    <property name="halign">center</property>
-                    <property name="wrap">False</property>
-                    <property name="single-line-mode">True</property>
-                    <property name="width-chars">5</property>
-                    <property name="label" translatable="yes">Select Location</property>
-                    <style>
-                      <class name="title"/>
-                    </style>
-                  </object>
-                  <packing>
+                  <object class="GtkStackPage">
                     <property name="name">search</property>
-                  </packing>
+                    <property name="child">
+                      <object class="GtkLabel">
+                        <property name="ellipsize">end</property>
+                        <property name="halign">center</property>
+                        <property name="single-line-mode">1</property>
+                        <property name="width-chars">5</property>
+                        <property name="label" translatable="yes">Select Location</property>
+                        <style>
+                          <class name="title"/>
+                        </style>
+                      </object>
+                    </property>
+                  </object>
                 </child>
                 <child>
-                  <object class="HdyViewSwitcherTitle" id="forecastStackSwitcher">
-                    <property name="visible">True</property>
-                    <property name="title" translatable="yes">Weather</property>
-                  </object>
-                  <packing>
+                  <object class="GtkStackPage">
                     <property name="name">city</property>
-                  </packing>
+                    <property name="child">
+                      <object class="AdwViewSwitcherTitle" id="forecastStackSwitcher">
+                     
+                        <property name="title" translatable="yes">Weather</property>
+                      </object>
+                    </property>
+                  </object>
                 </child>
               </object>
             </child>
             <child>
-              <object class="GtkMenuButton" id="primary-menu-button">
-                <property name="visible">True</property>
-                <property name="can_focus">True</property>
-                <property name="receives_default">False</property>
+              <object class="GtkMenuButton">
+                <property name="focusable">1</property>
                 <property name="valign">center</property>
                 <property name="menu_model">primary-menu</property>
                 <child>
-                  <object class="GtkImage" id="primary-menu-img">
-                    <property name="visible">True</property>
-                    <property name="can_focus">False</property>
+                  <object class="GtkImage">
                     <property name="icon_name">open-menu-symbolic</property>
                   </object>
                 </child>
               </object>
-              <packing>
-                <property name="pack_type">end</property>
-              </packing>
             </child>
           </object>
         </child>
         <child>
           <object class="GtkStack" id="stack">
-            <property name="can_focus">False</property>
+       
             <property name="transition_type">crossfade</property>
             <child>
-              <object class="HdyStatusPage" id="searchView">
-                <property name="visible">True</property>
-                <property name="icon_name">mark-location-symbolic</property>
-                <property name="title" translatable="yes">Welcome to Weather!</property>
-                <property name="description" translatable="yes">To get started, select a location.</property>
-                <child>
-                  <object class="GWeatherLocationEntry" id="searchEntry">
-                    <property name="visible">True</property>
-                    <property name="can_focus">True</property>
-                    <property name="activates_default">True</property>
-                    <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 class="GtkStackPage">
+                <property name="name">search</property>
+                <property name="child">
+                  <object class="AdwStatusPage" id="searchView">
+                
+                    <property name="icon_name">mark-location-symbolic</property>
+                    <property name="title" translatable="yes">Welcome to Weather!</property>
+                    <property name="description" translatable="yes">To get started, select a 
location.</property>
+                    <child>
+                      <object class="Gjs_LocationSearchEntry" id="searchEntry">
+                        <!-- <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>
+                    <style>
+                      <class name="search-view"/>
+                    </style>
                   </object>
-                </child>
-                <style>
-                  <class name="search-view"/>
-                </style>
+                </property>
               </object>
-              <packing>
-                <property name="name">search</property>
-              </packing>
             </child>
+
           </object>
         </child>
         <child>
-          <object class="HdyViewSwitcherBar" id="forecastStackSwitcherBar">
-            <property name="visible">True</property>
-            <property name="reveal" bind-source="forecastStackSwitcher" bind-property="title-visible" 
bind-flags="sync-create" />
+          <object class="AdwViewSwitcherBar" id="forecastStackSwitcherBar">
+      
+            <property name="reveal" bind-source="forecastStackSwitcher" bind-property="title-visible" 
bind-flags="sync-create"/>
           </object>
         </child>
       </object>
diff --git a/meson.build b/meson.build
index dc0f1b8..5c08bab 100644
--- a/meson.build
+++ b/meson.build
@@ -9,11 +9,11 @@ gnome = import('gnome')
 
 dependency('glib-2.0')
 dependency('gobject-introspection-1.0', version: '>=1.35.9')
-dependency('gtk+-3.0', version :'>=3.20')
-dependency('gjs-1.0', version: '>= 1.50.0')
+dependency('gtk4', version :'>=4.0')
+dependency('gjs-1.0', version: '>= 1.71.0')
 dependency('geoclue-2.0', version: '>= 0.12.99')
-dependency('gweather-3.0', version: '>= 40.0')
-dependency('libhandy-1', version: '>= 1.1.90')
+dependency('libadwaita-1')
+dependency('gweather4', version: '>= 3.90.0')
 
 # Profiles
 if get_option('profile') == 'development'
diff --git a/org.gnome.Weather.json b/org.gnome.Weather.json
index 36b18fe..e8ced94 100644
--- a/org.gnome.Weather.json
+++ b/org.gnome.Weather.json
@@ -54,32 +54,21 @@
                 {
                     "type" : "git",
                     "url" : "https://gitlab.gnome.org/GNOME/libgweather.git";,
-                    "tag" : "40.0"
+                    "branch" : "main"
                 }
             ]
         },
         {
-            "name" : "gnome-desktop",
+            "name" : "libadwaita",
             "buildsystem" : "meson",
             "config-opts" : [
-                "-Ddebug_tools=false",
-                "-Dudev=disabled",
-                "-Ddesktop_docs=false"
+                "-Denable_vala=false",
+                "-Dgtk_doc=false"
             ],
             "sources" : [
                 {
                     "type" : "git",
-                    "url" : "https://gitlab.gnome.org/GNOME/gnome-desktop.git";
-                }
-            ]
-        },
-        {
-            "name" : "libhandy",
-            "buildsystem" : "meson",
-            "sources" : [
-                {
-                    "type" : "git",
-                    "url" : "https://gitlab.gnome.org/GNOME/libhandy.git";
+                    "url" : "https://gitlab.gnome.org/GNOME/libadwaita.git";
                 }
             ]
         },
diff --git a/src/app/city.js b/src/app/city.js
index cf669c8..9fd535f 100644
--- a/src/app/city.js
+++ b/src/app/city.js
@@ -18,7 +18,6 @@
 
 const Gio = imports.gi.Gio;
 const GLib = imports.gi.GLib;
-const Gnome = imports.gi.GnomeDesktop;
 const GObject = imports.gi.GObject;
 const Gdk = imports.gi.Gdk;
 const Gtk = imports.gi.Gtk;
@@ -37,45 +36,45 @@ const UPDATED_TIME_TIMEOUT = 60; //s
 
 var WeatherWidget = GObject.registerClass({
     Template: 'resource:///org/gnome/Weather/weather-widget.ui',
-    InternalChildren: ['contentFrame', 'outerBox',
-                       'conditionsImage', 'placesButton', 'placesLabel',
-                       'temperatureLabel', 'apparentLabel',
-                       'forecastFrame', 'forecastStack',
-                       'leftButton', 'rightButton',
-                       'forecast-hourly', 'forecast-hourly-viewport',
-                       'forecast-daily', 'forecast-daily-viewport',
-                       'updatedTimeLabel', 'attributionLabel'],
-}, class WeatherWidget extends Gtk.Frame {
-
-    _init(application, window, params) {
-        super._init(Object.assign({
-            shadow_type: Gtk.ShadowType.NONE,
+    InternalChildren: [
+        'clamp',
+        'conditionsImage',
+        'placesButton',
+        'temperatureLabel',
+        'apparentLabel',
+        'forecastStack',
+        'leftButton',
+        'rightButton',
+        'forecastHourly',
+        'forecastHourlyViewport',
+        'forecastDaily',
+        'forecastDailyViewport',
+        'updatedTimeLabel',
+        'attributionLabel'],
+}, class WeatherWidget extends Gtk.Widget {
+    _init(application, window) {
+        super._init({
             name: 'weather-page'
-        }, params));
+        });
 
         this._info = null;
 
         this._worldView = new WorldView.WorldContentView(application, window);
         this._placesButton.set_popover(this._worldView);
 
-        this._forecasts = { };
-
-        for (let t of ['hourly', 'daily']) {
-            let box;
-            if (t == 'hourly') {
-                box = new HourlyForecast.HourlyForecastBox();
-            } else {
-                box = new DailyForecast.DailyForecastBox();
-            }
-
-            this._forecasts[t] = box;
-            this['_forecast_' + t + '_viewport'].add(box);
+        this._forecasts = {
+            hourly: new HourlyForecast.HourlyForecastBox(),
+            daily: new DailyForecast.DailyForecastBox(),
+        };
+        this._forecastHourlyViewport.set_child(this._forecasts.hourly);
+        this._forecastDailyViewport.set_child(this._forecasts.daily);
 
-            let fsw = this['_forecast_' + t];
-            let hscrollbar = fsw.get_hscrollbar();
+        for (const scrollWindow of [this._forecastHourly, this._forecastDaily]) {
+            let hscrollbar = scrollWindow.get_hscrollbar();
             hscrollbar.set_opacity(0.0);
             hscrollbar.hide();
-            let hadjustment = fsw.get_hadjustment();
+
+            let hadjustment = scrollWindow.get_hadjustment();
             hadjustment.connect('changed', () => this._syncLeftRightButtons());
             hadjustment.connect('value-changed', () => this._syncLeftRightButtons());
         }
@@ -111,29 +110,29 @@ var WeatherWidget = GObject.registerClass({
             this._beginScrollAnimation(target);
         });
 
-        this._forecastFrame.connect('draw', (frame, cr) => {
-            const width = frame.get_allocated_width();
-            const height = frame.get_allocated_height();
+        // this._forecastFrame.connect('draw', (frame, cr) => {
+        //     const width = frame.get_allocated_width();
+        //     const height = frame.get_allocated_height();
 
-            const borderRadius = 8;
+        //     const borderRadius = 8;
 
-            const arc0 = 0.0;
-            const arc1 = Math.PI * 0.5
-            const arc2 = Math.PI;
-            const arc3 = Math.PI * 1.5
+        //     const arc0 = 0.0;
+        //     const arc1 = Math.PI * 0.5
+        //     const arc2 = Math.PI;
+        //     const arc3 = Math.PI * 1.5
 
-            cr.newSubPath();
-            cr.arc(width - borderRadius, borderRadius, borderRadius, arc3, arc0);
-            cr.arc(width - borderRadius, height - borderRadius, borderRadius, arc0, arc1);
-            cr.arc(borderRadius, height - borderRadius, borderRadius, arc1, arc2);
-            cr.arc(borderRadius, borderRadius, borderRadius, arc2, arc3);
-            cr.closePath();
+        //     cr.newSubPath();
+        //     cr.arc(width - borderRadius, borderRadius, borderRadius, arc3, arc0);
+        //     cr.arc(width - borderRadius, height - borderRadius, borderRadius, arc0, arc1);
+        //     cr.arc(borderRadius, height - borderRadius, borderRadius, arc1, arc2);
+        //     cr.arc(borderRadius, borderRadius, borderRadius, arc2, arc3);
+        //     cr.closePath();
 
-            cr.clip();
-            cr.fill();
+        //     cr.clip();
+        //     cr.fill();
 
-            return false;
-        });
+        //     return false;
+        // });
 
         this._updatedTime = null;
         this._updatedTimeTimeoutId = 0;
@@ -141,6 +140,15 @@ var WeatherWidget = GObject.registerClass({
         this.connect('destroy', () => this._onDestroy());
     }
 
+    // vfunc_measure(orientation, for_size) {
+    //     return this._box.measure(orientation, for_size);
+    // }
+
+    _cleanup() {
+        // this._contentGrid.unparent();
+        this._worldView._cleanup();
+    }
+
     _onDestroy() {
         if (this._updatedTimeTimeoutId) {
             GLib.Source.remove(this._updatedTimeTimeoutId);
@@ -153,10 +161,10 @@ var WeatherWidget = GObject.registerClass({
         if ((hadjustment.get_upper() - hadjustment.get_lower()) == hadjustment.page_size) {
             this._leftButton.hide();
             this._rightButton.hide();
-        } else if (hadjustment.value == hadjustment.get_lower()){
+        } else if (hadjustment.value == hadjustment.get_lower()) {
             this._leftButton.hide();
             this._rightButton.show();
-        } else if (hadjustment.value >= (hadjustment.get_upper() - hadjustment.page_size)){
+        } else if (hadjustment.value >= (hadjustment.get_upper() - hadjustment.page_size)) {
             this._leftButton.show();
             this._rightButton.hide();
         } else {
@@ -183,7 +191,7 @@ var WeatherWidget = GObject.registerClass({
 
         if (now < end) {
             t = (now - start) / SCROLLING_ANIMATION_TIME;
-            t = Util.easeOutCubic (t);
+            t = Util.easeOutCubic(t);
             hadjustment.value = value + t * (target - value);
             return true;
         } else {
@@ -220,13 +228,13 @@ var WeatherWidget = GObject.registerClass({
             country = country.get_parent();
 
         if (country)
-            this._placesLabel.set_text(city.get_name() + ', ' + country.get_name());
+            this._placesButton.set_label(city.get_name() + ', ' + country.get_name());
         else
-            this._placesLabel.set_text(city.get_name());
+            this._placesButton.set_label(city.get_name());
 
         this._worldView.refilter();
 
-        this._conditionsImage.iconName = info.get_icon_name() + '-large';
+        this._conditionsImage.iconName = `${info.get_icon_name()}-large`;
 
         const [, tempValue] = info.get_value_temp(GWeather.TemperatureUnit.DEFAULT);
         this._temperatureLabel.label = '%d°'.format(Math.round(tempValue));
@@ -295,26 +303,32 @@ var WeatherWidget = GObject.registerClass({
     }
 });
 
+WeatherWidget.set_layout_manager_type(Gtk.BoxLayout);
+
+
 var WeatherView = GObject.registerClass({
     Template: 'resource:///org/gnome/Weather/city.ui',
-    InternalChildren: ['spinner']
-}, class WeatherView extends Gtk.Stack {
+
+    InternalChildren: ['spinner', 'stack']
+}, class WeatherView extends Gtk.Widget {
 
     _init(application, window, params) {
         super._init(params);
 
         this._infoPage = new WeatherWidget(application, window);
-        this.add_named(this._infoPage, 'info');
+        this._stack.add_named(this._infoPage, 'info');
 
         this._info = null;
         this._updateId = 0;
 
         this.connect('destroy', () => this._onDestroy());
 
-        this._wallClock = new Gnome.WallClock();
-        this._clockHandlerId = 0;
-
         this._desktopSettings = new Gio.Settings({ schema_id: 'org.gnome.desktop.interface' });
+
+    }
+
+    _cleanup() {
+        this._infoPage._cleanup();
     }
 
     get info() {
@@ -348,7 +362,7 @@ var WeatherView = GObject.registerClass({
     }
 
     update() {
-        this.visible_child_name = 'loading';
+        this._stack.visible_child_name = 'loading';
         this._spinner.start();
         this._infoPage.clear();
 
@@ -359,10 +373,12 @@ var WeatherView = GObject.registerClass({
         this._infoPage.clear();
         this._infoPage.update(info);
         this._spinner.stop();
-        this.visible_child_name = 'info';
+        this._stack.visible_child_name = 'info';
     }
 
     getInfoPage() {
         return this._infoPage;
     }
 });
+
+WeatherView.set_layout_manager_type(Gtk.BinLayout);
\ No newline at end of file
diff --git a/src/app/dailyForecast.js b/src/app/dailyForecast.js
index e3c71e8..3ac5ad9 100644
--- a/src/app/dailyForecast.js
+++ b/src/app/dailyForecast.js
@@ -20,6 +20,7 @@ const Gio = imports.gi.Gio;
 const GLib = imports.gi.GLib;
 const GObject = imports.gi.GObject;
 const Gtk = imports.gi.Gtk;
+const Gdk = imports.gi.Gdk;
 const GWeather = imports.gi.GWeather;
 
 const Thermometer = imports.app.thermometer;
@@ -28,14 +29,14 @@ const Util = imports.misc.util;
 
 var DailyForecastBox = GObject.registerClass(class DailyForecastBox extends Gtk.Box {
 
-    _init(params) {
-        super._init(Object.assign({
+    _init() {
+        super._init({
             orientation: Gtk.Orientation.HORIZONTAL,
             spacing: 0,
             name: 'daily-forecast-box',
-        }, params));
+        });
 
-        this.get_accessible().accessible_name = _('Daily Forecast');
+        this.update_property([Gtk.AccessibleProperty.LABEL], [_('Daily Forecast')]);
     }
 
     // get infos for the correct day
@@ -60,8 +61,8 @@ var DailyForecastBox = GObject.registerClass(class DailyForecastBox extends Gtk.
 
         let weekInfos = [];
         while (i < infos.length) {
-            let dayInfos = {day: day, infos: []};
-            for ( ; i < infos.length; i++) {
+            let dayInfos = { day: day, infos: [] };
+            for (; i < infos.length; i++) {
                 let info = infos[i];
 
                 let datetime = Util.getDateTime(info);
@@ -100,14 +101,16 @@ var DailyForecastBox = GObject.registerClass(class DailyForecastBox extends Gtk.
                     this._addSeparator();
             }
         } else {
-            let label = new Gtk.Label({ label: _('Forecast not available'),
-                                        use_markup: true,
-                                        visible: true });
-            this.pack_start(label, true, false, 0);
+            let label = new Gtk.Label({
+                label: _('Forecast not available'),
+                use_markup: true,
+                visible: true
+            });
+            this.prepend(label);
         }
     }
 
-    _addDayEntry({day, infos}, weekHighestTemp, weekLowestTemp) {
+    _addDayEntry({ day, infos }, weekHighestTemp, weekLowestTemp) {
         let maxInfo;
         let maxTemp = -Infinity;
 
@@ -189,12 +192,12 @@ var DailyForecastBox = GObject.registerClass(class DailyForecastBox extends Gtk.
         let dateFormat = _('%b %e');
         dayEntry.dateLabel.label = day.format(dateFormat);
 
-        dayEntry.image.iconName = dayInfo.get_icon_name() + '-small';
+        dayEntry.image.iconName = `${dayInfo.get_icon_name()}-small`;
 
         const adjustment = Gtk.Adjustment.new(minTemp,
-                                              weekLowestTemp, weekHighestTemp,
-                                              0, 0,
-                                              maxTemp - minTemp);
+            weekLowestTemp, weekHighestTemp,
+            0, 0,
+            maxTemp - minTemp);
         dayEntry.thermometer.adjustment = adjustment;
 
         dayEntry.nightTemperatureLabel.label = Util.getTempString(nightInfo);
@@ -217,19 +220,21 @@ var DailyForecastBox = GObject.registerClass(class DailyForecastBox extends Gtk.
         dayEntry.eveningHumidity.label = eveningInfo.get_humidity();
         this._setWindInfo(eveningInfo, dayEntry.eveningWind);
 
-        this.pack_start(dayEntry, false, false, 0);
+        this.prepend(dayEntry);
     }
 
     _addSeparator() {
-        let separator = new Gtk.Separator({ orientation: Gtk.Orientation.VERTICAL,
-                                            visible: true});
-        this.pack_start(separator, false, false, 0);
+        let separator = new Gtk.Separator({
+            orientation: Gtk.Orientation.VERTICAL,
+            visible: true
+        });
+        this.prepend(separator);
     }
 
     _setWindInfo(info, label) {
         let [ok, speed, direction] = info.get_value_wind(GWeather.SpeedUnit.DEFAULT);
         if (ok) {
-            label.label = speed.toFixed(1).toString() + ' ' +  
GWeather.speed_unit_to_string(GWeather.SpeedUnit.DEFAULT);
+            label.label = `${speed.toFixed(1).toString()} 
${GWeather.speed_unit_to_string(GWeather.SpeedUnit.DEFAULT)}`;
         } else {
             /* Fall back to get_wind() */
             label.label = info.get_wind();
@@ -237,26 +242,30 @@ var DailyForecastBox = GObject.registerClass(class DailyForecastBox extends Gtk.
     }
 
     clear() {
-        this.foreach(function(w) { w.destroy(); });
+        for (const w of Array.from(this)) {
+            this.remove(w);
+        }
     }
 });
 
 var DayEntry = GObject.registerClass({
     Template: 'resource:///org/gnome/Weather/day-entry.ui',
     InternalChildren: ['nameLabel', 'dateLabel', 'image',
-                       'thermometer',
-                       'nightTemperatureLabel', 'nightImage',
-                       'nightHumidity', 'nightWind',
-                       'morningTemperatureLabel', 'morningImage',
-                       'morningHumidity', 'morningWind',
-                       'afternoonTemperatureLabel', 'afternoonImage',
-                       'afternoonHumidity', 'afternoonWind',
-                       'eveningTemperatureLabel', 'eveningImage',
-                       'eveningHumidity', 'eveningWind'],
-}, class DayEntry extends Gtk.Box {
+        'thermometer',
+        'nightTemperatureLabel', 'nightImage',
+        'nightHumidity', 'nightWind',
+        'morningTemperatureLabel', 'morningImage',
+        'morningHumidity', 'morningWind',
+        'afternoonTemperatureLabel', 'afternoonImage',
+        'afternoonHumidity', 'afternoonWind',
+        'eveningTemperatureLabel', 'eveningImage',
+        'eveningHumidity', 'eveningWind'],
+}, class DayEntry extends Gtk.Widget {
 
     _init(params) {
         super._init(params);
+
+        this.layoutManager.orientation = Gtk.Orientation.VERTICAL;
     }
 
     get nameLabel() {
@@ -339,3 +348,6 @@ var DayEntry = GObject.registerClass({
         return this._eveningWind;
     }
 });
+
+DayEntry.set_layout_manager_type(Gtk.BoxLayout);
+      
\ No newline at end of file
diff --git a/src/app/entry.js b/src/app/entry.js
new file mode 100644
index 0000000..4531615
--- /dev/null
+++ b/src/app/entry.js
@@ -0,0 +1,360 @@
+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.object("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 {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;
+}
+
+GWeather.Location.prototype[Symbol.iterator] = function* () {
+    let child = this.next_child(null);
+
+    while (child != null) {
+        yield child;
+        child = this.next_child(child);
+    }
+}
+
+/** @typedef {Gio.ListModel} ListModel */
+
+const LocationListModel = GObject.registerClass(
+    {
+        Implements: [Gio.ListModel]
+    },
+    class LocationListModel extends Gtk.Widget {
+
+
+        _init() {
+            super._init();
+
+            this._top = GWeather.Location.get_world();
+            this._show_named_timezones = false;
+
+            this._list = [];
+        }
+
+        /**
+         * @param {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];
+            /** @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;
+        }
+    }
+);
+LocationListModel.set_layout_manager_type(Gtk.BinLayout);
+LocationListModel.set_css_name('entry');
+
+var LocationSearchEntry = GObject.registerClass(
+    {
+        Properties: {
+            'location': GObject.ParamSpec.object("location", "location", "location", 
GObject.ParamFlags.READWRITE, GWeather.Location.$gtype)
+        },
+        Signals: {
+            'search-updated': { param_types: [GObject.String] },
+        }
+    },
+    class LocationSearchEntry extends Gtk.Widget {
+        _init() {
+            super._init();
+
+            try {
+                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._entry.connect("search-changed", (source) => {
+                    const text = source.get_text();
+
+                    if (text === null || text === '') {
+                        this._filter?.set_search(null);
+                        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();
+                    }
+
+                    this._filter?.set_search(text);
+                    this.emit('search-updated', text);
+                });
+
+                try {
+                    this._model.fill();
+                } catch (error) {
+                    console.error(error);
+                }
+            } catch (e) {
+                logError(e);
+            }
+        }
+
+        set location(loc) {
+            this._location = loc;
+            this.notify('location');
+        }
+
+        get location() {
+            return this._location ?? null;
+        }
+
+        /**
+         * @param {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 {Gtk.ListView} listview 
+         */
+        setListView(listview) {
+            if (this._listview) {
+                this._listview.set_factory(null);
+                this._listview.set_model(null);
+                this._listview.unparent();
+            }
+
+            this._listview = listview;
+            const factory = this._buildFactory();
+            listview.set_factory(factory);
+            const selection = this._populateModel();
+            listview.set_model(selection);
+        }
+
+
+      
+        _populateModel() {
+            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) => {
+                const item = selection.get_selected_item();
+
+                if (item instanceof LocationItem) {
+                    this.location = item.location;
+
+                    if (this._popup) {
+                        this._popup.popdown();
+                    }
+                }
+            });
+            return selection;
+        }
+
+        _buildFactory() {
+            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;
+        }
+
+        _cleanup() {
+            if (this._listview instanceof Gtk.ListView) {
+                this._listview.set_model(null);
+            }
+        }
+    }
+);
+
diff --git a/src/app/hourlyForecast.js b/src/app/hourlyForecast.js
index 543753a..2284c7d 100644
--- a/src/app/hourlyForecast.js
+++ b/src/app/hourlyForecast.js
@@ -22,6 +22,7 @@ const GObject = imports.gi.GObject;
 const Gdk = imports.gi.Gdk;
 const Gtk = imports.gi.Gtk;
 const GWeather = imports.gi.GWeather;
+const Graphene = imports.gi.Graphene;
 
 const Util = imports.misc.util;
 
@@ -29,16 +30,14 @@ const Util = imports.misc.util;
 const TWENTY_FOUR_HOURS = 24 * 3600 * 1000 * 1000;
 
 var HourlyForecastBox = GObject.registerClass(class HourlyForecastBox extends Gtk.Box {
-
-    _init(params) {
-        super._init(Object.assign({
+    _init() {
+        super._init({
             orientation: Gtk.Orientation.HORIZONTAL,
             spacing: 0,
             name: 'hourly-forecast-box',
-        }, params));
-
-        this.get_accessible().accessible_name = _('Hourly Forecast');
-
+        });
+        
+        this.update_property([Gtk.AccessibleProperty.LABEL], [_('Hourly Forecast')]);
         this._settings = new Gio.Settings({ schema_id: 'org.gnome.desktop.interface' });
 
         this._hourlyInfo = [];
@@ -54,7 +53,7 @@ var HourlyForecastBox = GObject.registerClass(class HourlyForecastBox extends Gt
         for (let i = 0; i < infos.length; i++) {
             let info = infos[i];
 
-            let [ok, date] = info.get_value_update();
+            let [, date] = info.get_value_update();
             let datetime = GLib.DateTime.new_from_unix_utc(date).to_timezone(now.get_timezone());
 
             if (datetime.difference(now) <= 0)
@@ -74,7 +73,7 @@ var HourlyForecastBox = GObject.registerClass(class HourlyForecastBox extends Gt
 
         let coords = info.location.get_coords();
         let nearestCity = GWeather.Location.get_world().find_nearest_city(coords[0], coords[1]);
-        let tz = GLib.TimeZone.new(nearestCity.get_timezone().get_tzid());
+        let tz = nearestCity.get_timezone();
         let now = GLib.DateTime.new_now(tz);
 
         let hourlyInfo = this._preprocess(now, forecasts);
@@ -94,7 +93,7 @@ var HourlyForecastBox = GObject.registerClass(class HourlyForecastBox extends Gt
             let label = new Gtk.Label({ label: _('Forecast not available'),
                                         use_markup: true,
                                         visible: true });
-            this.pack_start(label, true, false, 0);
+            this.prepend(label);
         }
 
         this._hourlyInfo = hourlyInfo;
@@ -128,7 +127,7 @@ var HourlyForecastBox = GObject.registerClass(class HourlyForecastBox extends Gt
             hourEntry.temperatureLabel.set_markup(label);
         };
 
-        this.pack_start(hourEntry, false, false, 0);
+        this.prepend(hourEntry);
 
         this._hasForecastInfo = true;
     }
@@ -136,18 +135,26 @@ var HourlyForecastBox = GObject.registerClass(class HourlyForecastBox extends Gt
     _addSeparator() {
         let separator = new Gtk.Separator({ orientation: Gtk.Orientation.VERTICAL,
                                             visible: true});
-        this.pack_start(separator, false, false, 0);
+        this.prepend(separator);
     }
 
     clear() {
-        this.foreach(function(w) { w.destroy(); });
+      for (const w of Array.from(this)) {
+          this.remove(w);
+      }
     }
 
     hasForecastInfo() {
         return this._hasForecastInfo;
     }
 
-    vfunc_draw(cr) {
+    vfunc_snapshot(snapshot) {
+        const allocation = this.get_allocation();
+        
+        const rect = new Graphene.Rect();
+        rect.init(0, 0, allocation.width, allocation.height);
+        
+        let cr = snapshot.append_cairo(rect);
         const temps = this._hourlyInfo.map(info => Util.getTemp(info));
 
         const maxTemp = Math.max(...temps);
@@ -209,7 +216,7 @@ var HourlyForecastBox = GObject.registerClass(class HourlyForecastBox extends Gt
         cr.lineTo(0, height);
         cr.fill();
 
-        super.vfunc_draw(cr);
+        super.vfunc_snapshot(snapshot);
         cr.$dispose();
 
         return Gdk.EVENT_PROPAGATE;
@@ -218,11 +225,12 @@ var HourlyForecastBox = GObject.registerClass(class HourlyForecastBox extends Gt
 
 var HourEntry = GObject.registerClass({
     Template: 'resource:///org/gnome/Weather/hour-entry.ui',
+
     InternalChildren: ['timeLabel', 'image', 'temperatureLabel'],
-}, class HourEntry extends Gtk.Box {
+}, class HourEntry extends Gtk.Widget {
 
     _init(params) {
-        super._init(params);
+        super._init({...params});
     }
 
     get timeLabel() {
@@ -237,3 +245,4 @@ var HourEntry = GObject.registerClass({
         return this._temperatureLabel;
     }
 });
+HourEntry.set_layout_manager_type(Gtk.BoxLayout);
\ No newline at end of file
diff --git a/src/app/main.js b/src/app/main.js
index 3b554ee..83a4363 100644
--- a/src/app/main.js
+++ b/src/app/main.js
@@ -20,15 +20,18 @@ pkg.initFormat();
 pkg.initGettext();
 window.ngettext = imports.gettext.ngettext;
 
-pkg.require({ 'Gdk': '3.0',
-              'Gio': '2.0',
-              'GLib': '2.0',
-              'GObject': '2.0',
-              'Gtk': '3.0',
-              'GWeather': '3.0' });
+pkg.require({
+    'Gdk': '4.0',
+    'Gio': '2.0',
+    'GLib': '2.0',
+    'GObject': '2.0',
+    'Gtk': '4.0',
+    'Adw': '1',
+    'GWeather': '4.0'
+});
 
 const ByteArray = imports.byteArray;
-const Handy = imports.gi.Handy;
+const Adw = imports.gi.Adw;
 const Gdk = imports.gi.Gdk;
 const Gio = imports.gi.Gio;
 const GLib = imports.gi.GLib;
@@ -36,11 +39,15 @@ const GObject = imports.gi.GObject;
 const Gtk = imports.gi.Gtk;
 const GWeather = imports.gi.GWeather;
 
+// ensure the type before we call to GtkBuilder
+imports.app.entry;
+
 const Util = imports.misc.util;
 const Window = imports.app.window;
 const World = imports.shared.world;
 const CurrentLocationController = imports.app.currentLocationController;
 
+
 const ShellIntegrationInterface = ByteArray.toString(
     Gio.resources_lookup_data('/org/gnome/shell/ShellWeatherIntegration.xml', 0).get_data());
 
@@ -54,9 +61,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) ';
@@ -88,19 +96,19 @@ const Application = GObject.registerClass(
 
     vfunc_startup() {
         super.vfunc_startup();
-        Handy.init();
-        // ensure the type before we call to GtkBuilder
-        GWeather.LocationEntry;
+        Adw.init();
 
         Util.loadStyleSheet('/org/gnome/Weather/application.css');
 
-        Handy.StyleManager
-            .get_default()
-            .set_color_scheme(Handy.ColorScheme.PREFER_LIGHT);
+        // TODO
+        // Handy.StyleManager
+        //     .get_default()
+        //     .set_color_scheme(Handy.ColorScheme.PREFER_LIGHT);
 
         this.world = GWeather.Location.get_world();
         this.model = new World.WorldModel(this.world, true);
         this.currentLocationController = new CurrentLocationController.CurrentLocationController(this.model);
+
         this.model.load();
 
 
@@ -172,9 +180,9 @@ const Application = GObject.registerClass(
         });
         this.add_action(temperatureAction);
 
-        this.add_accelerator("Escape", "win.selection-mode", new GLib.Variant('b', false));
-        this.add_accelerator("<Primary>a", "win.select-all", null);
-        this.add_accelerator("<Primary>q", "app.quit", null);
+        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) {
@@ -270,5 +278,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.run_dispose();
+        }
+    });
+
+    application.run(argv);
 }
diff --git a/src/app/thermometer.js b/src/app/thermometer.js
index 8356ea4..b81a97f 100644
--- a/src/app/thermometer.js
+++ b/src/app/thermometer.js
@@ -23,6 +23,7 @@ const Gdk = imports.gi.Gdk;
 const Gtk = imports.gi.Gtk;
 const Pango = imports.gi.Pango;
 const Cairo = imports.cairo;
+const Graphene = imports.gi.Graphene;
 
 const Thermometer = GObject.registerClass({
   Properties: {
@@ -35,28 +36,21 @@ const Thermometer = GObject.registerClass({
     ),
   },
   CssName: 'thermometer',
-},class Thermometer extends Gtk.DrawingArea {
+}, class Thermometer extends Gtk.Widget {
 
-  _init(params) {
+  _init({ adjustment, ...params }) {
     super._init(params);
 
-    const styleContext = this.get_style_context();
-
-    const createStyleContext = (selector) => {
-      const path = styleContext.get_path().copy();
+    this._drawing = new Gtk.DrawingArea();
+    this._drawing.set_parent(this);
 
-      const pos = path.append_type(GObject.TYPE_NONE);
-      path.iter_set_object_name(pos, selector);
-
-      const context = Gtk.StyleContext.new();
-      context.set_parent(styleContext);
-      context.set_path(path);
+    this._adjustment = adjustment;
+    if (adjustment) {
 
-      return context;
     }
-
-    this._highStyleContext = createStyleContext('high');
-    this._lowStyleContext = createStyleContext('low');
+    this._highLayout = null;
+    this._lowLayout = null;
+    this._updatePangoLayouts();
 
     this._radius = 12;
     this._margin = 12;
@@ -72,35 +66,51 @@ const Thermometer = GObject.registerClass({
     this._updatePangoLayouts(adjustment);
   }
 
-  vfunc_get_preferred_width() {
+  // vfunc_get_preferred_width() {
+  //   const [highWidth] = this._highLayout.get_pixel_size();
+  //   const [lowWidth] = this._lowLayout.get_pixel_size();
+
+  //   const width = Math.max(this._radius, highWidth, lowWidth);
+  //   return [width, width];
+  // }
+
+  // vfunc_get_preferred_height() {
+  //   const [, highHeight] = this._highLayout.get_pixel_size();
+  //   const [, lowHeight] = this._lowLayout.get_pixel_size();
+
+  //   const height = highHeight + this._margin + lowHeight;
+  //   return [height, height];
+  // }
+
+  vfunc_measure(orientation, for_size) {
     const [highWidth] = this._highLayout.get_pixel_size();
     const [lowWidth] = this._lowLayout.get_pixel_size();
 
-    const width = Math.max(this._radius, highWidth, lowWidth);
-    return [width, width];
-  }
-
-  vfunc_get_preferred_height() {
     const [, highHeight] = this._highLayout.get_pixel_size();
     const [, lowHeight] = this._lowLayout.get_pixel_size();
 
-    const height = highHeight + this._maring + lowHeight;
-    return [height, height];
+    const allocatedHeight = highHeight + this._margin + this.radius + lowHeight;
+
+    return [allocatedHeight, allocatedHeight + 100];
   }
 
   _updatePangoLayouts(adjustment) {
+    if (!adjustment) return;
+
     const value = adjustment.get_value();
     const pageSize = adjustment.get_page_size();
 
     const highLabel = Math.round(value + pageSize) + "°";
-    this._highLayout = this._createPangoLayout(this._highStyleContext, highLabel);
+    this._highLayout = this._createPangoLayout(highLabel);
 
     const lowLabel = Math.round(value) + "°";
-    this._lowLayout = this._createPangoLayout(this._lowStyleContext, lowLabel);
+    this._lowLayout = this._createPangoLayout(lowLabel);
+
+    this.queue_draw();
   }
 
-  _createPangoLayout(styleContext, text) {
-    const context = this._createPangoContext(styleContext);
+  _createPangoLayout(text) {
+    const context = this.get_pango_context();
     const layout = Pango.Layout.new(context);
 
     layout.set_text(text, -1);
@@ -108,17 +118,35 @@ const Thermometer = GObject.registerClass({
     return layout;
   }
 
-  _createPangoContext(styleContext) {
-    const display = this.get_display();
-    const context = Gdk.pango_context_get_for_display(display);
+  vfunc_snapshot(snapshot) {
+    if (!this._adjustment) {
+      super.vfunc_snapshot(snapshot);
+
+      return Gdk.EVENT_PROPAGATE;
+    }
 
-    const font = styleContext.get_property('font', styleContext.get_state());
-    context.set_font_description (font);
+    const allocation = this._drawing.get_allocation();
 
-    return context;
-  }
+    const rect = new Graphene.Rect();
+    rect.init(0, 0, allocation.width, allocation.height);
+
+    const highColor = new Gdk.RGBA();
+    highColor.parse("#c89009");
+    const lowColor = new Gdk.RGBA();
+    lowColor.parse("#2174d9");
+
+    // snapshot.append_linear_gradient(rect, 0, allocation.height - 12, [
+    //   new Gsk.ColorStop({ offset: 0, color: highColor }),
+    //   new Gsk.ColorStop({ offset: allocation.height / 2, color: lowColor }),
+    // ])
+
+    // const border = new Gsk.RoundedRect();
+    // border.init_from_rect(rect, 12);
+    // snapshot.push_rounded_clip(border)
 
-  vfunc_draw(cr) {
+
+
+    let cr = snapshot.append_cairo(rect);
     const lower = this._adjustment.get_lower();
     const upper = this._adjustment.get_upper();
     const value = this._adjustment.get_value();
@@ -127,8 +155,8 @@ const Thermometer = GObject.registerClass({
     const width = this.get_allocated_width();
     const height = this.get_allocated_height();
 
-    const [highWidth, highHeight] = this._highLayout.get_pixel_size();
-    const [lowWidth, lowHeight] = this._lowLayout.get_pixel_size();
+    const [, highHeight] = this._highLayout.get_pixel_size();
+    const [, lowHeight] = this._lowLayout.get_pixel_size();
 
     const radius = this._radius;
     const margin = this._margin;
@@ -142,7 +170,6 @@ const Thermometer = GObject.registerClass({
     let highY = 0;
     let lowY = height - lowHeight;
 
-    cr.save();
 
     if (maxScaleHeight > 0) {
       this._renderScale(cr, width / 2 - radius, scaleY, radius, scaleHeight);
@@ -151,17 +178,14 @@ const Thermometer = GObject.registerClass({
       lowY = scaleY + scaleHeight + radius + margin;
     }
 
-    Gtk.render_layout(this._highStyleContext, cr,
-                      width / 2 - highWidth / 2, highY,
-                      this._highLayout);
+    snapshot.append_layout(this._highLayout, highColor);
+    snapshot.append_layout(this._lowLayout, lowColor);
 
-    Gtk.render_layout(this._lowStyleContext, cr,
-                      width / 2 - lowWidth / 2, lowY,
-                      this._lowLayout);
+    super.vfunc_snapshot(snapshot);
 
-    cr.restore();
+    cr.$dispose();
 
-    return false;
+    return Gdk.EVENT_PROPAGATE;
   }
 
   _renderScale(cr, x, y, radius, height) {
@@ -180,13 +204,16 @@ const Thermometer = GObject.registerClass({
 
     const styleContext = this.get_style_context();
 
-    const [, warmColor] = styleContext.lookup_color('thermometer_warm_color');
-    pattern.addColorStopRGB(0.0, warmColor.red, warmColor.green, warmColor.blue);
 
-    const [, coldColor] = styleContext.lookup_color('thermometer_cold_color');
-    pattern.addColorStopRGB(1.0, coldColor.red, coldColor.green, coldColor.blue);
+    //const [, warmColor] = styleContext.lookup_color('thermometer_warm_color');
+    pattern.addColorStopRGB(0.0, 100, 0, 0);
+
+    //const [, coldColor] = styleContext.lookup_color('thermometer_cold_color');
+    pattern.addColorStopRGB(1.0, 0, 0, 100);
 
     return pattern;
   }
 
 });
+
+Thermometer.set_layout_manager_type(Gtk.BoxLayout);
diff --git a/src/app/window.js b/src/app/window.js
index 22d0894..111899e 100644
--- a/src/app/window.js
+++ b/src/app/window.js
@@ -16,7 +16,7 @@
 // with Gnome Weather; if not, write to the Free Software Foundation,
 // Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 
-const Handy = imports.gi.Handy;
+const Adw = imports.gi.Adw;
 const Gio = imports.gi.Gio;
 const GObject = imports.gi.GObject;
 const Gtk = imports.gi.Gtk;
@@ -36,16 +36,15 @@ const Page = {
 var MainWindow = GObject.registerClass({
     Template: 'resource:///org/gnome/Weather/window.ui',
     InternalChildren: ['header', 'refreshRevealer', 'refresh', 'forecastStackSwitcher', 'stack',
-                       'titleStack', 'searchView', 'searchEntry', 'forecastStackSwitcherBar']
-}, class MainWindow extends Handy.ApplicationWindow {
-
+        'titleStack','searchEntry', 'searchView', 'forecastStackSwitcherBar']
+}, class MainWindow extends Adw.ApplicationWindow {    
     _init(params) {
         super._init(params);
 
         this._world = this.application.world;
         this.currentInfo = null;
         this._currentPage = Page.SEARCH;
-        this._pageWidgets = [[],[]];
+        this._pageWidgets = [[], []];
 
         let aboutAction = new Gio.SimpleAction({
             enabled: true,
@@ -79,7 +78,7 @@ var MainWindow = GObject.registerClass({
         this._pageWidgets[Page.CITY].push(this._refresh);
 
         this._cityView = new City.WeatherView(this.application, this,
-                                              { hexpand: true, vexpand: true });
+            { hexpand: true, vexpand: true });
         this._stack.add_named(this._cityView, 'city');
 
         this._forecastStackSwitcher.set_stack(this._cityView.getInfoPage().getForecastStack());
@@ -97,7 +96,12 @@ var MainWindow = GObject.registerClass({
         }
 
         this._showingDefault = false;
-        this.show_all();
+        // this.show_all();
+    }
+
+    on_destroy() {
+        this._cityView._cleanup();
+        this._searchEntry._cleanup();
     }
 
     update() {
@@ -116,9 +120,7 @@ var MainWindow = GObject.registerClass({
             this._pageWidgets[this._currentPage][i].hide();
 
         for (let i = 0; i < this._pageWidgets[page].length; i++) {
-            let widget = this._pageWidgets[page][i];
-            if (!widget.no_show_all)
-                this._pageWidgets[page][i].show();
+            this._pageWidgets[page][i].show();
         }
 
         this._currentPage = page;
@@ -164,8 +166,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;
             }
         }
 
@@ -179,14 +182,14 @@ var MainWindow = GObject.registerClass({
     }
 
     _showAbout() {
-        let artists = [ 'Jakub Steiner <jimmac gmail com>',
-                        'Pink Sherbet Photography (D. Sharon Pruitt)',
-                        'Elliott Brown',
-                        'Analogick',
-                        'DBduo Photography (Daniel R. Blume)',
-                        'davharuk',
-                        'Tech Haven Ministries',
-                        'Jim Pennucci' ];
+        let artists = ['Jakub Steiner <jimmac gmail com>',
+            'Pink Sherbet Photography (D. Sharon Pruitt)',
+            'Elliott Brown',
+            'Analogick',
+            'DBduo Photography (Daniel R. Blume)',
+            'davharuk',
+            'Tech Haven Ministries',
+            'Jim Pennucci'];
 
         let name_prefix = '';
         if (pkg.name.endsWith('Devel')) {
@@ -197,19 +200,19 @@ var MainWindow = GObject.registerClass({
         let attribution = this._cityView.info ? this._cityView.info.get_attribution() : '';
         copyright += attribution ? '\n' + attribution : '';
         let aboutDialog = new Gtk.AboutDialog(
-            { artists: artists,
-              authors: [ 'Giovanni Campagna <gcampagna src gnome org>' ],
-              translator_credits: _("translator-credits"),
-              program_name: name_prefix + _("Weather"),
-              comments: _("A weather application"),
-              license_type: Gtk.License.GPL_2_0,
-              logo_icon_name: pkg.name,
-              version: pkg.version,
-              website: 'https://wiki.gnome.org/Apps/Weather',
-              wrap_license: true,
-              modal: true,
-              transient_for: this,
-              use_header_bar: true
+            {
+                artists: artists,
+                authors: ['Giovanni Campagna <gcampagna src gnome org>'],
+                translator_credits: _("translator-credits"),
+                program_name: name_prefix + _("Weather"),
+                comments: _("A weather application"),
+                license_type: Gtk.License.GPL_2_0,
+                logo_icon_name: pkg.name,
+                version: pkg.version,
+                website: 'https://wiki.gnome.org/Apps/Weather',
+                wrap_license: true,
+                modal: true,
+                transient_for: this
             });
 
         // HACK: we need to poke into gtkaboutdialog internals
@@ -221,9 +224,6 @@ var MainWindow = GObject.registerClass({
         copyrightLabel.show();
 
         aboutDialog.show();
-        aboutDialog.connect('response', function() {
-            aboutDialog.destroy();
-        });
     }
 
     _close() {
diff --git a/src/app/world.js b/src/app/world.js
index 1815629..3279a55 100644
--- a/src/app/world.js
+++ b/src/app/world.js
@@ -25,23 +25,27 @@ const GWeather = imports.gi.GWeather;
 const CurrentLocationController = imports.app.currentLocationController;
 const Util = imports.misc.util;
 
+class _WorldContentView extends Gtk.Popover {
+    static [GObject.TypeName] = 'WorldContentView';
 
-var WorldContentView = GObject.registerClass(
-    class WorldContentView extends Gtk.Popover {
-
-    _init(application, window, params) {
-        super._init(Object.assign({
+    _init(application, window, params = {}) {
+        super._init({
             hexpand: false,
-            vexpand: false
-        }, params));
-
-        this.get_accessible().accessible_name = _("World view");
+            vexpand: false,
+            ...params,
+        });
 
+        this.update_property([Gtk.AccessibleProperty.LABEL], [_("World view")]);
         let builder = new Gtk.Builder();
         builder.add_from_resource('/org/gnome/Weather/places-popover.ui');
 
         let grid = builder.get_object('popover-grid');
-        this.add(grid);
+        this.set_child(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;
         this._window = window;
@@ -58,18 +62,26 @@ 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.setListView(this._searchResults);
+        this._locationEntry.connect('search-updated', (_, 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');
         let autoLocSwitch = builder.get_object('auto-location-switch');
         this._currentLocationController = application.currentLocationController;
 
-        if(this._currentLocationController.autoLocation == CurrentLocationController.AutoLocation.ENABLED) {
+        if (this._currentLocationController.autoLocation == CurrentLocationController.AutoLocation.ENABLED) {
             autoLocStack.visible_child_name = 'locating-label';
         } else {
             autoLocStack.visible_child_name = 'auto-location-switch-grid';
@@ -119,15 +131,21 @@ 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();
     }
 
     _syncStackPopover() {
         if (this.model.length == 1)
-            this._stackPopover.set_visible_child_name("search-grid");
+            this._stackPopover.set_visible_child(this._searchGrid);
         else
-            this._stackPopover.set_visible_child_name("locations-grid");
+            this._stackPopover.set_visible_child(this._locationsGrid);
     }
 
     _filterListbox(row) {
@@ -147,55 +165,71 @@ var WorldContentView = GObject.registerClass(
     _onLocationAdded(model, info, isCurrentLocation) {
         let location = info.location;
 
-        let grid = new Gtk.Grid({ orientation: Gtk.Orientation.HORIZONTAL,
-                                  column_spacing: 12,
-                                  margin: 12,
-                                  visible: true });
+        let 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 name = location.get_city_name();
-        let locationGrid = new Gtk.Grid({ orientation: Gtk.Orientation.HORIZONTAL,
-                                          column_spacing: 12,
-                                          halign: Gtk.Align.START,
-                                          hexpand: true,
-                                          visible: true });
-        let locationLabel = new Gtk.Label({ label: name,
-                                            use_markup: true,
-                                            halign: Gtk.Align.START,
-                                            visible: true });
+        let locationGrid = new Gtk.Grid({
+            orientation: Gtk.Orientation.HORIZONTAL,
+            column_spacing: 12,
+            halign: Gtk.Align.START,
+            hexpand: true,
+            visible: true
+        });
+        let locationLabel = new Gtk.Label({
+            label: name,
+            use_markup: true,
+            halign: Gtk.Align.START,
+            visible: true
+        });
         locationGrid.attach(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 });
+        let tempLabel = new Gtk.Label({
+            use_markup: true,
+            halign: Gtk.Align.END,
+            margin_start: 12,
+            visible: true
+        });
         grid.attach(tempLabel, 1, 0, 1, 1);
 
         if (isCurrentLocation) {
-            let image = new Gtk.Image({ icon_size: Gtk.IconSize.LARGE_TOOLBAR,
-                                        icon_name: 'mark-location-symbolic',
-                                        use_fallback: true,
-                                        halign: Gtk.Align.START,
-                                        visible: true });
+            let image = new Gtk.Image({
+                icon_size: Gtk.IconSize.LARGE,
+                icon_name: 'mark-location-symbolic',
+                use_fallback: true,
+                halign: Gtk.Align.START
+            });
             locationGrid.attach(image, 1, 0, 1, 1);
         }
 
-        let image = new Gtk.Image({ icon_size: Gtk.IconSize.LARGE_TOOLBAR,
-                                    use_fallback: true,
-                                    halign: Gtk.Align.END,
-                                    visible: true });
+        let image = new Gtk.Image({
+            icon_size: Gtk.IconSize.LARGE,
+            use_fallback: true,
+            halign: Gtk.Align.END
+        });
         grid.attach(image, 2, 0, 1, 1);
 
         let row = new Gtk.ListBoxRow({ visible: true });
-        row.add(grid);
+        row.set_child(grid);
         row._info = info;
         row._isCurrentLocation = isCurrentLocation;
 
         if (isCurrentLocation) {
             if (this._currentLocationAdded) {
                 let row0 = this._listbox.get_row_at_index(0);
-                if (row0)
-                    row0.destroy();
+                if (row0) {
+                    // TODO: GTK4
+                    delete row0._info;
+                    this._listbox.remove(row0);
+                }
             }
 
             this._currentLocationAdded = true;
@@ -220,14 +254,10 @@ var WorldContentView = GObject.registerClass(
     }
 
     _onLocationRemoved(model, info) {
-        let rows = this._listbox.get_children();
-
-        for (let row of rows) {
-            if (row._info == info) {
-                row.destroy();
-                break;
-            }
-        }
+        Array.from(this._listbox).forEach((row) => {
+            delete row._info;
+            this._listbox.remove(row);
+        });
 
         if (info._updatedId) {
             info.disconnect(info._updatedId);
@@ -238,4 +268,6 @@ var WorldContentView = GObject.registerClass(
 
         this._syncStackPopover();
     }
-});
+};
+
+var WorldContentView = GObject.registerClass(_WorldContentView);
diff --git a/src/misc/util.js b/src/misc/util.js
index bd00d83..e3ffadc 100644
--- a/src/misc/util.js
+++ b/src/misc/util.js
@@ -28,7 +28,7 @@ const Gdk = imports.gi.Gdk;
 const Gio = imports.gi.Gio;
 const GLib = imports.gi.GLib;
 const Gtk = imports.gi.Gtk;
-const Handy = imports.gi.Handy;
+const Adw = imports.gi.Adw;
 const System = imports.system;
 const GWeather = imports.gi.GWeather;
 
@@ -47,7 +47,7 @@ function loadUI(resourcePath, objects) {
 function loadStyleSheet(resource) {
     let provider = new Gtk.CssProvider();
     provider.load_from_file(Gio.File.new_for_uri('resource://' + resource));
-    Gtk.StyleContext.add_provider_for_screen(Gdk.Screen.get_default(),
+    Gtk.StyleContext.add_provider_for_display(Gdk.Display.get_default(),
                                              provider,
                                              Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION);
 }
@@ -89,14 +89,6 @@ function getSettings(schemaId, path) {
                                   path: path });
 }
 
-function loadIcon(iconName, size) {
-    let theme = Gtk.IconTheme.get_default();
-
-    return theme.load_icon(iconName,
-                           size,
-                           Gtk.IconLookupFlags.GENERIC_FALLBACK);
-}
-
 function getWeatherConditions(info) {
     let conditions = info.get_conditions();
     if (conditions == '-') // Not significant
@@ -211,5 +203,5 @@ function getTempString(info) {
 }
 
 function isDarkTheme() {
-    return Handy.StyleManager.get_default().dark;
+    return Adw.StyleManager.get_default().dark;
 }
diff --git a/src/org.gnome.Weather.src.gresource.xml.in b/src/org.gnome.Weather.src.gresource.xml.in
index 9613401..220a28b 100644
--- a/src/org.gnome.Weather.src.gresource.xml.in
+++ b/src/org.gnome.Weather.src.gresource.xml.in
@@ -6,6 +6,7 @@
     <file>app/hourlyForecast.js</file>
     <file>app/thermometer.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>
diff --git a/src/service/main.js b/src/service/main.js
index 113bf93..b28e3d3 100644
--- a/src/service/main.js
+++ b/src/service/main.js
@@ -21,7 +21,7 @@ pkg.initFormat();
 pkg.require({ 'Gio': '2.0',
               'GLib': '2.0',
               'GObject': '2.0',
-              'GWeather': '3.0' });
+              'GWeather': '4.0' });
 
 const Gio = imports.gi.Gio;
 const GLib = imports.gi.GLib;


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