[gnome-maps/wip/gtk4-and-libshumate] WIP: Port to GTK 4 and libshumate




commit 0c5630936015261b13bc288e07c2fc9352f76338
Author: Marcus Lundblad <ml dfupdate se>
Date:   Thu Jun 30 23:11:19 2022 +0200

    WIP: Port to GTK 4 and libshumate

 data/ui/favorites-popover.ui          |  34 ++--
 data/ui/headerbar-left.ui             |  71 +++----
 data/ui/headerbar-right.ui            |  28 +--
 data/ui/layers-popover.ui             |  96 ++++------
 data/ui/main-window.ui                |  65 ++-----
 data/ui/place-bar.ui                  |  59 +++---
 data/ui/place-buttons.ui              |  55 ++----
 data/ui/place-list-row.ui             |  72 +++----
 data/ui/place-popover.ui              |  31 +--
 data/ui/route-entry.ui                |  38 ++--
 data/ui/sidebar.ui                    | 343 ++++++++++++++--------------------
 data/ui/transit-options-panel.ui      | 112 +++++------
 lib/maps-file-tile-source.c           | 289 +++++++++++++---------------
 lib/maps-file-tile-source.h           |   8 +-
 lib/maps-sync-map-source.c            |  96 ++++++++++
 lib/maps-sync-map-source.h            |  73 ++++++++
 lib/meson.build                       |  10 +-
 meson.build                           |   6 +-
 org.gnome.Maps.json                   |  47 +----
 src/application.js                    |  17 +-
 src/contextMenu.js                    |   2 +-
 src/epaf.js                           |   4 +-
 src/favoritesPopover.js               |   5 +-
 src/geoJSONSource.js                  |  49 ++---
 src/graphHopperTransit.js             |  10 +-
 src/layersPopover.js                  |  17 +-
 src/main.js                           |  12 +-
 src/mainWindow.js                     | 149 +++++++--------
 src/mapBubble.js                      |  33 +++-
 src/mapMarker.js                      |  24 +--
 src/mapSource.js                      |   2 +-
 src/mapView.js                        | 111 ++++++-----
 src/mapWalker.js                      |   5 -
 src/placeBar.js                       |  17 +-
 src/placeEntry.js                     |  18 +-
 src/placePopover.js                   |   2 -
 src/placeViewImage.js                 |  28 ++-
 src/printLayout.js                    |  15 +-
 src/routeEntry.js                     |  16 +-
 src/searchPopover.js                  |  12 +-
 src/shapeLayer.js                     |   7 +-
 src/sidebar.js                        |  63 ++++---
 src/storedRoute.js                    |  10 +-
 src/transitBoardMarker.js             |   1 -
 src/transitOptionsPanel.js            |  31 ++-
 src/transitPrintLayout.js             |  12 +-
 src/transitplugins/openTripPlanner.js |   6 +-
 src/transitplugins/opendataCH.js      |  14 +-
 src/transitplugins/resrobot.js        |  14 +-
 src/turnPointMarker.js                |   2 -
 src/userLocationMarker.js             |  15 +-
 src/utils.js                          |  16 +-
 52 files changed, 1056 insertions(+), 1216 deletions(-)
---
diff --git a/data/ui/favorites-popover.ui b/data/ui/favorites-popover.ui
index 130f0a3d..15d17667 100644
--- a/data/ui/favorites-popover.ui
+++ b/data/ui/favorites-popover.ui
@@ -1,45 +1,39 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <interface>
-  <!-- interface-requires gtk+ 3.10 -->
+  <requires lib="gtk" version="4.0"/>
   <template class="Gjs_FavoritesPopover" parent="GtkPopover">
-    <property name="visible">False</property>
-    <property name="no_show_all">True</property>
-    <property name="hexpand">False</property>
+    <property name="hexpand">0</property>
     <property name="width-request">320</property>
     <property name="height-request">400</property>
     <style>
       <class name="maps-popover"/>
     </style>
-    <child>
+    <property name="child">
       <object class="GtkGrid" id="mainGrid">
-        <property name="visible">True</property>
-        <property name="can_focus">False</property>
         <property name="orientation">vertical</property>
         <property name="row_spacing">6</property>
-        <property name="margin">6</property>
+        <property name="margin-start">6</property>
+        <property name="margin-end">6</property>
+        <property name="margin-top">6</property>
+        <property name="margin-bottom">6</property>
         <child>
           <object class="GtkEntry" id="entry">
-            <property name="visible">True</property>
-            <property name="can_focus">True</property>
+            <property name="focusable">1</property>
           </object>
         </child>
         <child>
           <object class="GtkScrolledWindow" id="scrolledWindow">
             <property name="hscrollbar_policy">never</property>
-            <property name="shadow_type">in</property>
-            <property name="visible">True</property>
-            <property name="vexpand">True</property>
-            <child>
+            <property name="vexpand">1</property>
+            <property name="child">
               <object class="GtkListBox" id="list">
-                <property name="visible">True</property>
-                <property name="can_focus">False</property>
-                <property name="expand">True</property>
-                <property name="activate_on_single_click">True</property>
+                <property name="hexpand">1</property>
+                <property name="vexpand">1</property>
               </object>
-            </child>
+            </property>
           </object>
         </child>
       </object>
-    </child>
+    </property>
   </template>
 </interface>
diff --git a/data/ui/headerbar-left.ui b/data/ui/headerbar-left.ui
index aa293360..c36d77e5 100644
--- a/data/ui/headerbar-left.ui
+++ b/data/ui/headerbar-left.ui
@@ -1,25 +1,20 @@
 <?xml version="1.0" encoding="UTF-8"?>
-<!-- Generated with glade 3.22.0 -->
 <interface>
-  <requires lib="gtk+" version="3.22"/>
+  <requires lib="gtk" version="4.0"/>
   <template class="Gjs_HeaderBarLeft" parent="GtkBox">
-    <property name="visible">True</property>
-    <property name="can_focus">False</property>
     <property name="spacing">6</property>
     <child>
       <object class="GtkButton" id="gotoUserLocationButton">
-        <property name="visible">True</property>
-        <property name="can-focus">True</property>
+        <property name="focusable">1</property>
         <property name="valign">center</property>
         <property name="action-name">win.goto-user-location</property>
-        <property name="tooltip-text" translatable="yes" comments="Translators: This is a tooltip">Go to 
current location</property>
+        <property name="tooltip-text" translatable="1" comments="Translators: This is a tooltip">Go to 
current location</property>
         <style>
           <class name="image-button"/>
         </style>
         <child>
           <object class="GtkImage" id="track-user-button-image">
-            <property name="visible">True</property>
-            <property name="icon-size">1</property>
+            <property name="icon-size">normal</property>
             <property name="icon-name">find-location-symbolic</property>
           </object>
         </child>
@@ -27,58 +22,44 @@
     </child>
     <child>
       <object class="GtkMenuButton" id="layersButton">
-        <property name="visible">True</property>
-        <property name="can-focus">True</property>
+        <property name="focusable">1</property>
         <property name="valign">center</property>
-        <property name="tooltip-text" translatable="yes" comments="Translators: This is a tooltip">Choose 
map type</property>
+        <property name="tooltip-text" translatable="1" comments="Translators: This is a tooltip">Choose map 
type</property>
         <style>
           <class name="image-button"/>
         </style>
         <child>
           <object class="GtkImage" id="layers-button-image">
-            <property name="visible">True</property>
-            <property name="icon-size">1</property>
+            <property name="icon-size">normal</property>
             <property name="icon-name">layers-button-symbolic</property>
           </object>
         </child>
       </object>
     </child>
     <child>
-      <object class="GtkBox">
-        <property name="visible">True</property>
-        <style>
-          <class name="linked"/>
-        </style>
+      <object class="GtkButton">
+        <property name="focusable">1</property>
+        <property name="valign">center</property>
+        <property name="action-name">win.zoom-out</property>
+        <property name="tooltip-text" translatable="1" comments="Translators: This is a tooltip">Zoom 
out</property>
         <child>
-          <object class="GtkButton">
-            <property name="visible">True</property>
-            <property name="can-focus">True</property>
-            <property name="valign">center</property>
-            <property name="action-name">win.zoom-out</property>
-            <property name="tooltip-text" translatable="yes" comments="Translators: This is a tooltip">Zoom 
out</property>
-            <child>
-              <object class="GtkImage">
-                <property name="visible">True</property>
-                <property name="icon-size">1</property>
-                <property name="icon-name">zoom-out-symbolic</property>
-              </object>
-            </child>
+          <object class="GtkImage">
+            <property name="icon-size">normal</property>
+            <property name="icon-name">zoom-out-symbolic</property>
           </object>
         </child>
+      </object>
+    </child>
+    <child>
+      <object class="GtkButton">
+        <property name="focusable">1</property>
+        <property name="valign">center</property>
+        <property name="action-name">win.zoom-in</property>
+        <property name="tooltip-text" translatable="1" comments="Translators: This is a tooltip">Zoom 
in</property>
         <child>
-          <object class="GtkButton">
-            <property name="visible">True</property>
-            <property name="can-focus">True</property>
-            <property name="valign">center</property>
-            <property name="action-name">win.zoom-in</property>
-            <property name="tooltip-text" translatable="yes" comments="Translators: This is a tooltip">Zoom 
in</property>
-            <child>
-              <object class="GtkImage">
-                <property name="visible">True</property>
-                <property name="icon-size">1</property>
-                <property name="icon-name">zoom-in-symbolic</property>
-              </object>
-            </child>
+          <object class="GtkImage">
+            <property name="icon-size">normal</property>
+            <property name="icon-name">zoom-in-symbolic</property>
           </object>
         </child>
       </object>
diff --git a/data/ui/headerbar-right.ui b/data/ui/headerbar-right.ui
index a806b64c..06a94057 100644
--- a/data/ui/headerbar-right.ui
+++ b/data/ui/headerbar-right.ui
@@ -1,17 +1,14 @@
 <?xml version="1.0" encoding="UTF-8"?>
-<!-- Generated with glade 3.22.0 -->
 <interface>
-  <requires lib="gtk+" version="3.22"/>
+  <requires lib="gtk" version="4.0"/>
   <template class="Gjs_HeaderBarRight" parent="GtkBox">
-    <property name="visible">True</property>
-    <property name="no-show-all">True</property>
-    <property name="can_focus">False</property>
     <property name="spacing">6</property>
     <child>
       <object class="GtkButton" id="printRouteButton">
+        <property name="visible">0</property>
         <property name="name">print-route</property>
-        <property name="can-focus">True</property>
-        <property name="tooltip-text" translatable="yes" comments="Translators: This is a tooltip">Print 
Route</property>
+        <property name="focusable">1</property>
+        <property name="tooltip-text" translatable="1" comments="Translators: This is a tooltip">Print 
Route</property>
         <property name="action-name">win.print-route</property>
         <property name="valign">center</property>
         <style>
@@ -19,7 +16,6 @@
         </style>
         <child>
           <object class="GtkImage" id="print-route-button-image">
-            <property name="visible">True</property>
             <property name="icon-name">document-print-symbolic</property>
           </object>
         </child>
@@ -27,17 +23,15 @@
     </child>
     <child>
       <object class="GtkMenuButton" id="favoritesButton">
-        <property name="visible">True</property>
-        <property name="can-focus">True</property>
+        <property name="focusable">1</property>
         <property name="valign">center</property>
-        <property name="tooltip-text" translatable="yes" comments="Translators: This is a tooltip">Toggle 
favorites</property>
+        <property name="tooltip-text" translatable="1" comments="Translators: This is a tooltip">Toggle 
favorites</property>
         <style>
           <class name="image-button"/>
         </style>
         <child>
           <object class="GtkImage" id="favorites-button-image">
-            <property name="visible">True</property>
-            <property name="icon-size">1</property>
+            <property name="icon-size">normal</property>
             <property name="icon-name">bookmarks-symbolic</property>
           </object>
         </child>
@@ -45,18 +39,16 @@
     </child>
     <child>
       <object class="GtkToggleButton" id="toggleSidebarButton">
-        <property name="visible">True</property>
-        <property name="can-focus">True</property>
+        <property name="focusable">1</property>
         <property name="valign">center</property>
         <property name="action-name">win.toggle-sidebar</property>
-        <property name="tooltip-text" translatable="yes" comments="Translators: This is a tooltip">Toggle 
route planner</property>
+        <property name="tooltip-text" translatable="1" comments="Translators: This is a tooltip">Toggle 
route planner</property>
         <style>
           <class name="image-button"/>
         </style>
         <child>
           <object class="GtkImage" id="toggle-sidebar-button-image">
-            <property name="visible">True</property>
-            <property name="icon-size">1</property>
+            <property name="icon-size">normal</property>
             <property name="icon-name">route-button-symbolic</property>
           </object>
         </child>
diff --git a/data/ui/layers-popover.ui b/data/ui/layers-popover.ui
index b423a7a3..ed5adeb7 100644
--- a/data/ui/layers-popover.ui
+++ b/data/ui/layers-popover.ui
@@ -1,108 +1,94 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <interface>
-  <!-- interface-requires gtk+ 3.10 -->
+  <requires lib="gtk" version="4.0"/>
   <template class="Gjs_LayersPopover" parent="GtkPopover">
-    <child>
+    <property name="child">
       <object class="GtkGrid" id="grid">
-        <property name="visible">True</property>
-        <property name="can_focus">False</property>
         <property name="halign">center</property>
         <property name="valign">center</property>
         <property name="row_spacing">5</property>
-        <property name="margin">5</property>
+        <property name="margin-start">5</property>
+        <property name="margin-end">5</property>
+        <property name="margin-top">5</property>
+        <property name="margin-bottom">5</property>
+        <!-- disable the map type swithers for now, as we only have street right now...-->
+        <!--
         <child>
-          <object class="GtkRadioButton" id="streetLayerButton">
-            <property name="visible">True</property>
-            <property name="can-focus">True</property>
-            <property name="draw-indicator">False</property>
+          <object class="GtkToggleButton" id="streetLayerButton">
             <style>
               <class name="layer-radio-button"/>
             </style>
             <child>
-              <object class="GtkImage" id="streetLayerImage">
-                <property name="visible">True</property>
-              </object>
+              <object class="GtkImage" id="streetLayerImage"/>
             </child>
+            <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="GtkRadioButton" id="aerialLayerButton">
-            <property name="visible">True</property>
-            <property name="can-focus">True</property>
-            <property name="draw-indicator">False</property>
+          <object class="GtkToggleButton" id="aerialLayerButton">
             <style>
               <class name="layer-radio-button"/>
             </style>
             <child>
-              <object class="GtkImage" id="aerialLayerImage">
-                <property name="visible">True</property>
-              </object>
+              <object class="GtkImage" id="aerialLayerImage"/>
             </child>
+            <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="GtkBox">
-            <property name="orientation">GTK_ORIENTATION_HORIZONTAL</property>
-            <property name="visible">True</property>
-            <property name="can-focus">False</property>
             <child>
               <object class="GtkLabel">
-                <property name="visible">True</property>
-                <property name="can-focus">False</property>
-                <property name="hexpand">True</property>
-                <property name="halign">GTK_ALIGN_START</property>
-                <property name="label" translatable="yes">Show Scale</property>
+                <property name="hexpand">1</property>
+                <property name="halign">start</property>
+                <property name="label" translatable="1">Show Scale</property>
               </object>
             </child>
             <child>
               <object class="GtkCheckButton">
-                <property name="visible">True</property>
-                <property name="can-focus">True</property>
+                <property name="focusable">1</property>
                 <property name="action-name">win.show-scale</property>
               </object>
             </child>
+            <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="GtkListBox" id="layersListBox">
             <property name="name">layers-list-box</property>
-            <property name="visible">false</property>
-            <property name="can_focus">False</property>
+            <property name="visible">0</property>
             <property name="selection-mode">none</property>
             <style>
               <class name="frame"/>
             </style>
+            <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="GtkButton" id="loadLayerButton">
-            <property name="visible">True</property>
-            <property name="can-focus">True</property>
-            <property name="label" translatable="yes" comments="Translators: This string uses ellipsis 
character">Open Shape Layer…</property>
+            <property name="focusable">1</property>
+            <property name="label" translatable="1" comments="Translators: This string uses ellipsis 
character">Open Shape Layer…</property>
             <property name="action-name">win.open-shape-layer</property>
+            <layout>
+              <property name="column">0</property>
+              <property name="row">5</property>
+            </layout>
           </object>
-          <packing>
-            <property name="left-attach">0</property>
-            <property name="top-attach">5</property>
-          </packing>
         </child>
       </object>
-    </child>
+    </property>
   </template>
 </interface>
diff --git a/data/ui/main-window.ui b/data/ui/main-window.ui
index a80a2985..e804f111 100644
--- a/data/ui/main-window.ui
+++ b/data/ui/main-window.ui
@@ -1,6 +1,6 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <interface>
-  <!-- interface-requires gtk+ 3.0 -->
+  <requires lib="gtk" version="4.0"/>
   <menu id="hamburgerMenu">
     <section>
       <item>
@@ -26,78 +26,47 @@
   <template class="Gjs_MainWindow" parent="GtkApplicationWindow">
     <property name="width-request">300</property>
     <property name="height-request">500</property>
-    <property name="window-position">center</property>
-    <property name="title" translatable="yes">Maps</property>
+    <property name="title" translatable="1">Maps</property>
     <child type="titlebar">
       <object class="GtkHeaderBar" id="headerBar">
-        <property name="visible">True</property>
-        <property name="can-focus">False</property>
-        <property name="show-close-button">True</property>
         <style>
           <class name="titlebar"/>
         </style>
-        <child>
+        <child type="end">
           <object class="GtkMenuButton">
-            <property name="visible">True</property>
             <property name="halign">end</property>
             <property name="valign">center</property>
             <property name="menu-model">hamburgerMenu</property>
-            <accelerator key="F10" signal="clicked"/>
-            <child internal-child="accessible">
-              <object class="AtkObject">
-                <property name="accessible-name" translatable="yes">Open main menu</property>
-              </object>
-            </child>
-            <style>
-              <class name="image-button"/>
-            </style>
-            <child>
-              <object class="GtkImage">
-                <property name="visible">True</property>
-                <property name="icon-size">1</property>
-                <property name="icon-name">open-menu-symbolic</property>
-              </object>
-            </child>
+            <property name="tooltip-text" translatable="1">Open main menu</property>
+            <property name="icon-name">open-menu-symbolic</property>
           </object>
-          <packing>
-            <property name="pack-type">end</property>
-          </packing>
         </child>
       </object>
     </child>
     <child>
       <object class="GtkGrid" id="grid">
-        <property name="visible">True</property>
-        <property name="can_focus">False</property>
         <child>
           <object class="GtkBox" id="placeBarContainer">
-            <property name="visible">True</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>
-          </packing>
         </child>
         <child>
           <object class="GtkRevealer" id="actionBarRevealer">
-            <property name="visible">True</property>
-            <property name="reveal-child">False</property>
             <property name="transition-type">slide-up</property>
-            <child>
-              <object class="GtkActionBar" id="actionBar">
-                <property name="visible">True</property>
-              </object>
-            </child>
+            <property name="child">
+              <object class="GtkActionBar" id="actionBar"/>
+            </property>
+            <layout>
+              <property name="column">0</property>
+              <property name="row">2</property>
+              <property name="column-span">2</property>
+            </layout>
           </object>
-          <packing>
-            <property name="left-attach">0</property>
-            <property name="top-attach">2</property>
-            <property name="width">2</property>
-          </packing>
         </child>
       </object>
     </child>
   </template>
 </interface>
-
diff --git a/data/ui/place-bar.ui b/data/ui/place-bar.ui
index 14f187bc..c61d822a 100644
--- a/data/ui/place-bar.ui
+++ b/data/ui/place-bar.ui
@@ -1,49 +1,34 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <interface>
+  <requires lib="gtk" version="4.0"/>
   <template class="Gjs_PlaceBar" parent="GtkRevealer">
-    <property name="visible">True</property>
     <property name="transition_type">slide-up</property>
-    <property name="reveal_child">False</property>
-    <child>
+    <property name="child">
       <object class="GtkActionBar" id="actionbar">
-        <property name="visible">True</property>
         <child>
-          <object class="GtkEventBox" id="eventbox">
-            <property name="visible">True</property>
+          <object class="GtkBox" id="box">
+            <property name="orientation">vertical</property>
+            <property name="spacing">6</property>
             <child>
-              <object class="GtkBox" id="box">
-                <property name="visible">True</property>
-                <property name="orientation">vertical</property>
+              <object class="GtkBox">
+                <property name="hexpand">1</property>
                 <property name="spacing">6</property>
                 <child>
-                  <object class="GtkBox">
-                    <property name="visible">True</property>
-                    <property name="orientation">horizontal</property>
-                    <property name="hexpand">True</property>
-                    <property name="spacing">6</property>
-                    <child>
-                      <object class="GtkLabel" id="title">
-                        <style>
-                          <class name="title-2"/>
-                        </style>
-                        <property name="visible">True</property>
-                        <property name="ellipsize">end</property>
-                      </object>
-                    </child>
+                  <object class="GtkLabel" id="title">
+                    <style>
+                      <class name="title-2"/>
+                    </style>
+                    <property name="ellipsize">end</property>
+                  </object>
+                </child>
+                <child>
+                  <object class="GtkButton" id="altSendToButton">
+                    <property name="visible">0</property>
                     <child>
-                      <object class="GtkButton" id="altSendToButton">
-                        <property name="visible">False</property>
-                        <child>
-                          <object class="GtkImage">
-                            <property name="visible">True</property>
-                            <property name="icon_name">send-to-symbolic</property>
-                            <property name="tooltip_text" translatable="yes" comments="Translators: This is 
a tooltip">Share location</property>
-                          </object>
-                        </child>
+                      <object class="GtkImage">
+                        <property name="icon_name">send-to-symbolic</property>
+                        <property name="tooltip_text" translatable="1" comments="Translators: This is a 
tooltip">Share location</property>
                       </object>
-                      <packing>
-                        <property name="pack_type">end</property>
-                      </packing>
                     </child>
                   </object>
                 </child>
@@ -52,8 +37,6 @@
           </object>
         </child>
       </object>
-    </child>
+    </property>
   </template>
 </interface>
-
-
diff --git a/data/ui/place-buttons.ui b/data/ui/place-buttons.ui
index bace12aa..4f9be1d1 100644
--- a/data/ui/place-buttons.ui
+++ b/data/ui/place-buttons.ui
@@ -1,37 +1,26 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <interface>
-  <requires lib="gtk+" version="3.12"/>
+  <requires lib="gtk" version="4.0"/>
   <template class="Gjs_PlaceButtons" parent="GtkBox">
-    <property name="visible">True</property>
-    <property name="can_focus">False</property>
-    <property name="orientation">horizontal</property>
     <property name="spacing">6</property>
     <child>
       <object class="GtkButton" id="routeButton">
         <property name="name">bubble-route-button</property>
-        <property name="visible">False</property>
-        <property name="can_focus">True</property>
-        <property name="receives_default">False</property>
-        <property name="tooltip-text" translatable="yes" comments="Translators: This is a tooltip">Add to 
new route</property>
+        <property name="visible">0</property>
+        <property name="focusable">1</property>
+        <property name="tooltip-text" translatable="1" comments="Translators: This is a tooltip">Add to new 
route</property>
         <child>
           <object class="GtkBox">
-            <property name="visible">True</property>
-            <property name="can_focus">False</property>
-            <property name="orientation">horizontal</property>
             <property name="spacing">6</property>
             <child>
               <object class="GtkImage">
-                <property name="visible">True</property>
-                <property name="can_focus">False</property>
                 <property name="icon-name">route-button-symbolic</property>
                 <property name="pixel_size">16</property>
               </object>
             </child>
             <child>
               <object class="GtkLabel">
-                <property name="visible">True</property>
-                <property name="can_focus">False</property>
-                <property name="label" translatable="yes" comments="Translators: This is the button to find 
a route to a place">Directions</property>
+                <property name="label" translatable="1" comments="Translators: This is the button to find a 
route to a place">Directions</property>
               </object>
             </child>
           </object>
@@ -44,61 +33,43 @@
     <child>
       <object class="GtkButton" id="sendToButton">
         <property name="name">bubble-send-to-button</property>
-        <property name="visible">True</property>
-        <property name="can_focus">True</property>
-        <property name="receives_default">False</property>
-        <property name="tooltip-text" translatable="yes" comments="Translators: This is a tooltip">Share 
location</property>
+        <property name="focusable">1</property>
+        <property name="tooltip-text" translatable="1" comments="Translators: This is a tooltip">Share 
location</property>
         <child>
           <object class="GtkImage" id="sendToButtonImage">
-            <property name="visible">True</property>
-            <property name="can_focus">False</property>
             <property name="icon-name">send-to-symbolic</property>
             <property name="pixel_size">16</property>
           </object>
         </child>
       </object>
-      <packing>
-        <property name="pack-type">end</property>
-      </packing>
     </child>
     <child>
       <object class="GtkButton" id="favoriteButton">
         <property name="name">bubble-favorite-button</property>
-        <property name="visible">False</property>
-        <property name="can_focus">True</property>
-        <property name="receives_default">False</property>
-        <property name="tooltip-text" translatable="yes" comments="Translators: This is a tooltip">Mark as 
favorite</property>
+        <property name="visible">0</property>
+        <property name="focusable">1</property>
+        <property name="tooltip-text" translatable="1" comments="Translators: This is a tooltip">Mark as 
favorite</property>
         <child>
           <object class="GtkImage" id="favoriteButtonImage">
             <property name="name">bubble-favorite-button-image</property>
-            <property name="visible">True</property>
-            <property name="can_focus">False</property>
             <property name="icon-name">starred-symbolic</property>
             <property name="pixel_size">16</property>
           </object>
         </child>
       </object>
-      <packing>
-        <property name="pack-type">end</property>
-      </packing>
     </child>
     <child>
       <object class="GtkButton" id="editButton">
-        <property name="visible">False</property>
-        <property name="can_focus">True</property>
-        <property name="tooltip-text" translatable="yes" comments="Translators: This is a tooltip">Edit on 
OpenStreetMap</property>
+        <property name="visible">0</property>
+        <property name="focusable">1</property>
+        <property name="tooltip-text" translatable="1" comments="Translators: This is a tooltip">Edit on 
OpenStreetMap</property>
         <child>
           <object class="GtkImage">
-            <property name="visible">True</property>
-            <property name="can_focus">False</property>
             <property name="icon-name">document-edit-symbolic</property>
             <property name="pixel_size">16</property>
           </object>
         </child>
       </object>
-      <packing>
-        <property name="pack-type">end</property>
-      </packing>
     </child>
   </template>
 </interface>
diff --git a/data/ui/place-list-row.ui b/data/ui/place-list-row.ui
index dc476583..af687608 100644
--- a/data/ui/place-list-row.ui
+++ b/data/ui/place-list-row.ui
@@ -1,81 +1,71 @@
 <?xml version="1.0" encoding="UTF-8"?>
-<!-- Generated with glade 3.18.3 -->
 <interface>
-  <requires lib="gtk+" version="3.12"/>
+  <requires lib="gtk" version="4.0"/>
   <template class="Gjs_PlaceListRow" parent="GtkListBoxRow">
-    <property name="visible">True</property>
-    <child>
+    <property name="child">
       <object class="GtkGrid" id="grid">
-        <property name="visible">True</property>
-        <property name="can_focus">False</property>
         <property name="orientation">vertical</property>
-        <property name="row-homogeneous">True</property>
-        <property name="margin">5</property>
+        <property name="row-homogeneous">1</property>
+        <property name="margin-start">5</property>
+        <property name="margin-end">5</property>
+        <property name="margin-top">5</property>
+        <property name="margin-bottom">5</property>
         <child>
           <object class="GtkImage" id="icon">
-            <property name="visible">True</property>
-            <property name="can_focus">False</property>
             <property name="pixel_size">32</property>
             <property name="margin_end">12</property>
+            <layout>
+              <property name="column">0</property>
+              <property name="row">0</property>
+              <property name="row-span">2</property>
+            </layout>
           </object>
-          <packing>
-            <property name="left_attach">0</property>
-            <property name="top_attach">0</property>
-            <property name="height">2</property>
-          </packing>
         </child>
         <child>
           <object class="GtkImage" id="typeIcon">
-            <property name="visible">True</property>
-            <property name="can_focus">False</property>
             <property name="valign">center</property>
             <property name="halign">end</property>
-            <property name="hexpand">True</property>
+            <property name="hexpand">1</property>
             <property name="margin_start">10</property>
             <property name="pixel_size">16</property>
+            <layout>
+              <property name="column">2</property>
+              <property name="row">0</property>
+              <property name="row-span">2</property>
+            </layout>
           </object>
-          <packing>
-            <property name="left_attach">2</property>
-            <property name="top_attach">0</property>
-            <property name="height">2</property>
-          </packing>
         </child>
         <child>
           <object class="GtkLabel" id="name">
-            <property name="visible">True</property>
-            <property name="can_focus">False</property>
             <property name="halign">start</property>
             <property name="valign">end</property>
-            <property name="hexpand">True</property>
-            <property name="use_markup">True</property>
+            <property name="hexpand">1</property>
+            <property name="use_markup">1</property>
             <property name="ellipsize">end</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="GtkLabel" id="details">
-            <property name="visible">True</property>
-            <property name="can_focus">False</property>
             <property name="halign">start</property>
             <property name="valign">start</property>
-            <property name="hexpand">True</property>
-            <property name="use_markup">True</property>
+            <property name="hexpand">1</property>
+            <property name="use_markup">1</property>
             <property name="ellipsize">end</property>
             <style>
               <class name="subtitle"/>
               <class name="dim-label"/>
             </style>
+            <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>
       </object>
-    </child>
+    </property>
   </template>
 </interface>
-
diff --git a/data/ui/place-popover.ui b/data/ui/place-popover.ui
index 4ec01987..f4fed434 100644
--- a/data/ui/place-popover.ui
+++ b/data/ui/place-popover.ui
@@ -1,47 +1,36 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <interface>
-  <!-- interface-requires gtk+ 3.10 -->
+  <requires lib="gtk" version="4.0"/>
   <template class="Gjs_PlacePopover" parent="Gjs_SearchPopover">
     <property name="visible">False</property>
     <property name="hexpand">False</property>
-    <property name="modal">False</property>
     <property name="height-request">320</property>
     <style>
       <class name="maps-popover"/>
     </style>
     <child>
       <object class="GtkGrid" id="mainGrid">
-        <property name="visible">True</property>
-        <property name="can_focus">True</property>
+        <property name="focusable">1</property>
         <property name="orientation">vertical</property>
         <child>
           <object class="GtkStack" id="stack">
-            <property name="visible">True</property>
-            <property name="can_focus">False</property>
             <property name="transition-type">crossfade</property>
             <style>
               <class name="maps-stack"/>
             </style>
             <child>
               <object class="GtkScrolledWindow" id="scrolledWindow">
-                <property name="visible">True</property>
-                <property name="can_focus">False</property>
                 <property name="hscrollbar_policy">never</property>
-                <property name="shadow_type">in</property>
-                <child>
+                <property name="child">
                   <object class="GtkListBox" id="list">
-                    <property name="visible">True</property>
-                    <property name="can_focus">False</property>
-                    <property name="expand">True</property>
-                    <property name="activate_on_single_click">True</property>
+                    <property name="hexpand">1</property>
+                    <property name="vexpand">1</property>
                   </object>
-                </child>
+                </property>
               </object>
             </child>
             <child>
               <object class="GtkSpinner" id="spinner">
-                <property name="visible">True</property>
-                <property name="can_focus">False</property>
                 <property name="halign">center</property>
                 <property name="valign">center</property>
                 <property name="width_request">16</property>
@@ -50,9 +39,7 @@
             </child>
             <child>
               <object class="GtkLabel" id="noResultsLabel">
-                <property name="visible">True</property>
-                <property name="can_focus">False</property>
-                <property name="label" translatable="yes">No results found</property>
+                <property name="label" translatable="1">No results found</property>
                 <property name="width_request">16</property>
                 <property name="height_request">16</property>
                 <style>
@@ -62,9 +49,7 @@
             </child>
             <child>
               <object class="GtkLabel" id="errorLabel">
-                <property name="visible">True</property>
-                <property name="can_focus">False</property>
-                <property name="label" translatable="yes">An error has occurred</property>
+                <property name="label" translatable="1">An error has occurred</property>
                 <property name="width_request">16</property>
                 <property name="height_request">16</property>
                 <style>
diff --git a/data/ui/route-entry.ui b/data/ui/route-entry.ui
index b186e453..02d36954 100644
--- a/data/ui/route-entry.ui
+++ b/data/ui/route-entry.ui
@@ -1,48 +1,32 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <interface>
-  <requires lib="gtk+" version="3.10"/>
+  <requires lib="gtk" version="4.0"/>
   <template class="Gjs_RouteEntry" parent="GtkGrid">
-    <property name="visible">True</property>
-    <property name="orientation">horizontal</property>
-    <property name="hexpand">False</property>
+    <property name="hexpand">0</property>
     <child>
-      <object class="GtkEventBox" id="iconEventBox">
-        <property name="visible">True</property>
-        <property name="can_focus">False</property>
-        <child>
-          <object class="GtkImage" id="icon">
-            <property name="visible">True</property>
-            <property name="can_focus">False</property>
-            <property name="margin-end">8</property>
-            <property name="margin-start">13</property>
-            <property name="width-request">16</property>
-            <property name="icon-name">maps-point-end-symbolic</property>
-           <property name="tooltip-text" translatable="yes" comments="Translators: This is a tooltip">Drag 
to change order of the route</property>
-          </object>
-        </child>
+      <object class="GtkImage" id="icon">
+        <property name="margin-end">8</property>
+        <property name="margin-start">13</property>
+        <property name="width-request">16</property>
+        <property name="icon-name">maps-point-end-symbolic</property>
+        <property name="tooltip-text" translatable="1" comments="Translators: This is a tooltip">Drag to 
change order of the route</property>
       </object>
     </child>
     <child>
       <object class="GtkGrid" id="entryGrid">
-        <property name="visible">True</property>
-        <property name="can_focus">False</property>
         <property name="width_request">230</property>
-        <property name="hexpand">False</property>
+        <property name="hexpand">0</property>
       </object>
     </child>
     <child>
       <object class="GtkButton" id="button">
-        <property name="visible">True</property>
-        <property name="no_show_all">True</property>
-        <property name="can-focus">True</property>
+        <property name="focusable">1</property>
         <property name="valign">center</property>
         <property name="height-request">31</property>
         <property name="margin-start">4</property>
         <property name="margin-end">10</property>
         <child>
-          <object class="GtkImage" id="buttonImage">
-            <property name="visible">True</property>
-          </object>
+          <object class="GtkImage" id="buttonImage"/>
         </child>
       </object>
     </child>
diff --git a/data/ui/sidebar.ui b/data/ui/sidebar.ui
index ab7b3890..79771e64 100644
--- a/data/ui/sidebar.ui
+++ b/data/ui/sidebar.ui
@@ -1,46 +1,33 @@
 <?xml version="1.0" encoding="UTF-8"?>
-<!-- Generated with glade 3.18.3 -->
 <interface>
-  <requires lib="gtk+" version="3.10"/>
+  <requires lib="gtk" version="4.0"/>
   <template class="Gjs_Sidebar" parent="GtkRevealer">
-    <property name="visible">True</property>
     <property name="transition_type">slide-left</property>
     <property name="transition_duration">400</property>
     <property name="halign">end</property>
-    <property name="valign">fill</property>
     <style>
       <class name="maps-sidebar"/>
     </style>
-    <child>
+    <property name="child">
       <object class="GtkGrid" id="sidebar">
         <property name="name">sidebar</property>
-        <property name="visible">True</property>
-        <property name="can_focus">False</property>
-        <property name="vexpand">True</property>
-        <property name="valign">fill</property>
-        <property name="column_homogeneous">True</property>
+        <property name="vexpand">1</property>
+        <property name="column_homogeneous">1</property>
         <property name="orientation">vertical</property>
         <property name="width_request">320</property>
         <property name="row_spacing">2</property>
         <child>
           <object class="GtkBox" id="mode-chooser">
-            <property name="visible">True</property>
-            <property name="can_focus">False</property>
             <property name="halign">center</property>
             <property name="margin-top">10</property>
             <child>
-              <object class="GtkRadioButton" id="modePedestrianToggle">
+              <object class="GtkToggleButton" id="modePedestrianToggle">
                 <property name="name">mode-pedestrian-toggle</property>
-                <property name="visible">True</property>
-                <property name="can_focus">True</property>
-                <property name="receives_default">True</property>
-                <property name="draw_indicator">False</property>
+                <property name="receives_default">1</property>
                 <property name="height-request">32</property>
                 <property name="width-request">42</property>
                 <child>
                   <object class="GtkImage" id="mode-pedestrian-image">
-                    <property name="visible">True</property>
-                    <property name="can_focus">False</property>
                     <property name="icon-name">route-pedestrian-symbolic</property>
                   </object>
                 </child>
@@ -50,19 +37,14 @@
               </object>
             </child>
             <child>
-              <object class="GtkRadioButton" id="modeBikeToggle">
+              <object class="GtkToggleButton" id="modeBikeToggle">
                 <property name="name">mode-bike-toggle</property>
-                <property name="visible">True</property>
-                <property name="can_focus">True</property>
-                <property name="receives_default">True</property>
-                <property name="draw_indicator">False</property>
+                <property name="receives_default">1</property>
                 <property name="group">modePedestrianToggle</property>
                 <property name="height-request">32</property>
                 <property name="width-request">42</property>
                 <child>
                   <object class="GtkImage" id="mode-bike-image">
-                    <property name="visible">True</property>
-                    <property name="can_focus">False</property>
                     <property name="icon-name">route-bike-symbolic</property>
                   </object>
                 </child>
@@ -72,20 +54,15 @@
               </object>
             </child>
             <child>
-              <object class="GtkRadioButton" id="modeCarToggle">
+              <object class="GtkToggleButton" id="modeCarToggle">
                 <property name="name">mode-car-toggle</property>
-                <property name="visible">True</property>
-                <property name="can_focus">True</property>
-                <property name="receives_default">True</property>
-                <property name="active">True</property>
-                <property name="draw_indicator">False</property>
+                <property name="receives_default">1</property>
+                <property name="active">1</property>
                 <property name="group">modeBikeToggle</property>
                 <property name="height-request">32</property>
                 <property name="width-request">42</property>
                 <child>
                   <object class="GtkImage" id="mode-car-image">
-                    <property name="visible">True</property>
-                    <property name="can_focus">False</property>
                     <property name="icon-name">route-car-symbolic</property>
                   </object>
                 </child>
@@ -95,19 +72,14 @@
               </object>
             </child>
             <child>
-              <object class="GtkRadioButton" id="modeTransitToggle">
+              <object class="GtkToggleButton" id="modeTransitToggle">
                 <property name="name">mode-transit-toggle</property>
-                <property name="visible">True</property>
-                <property name="can_focus">True</property>
-                <property name="receives_default">True</property>
-                <property name="draw_indicator">False</property>
+                <property name="receives_default">1</property>
                 <property name="group">modeCarToggle</property>
                 <property name="height-request">32</property>
                 <property name="width-request">42</property>
                 <child>
                   <object class="GtkImage" id="mode-transit-image">
-                    <property name="visible">False</property>
-                    <property name="can_focus">False</property>
                     <property name="icon-name">route-transit-symbolic</property>
                   </object>
                 </child>
@@ -124,36 +96,27 @@
         <child>
           <object class="GtkListBox" id="entryList">
             <property name="name">sidebar-entry-list</property>
-            <property name="visible">True</property>
-            <property name="can_focus">False</property>
-            <property name="selection-mode">GTK_SELECTION_NONE</property>
+            <property name="selection-mode">none</property>
           </object>
         </child>
         <child>
           <object class="GtkGrid" id="sidebar-route-info-wrapper">
-            <property name="visible">True</property>
-            <property name="hexpand">False</property>
+            <property name="hexpand">0</property>
             <child>
               <object class="GtkGrid" id="sidebar-route-info">
                 <property name="name">sidebar-route-info</property>
-                <property name="visible">True</property>
-                <property name="can_focus">False</property>
                 <property name="margin_start">17</property>
                 <property name="margin_end">17</property>
                 <property name="margin_top">12</property>
-                <property name="hexpand">true</property>
+                <property name="hexpand">1</property>
                 <child>
                   <object class="GtkLabel" id="timeInfo">
-                    <property name="visible">True</property>
-                    <property name="can_focus">False</property>
                     <property name="halign">start</property>
-                    <property name="hexpand">true</property>
+                    <property name="hexpand">1</property>
                   </object>
                 </child>
                 <child>
                   <object class="GtkLabel" id="distanceInfo">
-                    <property name="visible">True</property>
-                    <property name="can_focus">False</property>
                     <property name="margin_start">10</property>
                   </object>
                 </child>
@@ -163,205 +126,185 @@
         </child>
         <child>
           <object class="GtkGrid">
-            <property name="visible">True</property>
-            <property name="valign">fill</property>
-            <property name="vexpand">True</property>
-            <property name="hexpand_set">True</property>
+            <property name="vexpand">1</property>
+            <property name="hexpand_set">1</property>
             <style>
               <class name="frame"/>
             </style>
             <child>
               <object class="GtkRevealer" id="transitRevealer">
-                <child>
+                <property name="visible">0</property>
+                <property name="child">
                   <object class="GtkStack" id="transitHeader">
-                    <property name="visible">True</property>
-                    <property name="transition-type">GTK_STACK_TRANSITION_TYPE_SLIDE_LEFT_RIGHT</property>
+                    <property name="transition-type">slide-left-right</property>
                   </object>
-                </child>
+                </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="GtkStack" id="instructionStack">
-                <property name="visible">True</property>
-                <property name="can_focus">False</property>
                 <child>
                   <object class="GtkScrolledWindow" id="instructionWindow">
                     <property name="name">instruction-window</property>
-                    <property name="visible">True</property>
-                    <property name="can_focus">False</property>
-                    <property name="valign">fill</property>
-                    <property name="vexpand">True</property>
-                    <property name="margin">1</property>
+                    <property name="vexpand">1</property>
+                    <property name="margin-start">1</property>
+                    <property name="margin-end">1</property>
+                    <property name="margin-top">1</property>
+                    <property name="margin-bottom">1</property>
                     <property name="hscrollbar_policy">never</property>
-                    <child>
+                    <property name="child">
                       <object class="GtkListBox" id="instructionList">
                         <property name="name">instruction-list</property>
-                        <property name="visible">True</property>
-                        <property name="can_focus">False</property>
-                        <property name="valign">fill</property>
-                        <property name="hexpand">True</property>
+                        <property name="hexpand">1</property>
                       </object>
-                    </child>
+                    </property>
                   </object>
                 </child>
                 <child>
                   <object class="GtkScrolledWindow" id="transitWindow">
-                    <property name="visible">True</property>
-                    <property name="can_focus">False</property>
-                    <property name="valign">fill</property>
-                    <property name="vexpand">True</property>
-                    <property name="margin">1</property>
+                    <property name="vexpand">1</property>
+                    <property name="margin-start">1</property>
+                    <property name="margin-end">1</property>
+                    <property name="margin-top">1</property>
+                    <property name="margin-bottom">1</property>
                     <property name="hscrollbar_policy">never</property>
-                    <child>
+                    <property name="child">
                       <object class="GtkStack" id="transitListStack">
-                        <property name="visible">True</property>
-                        <property 
name="transition-type">GTK_STACK_TRANSITION_TYPE_SLIDE_LEFT_RIGHT</property>
-                        <property name="vhomogeneous">False</property>
+                        <property name="transition-type">slide-left-right</property>
+                        <property name="vhomogeneous">0</property>
                         <child>
-                          <object class="GtkListBox" id="transitOverviewListBox">
-                            <property name="visible">True</property>
-                            <property name="can-focus">False</property>
-                          </object>
-                          <packing>
+                          <object class="GtkStackPage">
                             <property name="name">overview</property>
-                          </packing>
+                            <property name="child">
+                              <object class="GtkListBox" id="transitOverviewListBox"/>
+                            </property>
+                          </object>
                         </child>
                         <child>
-                          <object class="GtkListBox" id="transitItineraryListBox">
-                            <property name="visible">True</property>
-                            <property name="can-focus">False</property>
-                            <property name="selection-mode">GTK_SELECTION_NONE</property>
-                          </object>
-                          <packing>
+                          <object class="GtkStackPage">
                             <property name="name">itinerary</property>
-                          </packing>
+                            <property name="child">
+                              <object class="GtkListBox" id="transitItineraryListBox">
+                                <property name="selection-mode">none</property>
+                              </object>
+                            </property>
+                          </object>
                         </child>
                       </object>
-                    </child>
+                    </property>
                   </object>
                 </child>
                 <child>
                   <object class="GtkSpinner" id="instructionSpinner">
-                    <property name="visible">True</property>
-                    <property name="can_focus">False</property>
-                    <property name="active">True</property>
+                    <property name="spinning">True</property>
                   </object>
                 </child>
                 <child>
                   <object class="GtkLabel" id="errorLabel">
-                    <property name="visible">True</property>
-                    <property name="can_focus">False</property>
                     <style>
                       <class name="dim-label"/>
                     </style>
                   </object>
                 </child>
+                <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>
           </object>
         </child>
         <child>
           <object class="GtkStack" id="linkButtonStack">
+            <property name="visible">0</property>
             <child>
-              <object class="GtkLinkButton">
-                <property name="label" translatable="yes">Route search by GraphHopper</property>
-                <property name="visible">True</property>
-                <property name="can_focus">True</property>
-                <property name="receives_default">True</property>
-                <property name="use_action_appearance">False</property>
-                <property name="relief">none</property>
-                <property name="uri">https://graphhopper.com</property>
-                <style>
-                  <class name="small-label"/>
-                </style>
-              </object>
-              <packing>
+              <object class="GtkStackPage">
                 <property name="name">turnByTurn</property>
-              </packing>
-            </child>
-            <child>
-              <object class="GtkGrid">
-                <property name="visible">True</property>
-                <property name="halign">GTK_ALIGN_END</property>
-                <child>
-                  <object class="GtkLabel" id="transitAttributionLabel">
-                    <property name="visible">True</property>
-                    <property name="can_focus">True</property>
-                    <property name="receives_default">True</property>
-                    <property name="use_markup">True</property>
+                <property name="child">
+                  <object class="GtkLinkButton">
+                    <property name="label" translatable="1">Route search by GraphHopper</property>
+                    <property name="focusable">1</property>
+                    <property name="receives_default">1</property>
+                    <property name="uri">https://graphhopper.com</property>
                     <style>
                       <class name="small-label"/>
                     </style>
                   </object>
-                  <packing>
-                    <property name="left-attach">0</property>
-                    <property name="top-attach">0</property>
-                  </packing>
-                </child>
-                <child>
-                  <object class="GtkMenuButton">
-                    <property name="visible">True</property>
-                    <property name="popover">transitDisclaimerPopover</property>
-                    <property name="halign">GTK_ALIGN_END</property>
-                    <property name="margin-top">5</property>
-                    <property name="margin-bottom">5</property>
-                    <property name="margin-end">5</property>
-                    <property name="margin-start">5</property>
-                    <style>
-                      <class name="flat"/>
-                    </style>
+                </property>
+              </object>
+            </child>
+            <child>
+              <object class="GtkStackPage">
+                <property name="name">transit</property>
+                <property name="child">
+                  <object class="GtkGrid">
+                    <property name="halign">end</property>
                     <child>
-                      <object class="GtkGrid">
-                        <property name="visible">True</property>
-                        <property name="valign">GTK_ALIGN_CENTER</property>
+                      <object class="GtkLabel" id="transitAttributionLabel">
+                        <property name="focusable">1</property>
+                        <property name="receives_default">1</property>
+                        <property name="use_markup">1</property>
+                        <style>
+                          <class name="small-label"/>
+                        </style>
+                        <layout>
+                          <property name="column">0</property>
+                          <property name="row">0</property>
+                        </layout>
+                      </object>
+                    </child>
+                    <child>
+                      <object class="GtkMenuButton">
+                        <property name="popover">transitDisclaimerPopover</property>
+                        <property name="halign">end</property>
+                        <property name="margin-top">5</property>
+                        <property name="margin-bottom">5</property>
+                        <property name="margin-end">5</property>
+                        <property name="margin-start">5</property>
+                        <style>
+                          <class name="flat"/>
+                        </style>
                         <child>
-                          <object class="GtkImage">
-                            <property name="visible">True</property>
-                            <property name="can_focus">False</property>
-                            <property name="halign">GTK_ALIGN_CENTER</property>
-                            <property name="hexpand">False</property>
-                            <property name="icon-name">dialog-information-symbolic</property>
+                          <object class="GtkGrid">
+                            <property name="valign">center</property>
+                            <child>
+                              <object class="GtkImage">
+                                <property name="halign">center</property>
+                                <property name="hexpand">0</property>
+                                <property name="icon-name">dialog-information-symbolic</property>
+                              </object>
+                            </child>
                           </object>
                         </child>
+                        <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>
-                  </packing>
-                </child>
+                </property>
               </object>
-              <packing>
-                <property name="name">transit</property>
-              </packing>
             </child>
           </object>
         </child>
       </object>
-    </child>
+    </property>
   </template>
   <object class="GtkPopover" id="transitDisclaimerPopover">
-    <property name="visible">False</property>
-    <child>
+    <property name="child">
       <object class="GtkGrid">
-        <property name="visible">True</property>
         <child>
           <object class="GtkLabel">
-            <property name="visible">True</property>
             <property name="margin-top">5</property>
             <property name="margin-bottom">5</property>
             <property name="margin-start">5</property>
             <property name="margin-end">5</property>
-            <property name="label" translatable="yes">Routing itineraries for public transit is provided by 
third-party
+            <property name="label" translatable="1">Routing itineraries for public transit is provided by 
third-party
 services.
 GNOME can not guarantee correctness of the itineraries and schedules shown.
 Note that some providers might not include all available modes of transportation,
@@ -371,74 +314,66 @@ Names and brands shown are to be considered as registered trademarks when applic
           </object>
         </child>
       </object>
-    </child>
+    </property>
   </object>
   <object class="GtkGrid" id="transitItineraryHeader">
-    <property name="visible">True</property>
-    <property name="can-focus">False</property>
     <style>
       <class name="shaded"/>
     </style>
     <child>
       <object class="GtkButton" id="transitItineraryBackButton">
-        <property name="visible">True</property>
         <property name="margin-start">6</property>
         <property name="margin-end">6</property>
         <property name="margin-top">4</property>
         <property name="margin-bottom">4</property>
-        <property name="halign">GTK_ALIGN_START</property>
+        <property name="halign">start</property>
         <child>
           <object class="GtkGrid">
-            <property name="visible">True</property>
-            <property name="valign">GTK_ALIGN_CENTER</property>
+            <property name="valign">center</property>
             <child>
               <object class="GtkImage">
-                <property name="visible">True</property>
-                <property name="can-focus">False</property>
-                <property name="hexpand">False</property>
+                <property name="hexpand">0</property>
                 <property name="icon-name">go-previous-symbolic</property>
               </object>
             </child>
           </object>
         </child>
+        <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="GtkLabel" id="transitItineraryTimeLabel">
-        <property name="visible">True</property>
         <property name="margin-start">6</property>
         <property name="margin-end">6</property>
         <property name="margin-top">4</property>
         <property name="margin-bottom">4</property>
-        <property name="hexpand">False</property>
-        <property name="halign">GTK_ALIGN_START</property>
+        <property name="hexpand">0</property>
+        <property name="halign">start</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="GtkLabel" id="transitItineraryDurationLabel">
-        <property name="visible">True</property>
         <property name="margin-start">6</property>
         <property name="margin-end">6</property>
         <property name="margin-top">4</property>
         <property name="margin-bottom">4</property>
-        <property name="hexpand">True</property>
-        <property name="halign">GTK_ALIGN_START</property>
+        <property name="hexpand">1</property>
+        <property name="halign">start</property>
         <style>
           <class name="dim-label"/>
         </style>
+        <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>
   </object>
 </interface>
diff --git a/data/ui/transit-options-panel.ui b/data/ui/transit-options-panel.ui
index a11acfa0..4c7024be 100644
--- a/data/ui/transit-options-panel.ui
+++ b/data/ui/transit-options-panel.ui
@@ -1,14 +1,12 @@
+<?xml version="1.0" encoding="UTF-8"?>
 <interface>
+  <requires lib="gtk" version="4.0"/>
   <template class="Gjs_TransitOptionsPanel" parent="GtkGrid">
-    <property name="visible">True</property>
-    <property name="can_focus">False</property>
-    <property name="no-show-all">True</property>
     <style>
       <class name="shaded"/>
     </style>
     <child>
       <object class="GtkComboBoxText" id="transitTimeOptionsComboBox">
-        <property name="visible">True</property>
         <property name="active_id">leaveNow</property>
         <property name="margin_start">6</property>
         <property name="margin_end">6</property>
@@ -19,91 +17,84 @@
           <item translatable="yes" id="leaveBy" comments="Indicates searching for itineraries leaving at the 
specified time at the earliest">Leave By</item>
           <item translatable="yes" id="arriveBy" comments="Indicates searching for itineraries arriving no 
later than the specified time">Arrive By</item>
         </items>
+        <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="GtkEntry" id="transitTimeEntry">
-        <property name="visible">False</property>
+        <property name="visible">0</property>
         <property name="width_chars">5</property>
         <property name="margin_start">3</property>
         <property name="margin_end">3</property>
         <property name="margin_top">4</property>
         <property name="margin_bottom">4</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="GtkMenuButton" id="transitDateButton">
-        <property name="visible">False</property>
+        <property name="visible">0</property>
         <property name="popover">transitDatePopover</property>
         <property name="margin_start">3</property>
         <property name="margin_end">3</property>
         <property name="margin_top">4</property>
         <property name="margin_bottom">4</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="GtkMenuButton" id="transitParametersMenuButton">
-        <property name="visible">True</property>
         <property name="popover">transitParametersPopover</property>
-        <property name="halign">GTK_ALIGN_END</property>
+        <property name="halign">end</property>
         <property name="margin_start">3</property>
         <property name="margin_end">6</property>
         <property name="margin_top">4</property>
         <property name="margin_bottom">4</property>
         <child>
           <object class="GtkGrid">
-            <property name="visible">True</property>
-            <property name="valign">GTK_ALIGN_CENTER</property>
+            <property name="valign">center</property>
             <child>
               <object class="GtkImage">
-                <property name="visible">True</property>
-                <property name="can_focus">False</property>
-                <property name="halign">GTK_ALIGN_CENTER</property>
-                <property name="hexpand">True</property>
+                <property name="halign">center</property>
+                <property name="hexpand">1</property>
                 <property name="icon-name">view-more-symbolic</property>
               </object>
             </child>
           </object>
         </child>
+        <layout>
+          <property name="column">3</property>
+          <property name="row">0</property>
+        </layout>
       </object>
-      <packing>
-        <property name="left_attach">3</property>
-        <property name="top_attach">0</property>
-      </packing>
     </child>
   </template>
   <object class="GtkPopover" id="transitDatePopover">
-    <property name="visible">False</property>
-    <child>
-      <object class="GtkCalendar" id="transitDateCalendar">
-        <property name="visible">True</property>
-      </object>
-    </child>
+    <property name="child">
+      <object class="GtkCalendar" id="transitDateCalendar"/>
+    </property>
   </object>
   <object class="GtkPopover" id="transitParametersPopover">
-    <property name="visible">False</property>
-    <child>
+    <property name="child">
       <object class="GtkGrid">
-        <property name="visible">True</property>
-        <property name="margin">6</property>
-        <property name="orientation">GTK_ORIENTATION_VERTICAL</property>
+        <property name="margin-start">6</property>
+        <property name="margin-end">6</property>
+        <property name="margin-top">6</property>
+        <property name="margin-bottom">6</property>
+        <property name="orientation">vertical</property>
         <child>
           <object class="GtkLabel">
-            <property name="visible">True</property>
-            <property name="halign">GTK_ALIGN_START</property>
-            <property name="label" translatable="yes" comments="Header indicating selected modes of 
transit">Show</property>
+            <property name="halign">start</property>
+            <property name="label" translatable="1" comments="Header indicating selected modes of 
transit">Show</property>
             <property name="margin_start">6</property>
             <style>
               <class name="dim-label"/>
@@ -112,48 +103,41 @@
         </child>
         <child>
           <object class="GtkCheckButton" id="busCheckButton">
-            <property name="visible">True</property>
-            <property name="active">True</property>
-            <property name="label" translatable="yes">Buses</property>
+            <property name="active">1</property>
+            <property name="label" translatable="1">Buses</property>
           </object>
         </child>
         <child>
           <object class="GtkCheckButton" id="tramCheckButton">
-            <property name="visible">True</property>
-            <property name="active">True</property>
-            <property name="label" translatable="yes">Trams</property>
+            <property name="active">1</property>
+            <property name="label" translatable="1">Trams</property>
           </object>
         </child>
         <child>
           <object class="GtkCheckButton" id="trainCheckButton">
-            <property name="visible">True</property>
-            <property name="active">True</property>
-            <property name="label" translatable="yes">Trains</property>
+            <property name="active">1</property>
+            <property name="label" translatable="1">Trains</property>
           </object>
         </child>
         <child>
           <object class="GtkCheckButton" id="subwayCheckButton">
-            <property name="visible">True</property>
-            <property name="active">True</property>
-            <property name="label" translatable="yes">Subway</property>
+            <property name="active">1</property>
+            <property name="label" translatable="1">Subway</property>
           </object>
         </child>
         <child>
           <object class="GtkCheckButton" id="ferryCheckButton">
-            <property name="visible">True</property>
-            <property name="active">True</property>
-            <property name="label" translatable="yes">Ferries</property>
+            <property name="active">1</property>
+            <property name="label" translatable="1">Ferries</property>
           </object>
         </child>
         <child>
           <object class="GtkCheckButton" id="airplaneCheckButton">
-            <property name="visible">True</property>
-            <property name="active">True</property>
-            <property name="label" translatable="yes">Airplanes</property>
+            <property name="active">1</property>
+            <property name="label" translatable="1">Airplanes</property>
           </object>
         </child>
       </object>
-    </child>
+    </property>
   </object>
 </interface>
-  
diff --git a/lib/maps-file-tile-source.c b/lib/maps-file-tile-source.c
index 61e5f060..fdabbd60 100644
--- a/lib/maps-file-tile-source.c
+++ b/lib/maps-file-tile-source.c
@@ -17,7 +17,7 @@
  * Author: Jonas Danielsson <jonas threetimestwo org>
  */
 
-#include <champlain/champlain.h>
+#include <shumate/shumate.h>
 #include <gio/gio.h>
 #include <glib.h>
 #include <glib-object.h>
@@ -41,8 +41,10 @@ enum {
   PROP_PATH,
   PROP_MAX_ZOOM,
   PROP_MIN_ZOOM,
-  PROP_WORLD
-
+  PROP_WORLD_LEFT,
+  PROP_WORLD_TOP,
+  PROP_WORLD_RIGHT,
+  PROP_WORLD_BOTTOM
 };
 
 struct _MapsFileTileSourcePrivate
@@ -51,7 +53,10 @@ struct _MapsFileTileSourcePrivate
   gchar *extension;
   gint max_zoom;
   gint min_zoom;
-  ChamplainBoundingBox *world;
+  double world_left;
+  double world_top;
+  double world_right;
+  double world_bottom;
 
   long min_x;
   long min_y;
@@ -59,17 +64,7 @@ struct _MapsFileTileSourcePrivate
   long max_y;
 };
 
-typedef struct
-{
-  ChamplainMapSource *map_source;
-  ChamplainTile *tile;
-} CallbackData;
-
-G_DEFINE_TYPE_WITH_PRIVATE (MapsFileTileSource, maps_file_tile_source, CHAMPLAIN_TYPE_TILE_SOURCE)
-
-static void fill_tile (ChamplainMapSource *map_source,
-    ChamplainTile *tile);
-
+G_DEFINE_TYPE_WITH_PRIVATE (MapsFileTileSource, maps_file_tile_source, SHUMATE_TYPE_MAP_SOURCE)
 
 static void
 maps_file_tile_source_set_property (GObject      *object,
@@ -112,8 +107,20 @@ maps_file_tile_source_get_property (GObject *object,
       g_value_set_uint (value, tile_source->priv->max_zoom);
       break;
 
-    case PROP_WORLD:
-      g_value_set_boxed (value, tile_source->priv->world);
+    case PROP_WORLD_LEFT:
+      g_value_set_double (value, tile_source->priv->world_left);
+      break;
+
+    case PROP_WORLD_TOP:
+      g_value_set_double (value, tile_source->priv->world_top);
+      break;
+
+    case PROP_WORLD_RIGHT:
+      g_value_set_double (value, tile_source->priv->world_right);
+      break;
+
+    case PROP_WORLD_BOTTOM:
+      g_value_set_double (value, tile_source->priv->world_bottom);
       break;
 
     default:
@@ -141,26 +148,22 @@ maps_file_tile_source_finalize (GObject *object)
   G_OBJECT_CLASS (maps_file_tile_source_parent_class)->finalize (object);
 }
 
-static guint
-get_max_zoom_level (ChamplainMapSource *source)
-{
-  MapsFileTileSource *tile_source = (MapsFileTileSource *) source;
-
-  return tile_source->priv->max_zoom;
-}
-
-static guint
-get_min_zoom_level (ChamplainMapSource *source)
-{
-  MapsFileTileSource *tile_source = (MapsFileTileSource *) source;
+static void
+fill_tile_async (ShumateMapSource     *source,
+                 ShumateTile          *tile,
+                 GCancellable         *cancellable,
+                 GAsyncReadyCallback   callback,
+                 gpointer              user_data);
 
-  return tile_source->priv->min_zoom;
-}
+static gboolean
+fill_tile_finish (ShumateMapSource *map_source,
+                  GAsyncResult *result,
+                  GError **error);
 
 static void
 maps_file_tile_source_class_init (MapsFileTileSourceClass *klass)
 {
-  ChamplainMapSourceClass *map_source_class = CHAMPLAIN_MAP_SOURCE_CLASS (klass);
+  ShumateMapSourceClass *map_source_class = SHUMATE_MAP_SOURCE_CLASS (klass);
   GObjectClass *object_class = G_OBJECT_CLASS (klass);
   GParamSpec *pspec;
 
@@ -168,9 +171,8 @@ maps_file_tile_source_class_init (MapsFileTileSourceClass *klass)
   object_class->dispose = maps_file_tile_source_dispose;
   object_class->get_property = maps_file_tile_source_get_property;
   object_class->set_property = maps_file_tile_source_set_property;
-  map_source_class->get_max_zoom_level = get_max_zoom_level;
-  map_source_class->get_min_zoom_level = get_min_zoom_level;
-  map_source_class->fill_tile = fill_tile;
+  map_source_class->fill_tile_async = fill_tile_async;
+  map_source_class->fill_tile_finish = fill_tile_finish;
 
   /**
    * MapsFileTileSource:path:
@@ -216,19 +218,59 @@ maps_file_tile_source_class_init (MapsFileTileSourceClass *klass)
   g_object_class_install_property (object_class, PROP_MAX_ZOOM, pspec);
 
   /**
-   * MapsFileTileSource:world:
+   * MapsFileTileSource:world_left:
    *
-   * Set a bounding box to limit the world to. No tiles will be loaded
-   * outside of this bounding box. It will not be possible to scroll outside
-   * of this bounding box.
+   * The leftmost of the world bounding box.
    *
    */
-  pspec = g_param_spec_boxed ("world",
-                              "The world",
-                              "The bounding box to limit the #ChamplainView to",
-                              CHAMPLAIN_TYPE_BOUNDING_BOX,
-                              G_PARAM_READABLE);
-  g_object_class_install_property (object_class, PROP_WORLD, pspec);
+  pspec = g_param_spec_double ("worl-left",
+                               "Boundingbox left",
+                               "The leftmost coordinate of the world boundingbox",
+                               -180,
+                               180,
+                               -180,
+                               G_PARAM_READABLE);
+
+  /**
+   * MapsFileTileSource:world_top:
+   *
+   * The top of the world bounding box.
+   *
+   */
+  pspec = g_param_spec_double ("worl-top",
+                               "Boundingbox top",
+                               "The topmost coordinate of the world boundingbox",
+                               -90,
+                               90,
+                               90,
+                               G_PARAM_READABLE);
+  /**
+   * MapsFileTileSource:world_right:
+   *
+   * The rightmost of the world bounding box.
+   *
+   */
+  pspec = g_param_spec_double ("worl-right",
+                               "Boundingbox right",
+                               "The rightmost coordinate of the world boundingbox",
+                               -180,
+                               180,
+                               180,
+                               G_PARAM_READABLE);
+
+  /**
+   * MapsFileTileSource:world_bottom:
+   *
+   * The bottommost of the world bounding box.
+   *
+   */
+  pspec = g_param_spec_double ("worl-bottom",
+                               "Boundingbox bottom",
+                               "The bottommost coordinate of the world boundingbox",
+                               -90,
+                               90,
+                               -90,
+                               G_PARAM_READABLE);
 }
 
 static void
@@ -239,11 +281,14 @@ maps_file_tile_source_init (MapsFileTileSource *tile_source)
   tile_source->priv->extension = NULL;
   tile_source->priv->max_zoom = -1;
   tile_source->priv->min_zoom = 21;
-  tile_source->priv->world = NULL;
   tile_source->priv->min_x = G_MAXLONG;
   tile_source->priv->min_y = G_MAXLONG;
   tile_source->priv->max_x = 0;
   tile_source->priv->max_y = 0;
+  tile_source->priv->world_left = -180;
+  tile_source->priv->world_top = 90;
+  tile_source->priv->world_right = 180;
+  tile_source->priv->world_bottom = -90;
 }
 
 static gboolean
@@ -474,9 +519,9 @@ maps_file_tile_source_prepare (MapsFileTileSource *tile_source,
   g_return_val_if_fail (MAPS_IS_FILE_TILE_SOURCE (tile_source), FALSE);
   g_return_val_if_fail (tile_source->priv->path != NULL, FALSE);
 
-  ChamplainMapSource *source = (ChamplainMapSource *) tile_source;
+  ShumateMapSource *source = (ShumateMapSource *) tile_source;
   gboolean ret = TRUE;
-  guint tile_size = champlain_map_source_get_tile_size (source);
+  guint tile_size = shumate_map_source_get_tile_size (source);
 
   if (!get_zoom_levels (tile_source, error)) {
     ret = FALSE;
@@ -488,20 +533,19 @@ maps_file_tile_source_prepare (MapsFileTileSource *tile_source,
     goto out;
   }
 
-  tile_source->priv->world = champlain_bounding_box_new ();
-  tile_source->priv->world->left = champlain_map_source_get_longitude (source,
+  tile_source->priv->world_left = shumate_map_source_get_longitude (source,
                                                     tile_source->priv->min_zoom,
                                                     tile_source->priv->min_x *
                                                     tile_size);
-  tile_source->priv->world->right = champlain_map_source_get_longitude (source,
+  tile_source->priv->world_right = shumate_map_source_get_longitude (source,
                                                      tile_source->priv->min_zoom,
                                                      (tile_source->priv->max_x + 1) *
                                                      tile_size);
-  tile_source->priv->world->top = champlain_map_source_get_latitude (source,
+  tile_source->priv->world_top = shumate_map_source_get_latitude (source,
                                                      tile_source->priv->min_zoom,
                                                      tile_source->priv->min_y *
                                                      tile_size);
-  tile_source->priv->world->bottom = champlain_map_source_get_latitude (source,
+  tile_source->priv->world_bottom = shumate_map_source_get_latitude (source,
                                                   tile_source->priv->min_zoom,
                                                   (tile_source->priv->max_y + 1) *
                                                   tile_size);
@@ -510,131 +554,56 @@ maps_file_tile_source_prepare (MapsFileTileSource *tile_source,
 }
 
 static void
-tile_rendered_cb (ChamplainTile    *tile,
-                  gpointer          data,
-                  guint             size,
-                  gboolean          error,
-                  CallbackData     *user_data)
-{
-  ChamplainMapSource *map_source = user_data->map_source;
-  ChamplainMapSource *next_source;
-
-  g_signal_handlers_disconnect_by_func (tile, tile_rendered_cb, user_data);
-  g_slice_free (CallbackData, user_data);
-
-  next_source = champlain_map_source_get_next_source (map_source);
-
-  if (!error)
-    {
-      ChamplainTileSource *tile_source = CHAMPLAIN_TILE_SOURCE (map_source);
-      ChamplainTileCache *tile_cache = champlain_tile_source_get_cache (tile_source);
-
-      if (tile_cache && data)
-        champlain_tile_cache_store_tile (tile_cache, tile, data, size);
-
-      champlain_tile_set_fade_in (tile, TRUE);
-      champlain_tile_set_state (tile, CHAMPLAIN_STATE_DONE);
-      champlain_tile_display_content (tile);
-    }
-  else if (next_source)
-    champlain_map_source_fill_tile (next_source, tile);
-
-  g_object_unref (map_source);
-  g_object_unref (tile);
-}
-
-static void
-tile_loaded_cb (GFile        *file,
-                GAsyncResult *res,
-                CallbackData *user_data)
+fill_tile_async (ShumateMapSource     *source,
+                 ShumateTile          *tile,
+                 GCancellable         *cancellable,
+                 GAsyncReadyCallback   callback,
+                 gpointer              user_data)
 {
-  ChamplainMapSource *map_source = user_data->map_source;
-  ChamplainMapSource *next_source = champlain_map_source_get_next_source (map_source);
-  ChamplainTile *tile = user_data->tile;
-  CallbackData *data;
-  ChamplainRenderer *renderer;
-  guint8 *content;
-  gsize length;
-
-  g_slice_free (CallbackData, user_data);
-
-  if (!g_file_load_contents_finish (file, res, (char **) &content, &length, NULL, NULL))
-    {
-      goto load_next;
-    }
-
-  renderer = champlain_map_source_get_renderer (map_source);
-  g_return_if_fail (CHAMPLAIN_IS_RENDERER (renderer));
-
-  data = g_slice_new (CallbackData);
-  data->map_source = map_source;
-
-  g_signal_connect (tile, "render-complete", G_CALLBACK (tile_rendered_cb), data);
-
-  champlain_renderer_set_data (renderer, content, length);
-  champlain_renderer_render (renderer, tile);
-
-  return;
-
-load_next:
-  if (next_source)
-    champlain_map_source_fill_tile (next_source, tile);
-
-  goto cleanup;
+  g_return_if_fail (MAPS_IS_FILE_TILE_SOURCE (source));
 
-  champlain_tile_set_fade_in (tile, TRUE);
-  champlain_tile_set_state (tile, CHAMPLAIN_STATE_DONE);
-  champlain_tile_display_content (tile);
-
-cleanup:
-  g_object_unref (tile);
-  g_object_unref (map_source);
-}
-
-static void
-fill_tile (ChamplainMapSource *map_source,
-           ChamplainTile      *tile)
-{
-  g_return_if_fail (MAPS_IS_FILE_TILE_SOURCE (map_source));
-  g_return_if_fail (CHAMPLAIN_IS_TILE (tile));
-
-  MapsFileTileSource *tile_source = MAPS_FILE_TILE_SOURCE (map_source);
-  CallbackData *callback_data;
+  MapsFileTileSource *tile_source = MAPS_FILE_TILE_SOURCE (source);
   GFile *file;
   gchar *path = NULL;
-
-  if (champlain_tile_get_state (tile) == CHAMPLAIN_STATE_DONE)
-    return;
+  g_autoptr(GTask) task = NULL;
 
   path = g_strdup_printf("%s/%d/%d/%d.%s",
                          tile_source->priv->path,
-                         champlain_tile_get_zoom_level (tile),
-                         champlain_tile_get_x (tile),
-                         champlain_tile_get_y (tile),
+                         shumate_tile_get_zoom_level (tile),
+                         shumate_tile_get_x (tile),
+                         shumate_tile_get_y (tile),
                          tile_source->priv->extension);
   file = g_file_new_for_path (path);
 
+  task = g_task_new (source, cancellable, callback, user_data);
+  g_task_set_source_tag (task, fill_tile_async);
+
   if (g_file_query_exists(file, NULL))
     {
-      callback_data = g_slice_new (CallbackData);
-      callback_data->tile = tile;
-      callback_data->map_source = map_source;
-
-      g_object_ref (map_source);
-      g_object_ref (tile);
-
       g_file_load_contents_async (file, NULL,
-                                  (GAsyncReadyCallback) tile_loaded_cb,
-                                  callback_data);
+                                  callback,
+                                  user_data);
+
+      g_task_return_boolean (task, TRUE);
     }
   else
     {
-      ChamplainMapSource *next_source = champlain_map_source_get_next_source (map_source);
-
-      if (CHAMPLAIN_IS_MAP_SOURCE (next_source))
-        champlain_map_source_fill_tile (next_source, tile);
+      g_task_return_boolean (task, FALSE);
     }
 
   g_object_unref (file);
   g_free (path);
 }
+
+static gboolean
+fill_tile_finish (ShumateMapSource *map_source,
+                                        GAsyncResult *result,
+                                        GError **error)
+{
+  MapsFileTileSource *self = (MapsFileTileSource *) map_source;
+
+  g_return_val_if_fail (MAPS_IS_FILE_TILE_SOURCE (self), FALSE);
+  g_return_val_if_fail (g_task_is_valid (result, self), FALSE);
+
+  return g_task_propagate_boolean (G_TASK (result), error);
+}
diff --git a/lib/maps-file-tile-source.h b/lib/maps-file-tile-source.h
index 9b432e04..f7c88e15 100644
--- a/lib/maps-file-tile-source.h
+++ b/lib/maps-file-tile-source.h
@@ -20,7 +20,7 @@
 #ifndef _MAPS_FILE_TILE_SOURCE_H_
 #define _MAPS_FILE_TILE_SOURCE_H_
 
-#include <champlain/champlain.h>
+#include <shumate/shumate.h>
 
 G_BEGIN_DECLS
 
@@ -55,19 +55,19 @@ typedef struct _MapsFileTileSourceClass MapsFileTileSourceClass;
  */
 struct _MapsFileTileSource
 {
-  ChamplainTileSource parent_instance;
+  ShumateMapSource parent_instance;
 
   MapsFileTileSourcePrivate *priv;
 };
 
 struct _MapsFileTileSourceClass
 {
-  ChamplainTileSourceClass parent_class;
+  ShumateMapSourceClass parent_class;
 };
 
 GType maps_file_tile_source_get_type (void);
 
-gboolean maps_file_tile_source_prepare (MapsFileTileSource *tile_source, GError **error);
+gboolean maps_file_tile_source_prepare (MapsFileTileSource *data_source, GError **error);
 G_END_DECLS
 
 #endif /* _MAPS_FILE_TILE_SOURCE_H_ */
diff --git a/lib/maps-sync-map-source.c b/lib/maps-sync-map-source.c
new file mode 100644
index 00000000..5955f6c6
--- /dev/null
+++ b/lib/maps-sync-map-source.c
@@ -0,0 +1,96 @@
+/*
+ * Copyright (c) 2022 Marcus Lundblad
+ *
+ * GNOME Maps is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * GNOME Maps is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with GNOME Maps; if not, see <http://www.gnu.org/licenses/>.
+ *
+ * Author: Marcus Lundblad <ml dfupdate se>
+ */
+
+#include <shumate/shumate.h>
+#include <glib.h>
+#include <glib-object.h>
+#include <stdlib.h>
+
+#include "maps-sync-map-source.h"
+
+/*
+struct _MapsFileTileSourcePrivate
+{
+};
+*/
+
+G_DEFINE_ABSTRACT_TYPE (MapsSyncMapSource, maps_sync_map_source, SHUMATE_TYPE_MAP_SOURCE)
+
+static void
+fill_tile_async (ShumateMapSource     *source,
+                 ShumateTile          *tile,
+                 GCancellable         *cancellable,
+                 GAsyncReadyCallback   callback,
+                 gpointer              user_data);
+
+static gboolean
+fill_tile_finish (ShumateMapSource *map_source,
+                  GAsyncResult *result,
+                  GError **error);
+
+static void
+maps_sync_map_source_class_init (MapsSyncMapSourceClass *klass)
+{
+  ShumateMapSourceClass *map_source_class = SHUMATE_MAP_SOURCE_CLASS (klass);
+
+  map_source_class->fill_tile_async = fill_tile_async;
+  map_source_class->fill_tile_finish = fill_tile_finish;
+
+  klass->fill_tile = NULL;
+}
+
+static void
+maps_sync_map_source_init (MapsSyncMapSource *map_source)
+{
+  map_source->priv = maps_sync_map_source_get_instance_private (map_source);
+}
+
+static void
+fill_tile_async (ShumateMapSource     *source,
+                 ShumateTile          *tile,
+                 GCancellable         *cancellable,
+                 GAsyncReadyCallback   callback,
+                 gpointer              user_data)
+{
+  MapsSyncMapSource *self = (MapsSyncMapSource *) source;
+  g_return_if_fail (MAPS_IS_SYNC_MAP_SOURCE (source));
+
+  g_autoptr(GTask) task = NULL;
+
+  MAPS_SYNC_MAP_SOURCE_CLASS (self)->fill_tile (self, tile);
+
+  task = g_task_new (source, cancellable, callback, user_data);
+  g_task_set_source_tag (task, fill_tile_async);
+
+
+  g_task_return_boolean (task, TRUE);
+}
+
+static gboolean
+fill_tile_finish (ShumateMapSource *map_source,
+                                        GAsyncResult *result,
+                                        GError **error)
+{
+  MapsSyncMapSource *self = (MapsSyncMapSource *) map_source;
+
+  g_return_val_if_fail (MAPS_IS_SYNC_MAP_SOURCE (self), FALSE);
+  g_return_val_if_fail (g_task_is_valid (result, self), FALSE);
+
+  return g_task_propagate_boolean (G_TASK (result), error);
+}
diff --git a/lib/maps-sync-map-source.h b/lib/maps-sync-map-source.h
new file mode 100644
index 00000000..443ed39d
--- /dev/null
+++ b/lib/maps-sync-map-source.h
@@ -0,0 +1,73 @@
+/*
+ * Copyright (c) 2022 Marcus Lundblad
+ *
+ * GNOME Maps is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * GNOME Maps is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with GNOME Maps; if not, see <http://www.gnu.org/licenses/>.
+ *
+ * Author: Marcus Lundblad <ml dfupdate se>
+ */
+
+#ifndef _MAPS_SYNC_MAP_SOURCE_H_
+#define _MAPS_SYNC_MAP_SOURCE_H_
+
+#include <shumate/shumate.h>
+
+G_BEGIN_DECLS
+
+#define MAPS_TYPE_SYNC_MAP_SOURCE maps_sync_map_source_get_type ()
+
+#define MAPS_SYNC_MAP_SOURCE(obj) \
+  (G_TYPE_CHECK_INSTANCE_CAST ((obj), MAPS_TYPE_SYNC_MAP_SOURCE, MapsSyncMapSource))
+
+#define MAPS_SYNC_MAP_SOURCE_CLASS(klass) \
+  (G_TYPE_CHECK_CLASS_CAST ((klass), MAPS_TYPE_SYNC_MAP_SOURCE, MapsSyncMapSourceClass))
+
+#define MAPS_IS_SYNC_MAP_SOURCE(obj) \
+  (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MAPS_TYPE_SYNC_MAP_SOURCE))
+
+#define MAPS_IS_SYNC_MAP_SOURCE_CLASS(klass) \
+  (G_TYPE_CHECK_CLASS_TYPE ((klass), MAPS_TYPE_SYNC_MAP_SOURCE))
+
+#define MAPS_SYNC_MAP_SOURCE_GET_CLASS(obj) \
+  (G_TYPE_INSTANCE_GET_CLASS ((obj), MAPS_TYPE_SYNC_MAP_SOURCE, MapsSyncMapSourceClass))
+
+typedef struct _MapsSyncMapSourcePrivate MapsSyncMapSourcePrivate;
+
+typedef struct _MapsSyncMapSource MapsSyncMapSource;
+typedef struct _MapsSyncMapSourceClass MapsSyncMapSourceClass;
+
+/**
+ * MapsSyncMapSource:
+ *
+ * The #MapsSyncMapSource structure contains only private data
+ * and should be accessed using the provided API
+ *
+ */
+struct _MapsSyncMapSource
+{
+  ShumateMapSource parent_instance;
+
+  MapsSyncMapSourcePrivate *priv;
+};
+
+struct _MapsSyncMapSourceClass
+{
+  ShumateMapSourceClass parent_class;
+
+  void (*fill_tile)  (MapsSyncMapSource     *self,
+                      ShumateTile              *tile);
+};
+
+GType maps_sync_map_source_get_type (void);
+
+#endif /* _MAPS_SYNC_MAP_SOURCE_H_ */
diff --git a/lib/meson.build b/lib/meson.build
index 8de63691..ba6251a9 100644
--- a/lib/meson.build
+++ b/lib/meson.build
@@ -6,7 +6,8 @@ headers_private = files(
        'maps-osm-object.h',
        'maps-osm-way.h',
        'maps-osm-relation.h',
-       'maps-osm-oauth-proxy-call.h'
+       'maps-osm-oauth-proxy-call.h',
+       'maps-sync-map-source.h'
 )
 
 sources = files(
@@ -17,7 +18,8 @@ sources = files(
        'maps-osm-object.c',
        'maps-osm-way.c',
        'maps-osm-relation.c',
-       'maps-osm-oauth-proxy-call.c'
+       'maps-osm-oauth-proxy-call.c',
+       'maps-sync-map-source.c'
 )
 
 cflags = [
@@ -47,8 +49,8 @@ gnome.generate_gir(
        includes: [
                'GLib-2.0',
                'GObject-2.0',
-               'Champlain-0.12',
-               'Rest-0.7'
+               'Rest-0.7',
+               'Shumate-1.0'
        ],
        install: true,
        install_dir_gir: join_paths(pkgdatadir, 'gir-' + maps_gir_version),
diff --git a/meson.build b/meson.build
index a9e018f3..2380f1c8 100644
--- a/meson.build
+++ b/meson.build
@@ -34,13 +34,13 @@ glib = dependency('glib-2.0', version: '>= 2.66.0')
 gio = dependency('gio-2.0', version: '>= 2.44.0')
 gjs = dependency('gjs-1.0', version: '>= 1.69.2')
 girepository = dependency('gobject-introspection-1.0', version: '>= 0.10.1')
-gtk3 = dependency('gtk+-3.0', version: '>= 3.22.0')
+gtk4 = dependency('gtk4')
 geoclue2 = dependency('geoclue-2.0', version: '>= 0.12.99')
-handy = dependency('libhandy-1', version: '>= 1.5.0')
+libadwaita = dependency('libadwaita-1')
 gweather = dependency('gweather4', version: '>= 3.90.0')
 
 libmaps_deps = [
-       dependency('champlain-0.12', version: '>= 0.12.14'),
+       dependency('shumate-1.0'),
        dependency('libxml-2.0'),
        dependency('rest-0.7', version: '>= 0.7.90')
 ]
diff --git a/org.gnome.Maps.json b/org.gnome.Maps.json
index bb2e1196..be095983 100644
--- a/org.gnome.Maps.json
+++ b/org.gnome.Maps.json
@@ -90,51 +90,18 @@
             ]
         },
         {
-            "name": "cogl",
-            "config-opts": [
-                "--disable-cogl-gst",
-                "--enable-xlib-egl-platform",
-                "--enable-wayland-egl-platform"
-            ],
-            "sources": [
-                {
-                    "type": "archive",
-                    "url": "https://download.gnome.org/sources/cogl/1.22/cogl-1.22.8.tar.xz";,
-                    "sha256": "a805b2b019184710ff53d0496f9f0ce6dcca420c141a0f4f6fcc02131581d759"
-                }
-            ]
-        },
-        {
-            "name": "clutter",
+            "name" : "libshumate",
+            "buildsystem": "meson",
             "config-opts": [
-                "--enable-egl-backend",
-                "--enable-wayland-backend"
+                "-Ddemos=false",
+                "-Dgtk_doc=false",
+                "-Dvapi=false"
             ],
-            "sources": [
-                {
-                    "type": "archive",
-                    "url": "https://download.gnome.org/sources/clutter/1.26/clutter-1.26.4.tar.xz";,
-                    "sha256": "8b48fac159843f556d0a6be3dbfc6b083fc6d9c58a20a49a6b4919ab4263c4e6"
-                }
-            ]
-        },
-        {
-            "name": "clutter-gtk",
-            "sources": [
-                {
-                    "type": "archive",
-                    "url": "https://download.gnome.org/sources/clutter-gtk/1.8/clutter-gtk-1.8.4.tar.xz";,
-                    "sha256": "521493ec038973c77edcb8bc5eac23eed41645117894aaee7300b2487cb42b06"
-                }
-            ]
-        },
-        {
-            "name" : "libchamplain",
-            "buildsystem": "meson",
             "sources" : [
                 {
                     "type" : "git",
-                    "url" : "https://gitlab.gnome.org/GNOME/libchamplain.git";
+                    "url" : "https://gitlab.gnome.org/GNOME/libshumate.git";,
+                    "branch" : "main"
                 }
             ]
         },
diff --git a/src/application.js b/src/application.js
index e45a2d93..f502dc22 100644
--- a/src/application.js
+++ b/src/application.js
@@ -20,13 +20,13 @@
  *         Zeeshan Ali (Khattak) <zeeshanak gnome org>
  */
 
+import Gdk from 'gi://Gdk';
 import GLib from 'gi://GLib';
 import GObject from 'gi://GObject';
 import Geocode from 'gi://GeocodeGlib';
 import Gio from 'gi://Gio';
 import Gtk from 'gi://Gtk';
-import GtkClutter from 'gi://GtkClutter';
-import Hdy from 'gi://Handy';
+import Adw from 'gi://Adw';
 
 import {Geoclue} from './geoclue.js';
 import * as GeocodeFactory from './geocode.js';
@@ -140,9 +140,6 @@ export class Application extends Gtk.Application {
     vfunc_startup() {
         super.vfunc_startup();
 
-        GtkClutter.init(null);
-        Hdy.init();
-
         Utils.loadStyleSheet(Gio.file_new_for_uri('resource:///org/gnome/Maps/application.css'));
 
         Application.application = this;
@@ -163,11 +160,13 @@ export class Application extends Gtk.Application {
         }, Application.settings);
 
 
-        this._styleManager = Hdy.StyleManager.get_default();
-        this._styleManager.set_color_scheme(Hdy.ColorScheme.PREFER_LIGHT);
+        this._styleManager = Adw.StyleManager.get_default();
+        this._styleManager.set_color_scheme(Adw.ColorScheme.PREFER_LIGHT);
+
+        let display = Gdk.Display.get_default();
 
-        Gtk.IconTheme.get_default().append_search_path(GLib.build_filenamev([pkg.pkgdatadir,
-                                                                             'icons']));
+        Gtk.IconTheme.get_for_display(display).add_search_path(
+            GLib.build_filenamev([pkg.pkgdatadir, 'icons']));
         this._initPlaceStore();
     }
 
diff --git a/src/contextMenu.js b/src/contextMenu.js
index 0b71b08e..e4d406ea 100644
--- a/src/contextMenu.js
+++ b/src/contextMenu.js
@@ -36,7 +36,7 @@ import {RouteQuery} from './routeQuery.js';
 import * as Utils from './utils.js';
 import {ZoomInDialog} from './zoomInDialog.js';
 
-export class ContextMenu extends Gtk.Menu {
+export class ContextMenu extends Gtk.PopoverMenu {
     constructor(params) {
         let mapView = params.mapView;
         delete params.mapView;
diff --git a/src/epaf.js b/src/epaf.js
index 8235e8f5..93254328 100644
--- a/src/epaf.js
+++ b/src/epaf.js
@@ -23,7 +23,7 @@
 // Google encoded polyline decoder
 // https://developers.google.com/maps/documentation/utilities/polylinealgorithm
 
-import Champlain from 'gi://Champlain';
+import Shumate from 'gi://Shumate';
 
 function _decodeValue(data, index) {
     let b;
@@ -61,7 +61,7 @@ export function decode(data) {
         // first value is absolute, rest are relative to previous value
         lat += latdelta;
         lon += londelta;
-        polyline.push(new Champlain.Coordinate({
+        polyline.push(new Shumate.Coordinate({
             latitude:  lat * 1e-5,
             longitude: lon * 1e-5
         }));
diff --git a/src/favoritesPopover.js b/src/favoritesPopover.js
index 45d8fe15..ca2453f0 100644
--- a/src/favoritesPopover.js
+++ b/src/favoritesPopover.js
@@ -33,7 +33,6 @@ export class FavoritesPopover extends Gtk.Popover {
         let mapView = params.mapView;
         delete params.mapView;
 
-        params.transitions_enabled = false;
         super(params);
 
         this._mapView = mapView;
@@ -88,7 +87,9 @@ export class FavoritesPopover extends Gtk.Popover {
     }
 
     _updateList() {
-        this._list.forall((row) => row.destroy());
+        for (let row of this._list) {
+            row.destroy();
+        }
 
         let rows = 0;
         this._model.foreach((model, path, iter) => {
diff --git a/src/geoJSONSource.js b/src/geoJSONSource.js
index f0b6e5ef..ef998a38 100644
--- a/src/geoJSONSource.js
+++ b/src/geoJSONSource.js
@@ -19,10 +19,11 @@
  */
 
 import Cairo from 'cairo';
-import Champlain from 'gi://Champlain';
-import Clutter from 'gi://Clutter';
 import GLib from 'gi://GLib';
 import GObject from 'gi://GObject';
+import Shumate from 'gi://Shumate';
+
+import GnomeMaps from 'gi://GnomeMaps';
 
 import {BoundingBox} from './boundingBox.js';
 import * as Geojsonvt from './geojsonvt/geojsonvt.js';
@@ -38,7 +39,7 @@ const TileFeature = { POINT: 1,
                       LINESTRING: 2,
                       POLYGON: 3 };
 
-export class GeoJSONSource extends Champlain.TileSource {
+export class GeoJSONSource extends GnomeMaps.SyncMapSource {
 
     constructor(params) {
         super();
@@ -46,46 +47,21 @@ export class GeoJSONSource extends Champlain.TileSource {
         this._mapView = params.mapView;
         this._markerLayer = params.markerLayer;
         this._bbox = new BoundingBox();
-        this._tileSize = Service.getService().tiles.street.tile_size;
+        this.tile_size = Service.getService().tiles.street.tile_size;
+        this.max_zoom_level = 20;
+        this.min_zoom_level = 0;
     }
 
     get bbox() {
         return this._bbox;
     }
 
-    vfunc_get_tile_size() {
-        return this._tileSize;
-    }
-
-    vfunc_get_max_zoom_level() {
-        return 20;
-    }
-
-    vfunc_get_min_zoom_level() {
-        return 0;
-    }
-
-    vfunc_get_id() {
-        return 'GeoJSONSource';
-    }
-
-    vfunc_get_name() {
-        return 'GeoJSONSource';
-    }
-
     vfunc_fill_tile(tile) {
-        if (tile.get_state() === Champlain.State.DONE)
+        if (tile.get_state() === Shumate.State.DONE)
             return;
 
-        tile.connect('render-complete', (tile, data, size, error) => {
-            if(!error) {
-                tile.set_state(Champlain.State.DONE);
-                tile.display_content();
-            } else if(this.next_source)
-                this.next_source.fill_tile(tile);
-        });
-
-        GLib.idle_add(tile, () => this._renderTile(tile));
+        tile.set_state(Shumate.State.DONE);
+        this._renderTile(tile);
     }
 
     _validate([lon, lat]) {
@@ -206,11 +182,12 @@ export class GeoJSONSource extends Champlain.TileSource {
 
     parse(json) {
         this._parseInternal(json);
-        this._tileIndex = Geojsonvt.geojsonvt(json, { extent: this._tileSize,
-                                                      maxZoom: 20 });
+        this._tileIndex = Geojsonvt.geojsonvt(json, { extent: this.tile_size,
+                                                      maxZoom: this.max_zoom_level });
         this._clampBBox();
     }
 
+    // TODO: render to a GdkTexture and set it on the tile
     _renderTile(tile) {
         let tileJSON = this._tileIndex.getTile(tile.zoom_level, tile.x, tile.y);
         let content = new Clutter.Canvas({ width: this._tileSize,
diff --git a/src/graphHopperTransit.js b/src/graphHopperTransit.js
index e60aff80..76d6411b 100644
--- a/src/graphHopperTransit.js
+++ b/src/graphHopperTransit.js
@@ -25,7 +25,7 @@
  * routing for walking legs
  */
 
-import Champlain from 'gi://Champlain';
+import Shumate from 'gi://Shumate';
 
 import {Application} from './application.js';
 import {Location} from './location.js';
@@ -64,10 +64,10 @@ export function createWalkingLeg(from, to, fromName, toName, route) {
 
 // create a straight-line "as the crow flies" polyline between two places
 function createStraightPolyline(fromLoc, toLoc) {
-    return [new Champlain.Coordinate({ latitude: fromLoc.latitude,
-                                       longitude: fromLoc.longitude }),
-            new Champlain.Coordinate({ latitude: toLoc.latitude,
-                                       longitude: toLoc.longitude })];
+    return [new Shumate.Coordinate({ latitude: fromLoc.latitude,
+                                     longitude: fromLoc.longitude }),
+            new Shumate.Coordinate({ latitude: toLoc.latitude,
+                                     longitude: toLoc.longitude })];
 }
 
 var _walkingRoutes = [];
diff --git a/src/layersPopover.js b/src/layersPopover.js
index 9297b152..f6c8ca9d 100644
--- a/src/layersPopover.js
+++ b/src/layersPopover.js
@@ -17,11 +17,11 @@
  * Author: Dario Di Nucci <linkin88mail gmail com>
  */
 
-import Champlain from 'gi://Champlain';
+import Adw from 'gi://Adw';
 import GObject from 'gi://GObject';
 import Gtk from 'gi://Gtk';
 import Gdk from 'gi://Gdk';
-import Handy from 'gi://Handy';
+import Shumate from 'gi://Shumate';
 
 import {Application} from './application.js';
 import * as MapSource from './mapSource.js';
@@ -67,13 +67,11 @@ export class LayersPopover extends Gtk.Popover {
 
     constructor(params) {
         super({ width_request: 200,
-                no_show_all: true,
-                transitions_enabled: false,
                 visible: false });
 
         this._mapView = params.mapView;
 
-        this._aerialLayerButton.join_group(this._streetLayerButton);
+        //this._aerialLayerButton.join_group(this._streetLayerButton);
 
         this.get_style_context().add_class('maps-popover');
 
@@ -88,6 +86,8 @@ export class LayersPopover extends Gtk.Popover {
             row.set_header(header);
         });
 
+        // for now let's disable the map type swithery, as we only have street
+        /*
         this._layerPreviews = {
             street: {
                 source: MapSource.createStreetSource(),
@@ -133,8 +133,10 @@ export class LayersPopover extends Gtk.Popover {
         this._mapView.connect("map-type-changed", (_mapView, type) => {
             this.setMapType(type);
         });
+        */
     }
 
+    /*
     _setLayerPreviews() {
         this._setLayerPreviewImage('street');
         this._setLayerPreviewImage('aerial');
@@ -193,6 +195,7 @@ export class LayersPopover extends Gtk.Popover {
             this._aerialLayerButton.active = true;
         }
     }
+    */
 
     _onRemoveClicked(row) {
         this._mapView.removeShapeLayer(row.shapeLayer);
@@ -211,10 +214,10 @@ export class LayersPopover extends Gtk.Popover {
 
 GObject.registerClass({
     Template: 'resource:///org/gnome/Maps/ui/layers-popover.ui',
-    InternalChildren: [ 'streetLayerButton',
+    InternalChildren: [ /*'streetLayerButton',
                         'aerialLayerButton',
                         'streetLayerImage',
-                        'aerialLayerImage',
+                        'aerialLayerImage',*/
                         'layersListBox',
                         'loadLayerButton' ]
 }, LayersPopover);
diff --git a/src/main.js b/src/main.js
index d46faa94..b7f0b87b 100644
--- a/src/main.js
+++ b/src/main.js
@@ -20,21 +20,17 @@
  *         Zeeshan Ali (Khattak) <zeeshanak gnome org>
  */
 
-import 'gi://Champlain?version=0.12';
-import 'gi://Clutter?version=1.0';
-import 'gi://Cogl?version=1.0';
+import 'gi://Adw?version=1';
 import 'gi://GeocodeGlib?version=1.0';
-import 'gi://Gdk?version=3.0';
+import 'gi://Gdk?version=4.0';
 import 'gi://GdkPixbuf?version=2.0';
 import 'gi://Gio?version=2.0';
 import 'gi://GLib?version=2.0';
 import 'gi://GObject?version=2.0';
-import 'gi://Gtk?version=3.0';
-import 'gi://GtkChamplain?version=0.12';
-import 'gi://GtkClutter?version=1.0';
+import 'gi://Gtk?version=4.0';
 import 'gi://GWeather?version=4.0';
-import 'gi://Handy?version=1';
 import 'gi://Rest?version=0.7';
+import 'gi://Shumate?version=1.0';
 import 'gi://Soup?version=2.4';
 
 import * as system from 'system';
diff --git a/src/mainWindow.js b/src/mainWindow.js
index 6429dbc1..84e6165f 100644
--- a/src/mainWindow.js
+++ b/src/mainWindow.js
@@ -22,12 +22,12 @@
 
 import gettext from 'gettext';
 
-import Champlain from 'gi://Champlain';
 import GLib from 'gi://GLib';
 import GObject from 'gi://GObject';
 import Gdk from 'gi://Gdk';
 import Gio from 'gi://Gio';
 import Gtk from 'gi://Gtk';
+import Shumate from 'gi://Shumate';
 
 import {Application} from './application.js';
 import {ContextMenu} from './contextMenu.js';
@@ -98,7 +98,9 @@ export class MainWindow extends Gtk.ApplicationWindow {
         this._mapView = new MapView({
             mapType: this.application.local_tile_path ?
                 MapView.MapType.LOCAL : undefined,
-            mainWindow: this });
+            mainWindow: this,
+            hexpand: true,
+            vexpand: true });
 
         this._grid.attach(this._mapView, 0, 0, 1, 1);
 
@@ -106,8 +108,9 @@ export class MainWindow extends Gtk.ApplicationWindow {
 
         this._sidebar = this._createSidebar();
 
-        this._contextMenu = new ContextMenu({ mapView: this._mapView,
-                                              mainWindow: this });
+        // TODO: fix the context menu (convert to GtkPopoverMenu with menu model
+        //this._contextMenu = new ContextMenu({ mapView: this._mapView,
+        //                                      mainWindow: this });
 
         if (pkg.name.endsWith('.Devel'))
             this.get_style_context().add_class('devel');
@@ -121,7 +124,7 @@ export class MainWindow extends Gtk.ApplicationWindow {
 
         this._grid.attach(this._sidebar, 1, 0, 1, 2);
 
-        this._grid.show_all();
+        //this._grid.show_all();
 
         /* for some reason, setting the title of the window through the .ui
          * template does not work anymore (maybe has something to do with
@@ -138,7 +141,6 @@ export class MainWindow extends Gtk.ApplicationWindow {
                                           margin_start: _PLACE_ENTRY_MARGIN,
                                           margin_end: _PLACE_ENTRY_MARGIN,
                                           max_width_chars: 50,
-                                          loupe: true,
                                           matchRoute: true });
         placeEntry.connect('notify::place', () => {
             if (placeEntry.place) {
@@ -148,7 +150,10 @@ export class MainWindow extends Gtk.ApplicationWindow {
 
         let popover = placeEntry.popover;
         popover.connect('selected', () => this._mapView.grab_focus());
-        this._mapView.view.connect('button-press-event', () => popover.hide());
+
+        this._buttonPressGesture = new Gtk.GestureSingle();
+        this._mapView.add_controller(this._buttonPressGesture);
+        this._buttonPressGesture.connect('begin', () => popover.popdown());
         return placeEntry;
     }
 
@@ -162,7 +167,7 @@ export class MainWindow extends Gtk.ApplicationWindow {
 
     _initPlaceBar() {
         this._placeBar = new PlaceBar({ mapView: this._mapView, visible: true });
-        this._placeBarContainer.add(this._placeBar);
+        this._placeBarContainer.append(this._placeBar);
 
         this.application.bind_property('selected-place',
                                        this._placeBar, 'place',
@@ -170,20 +175,11 @@ export class MainWindow extends Gtk.ApplicationWindow {
     }
 
     _initDND() {
-        this.drag_dest_set(Gtk.DestDefaults.DROP, null, 0);
-        this.drag_dest_add_uri_targets();
-
-        this.connect('drag-motion', (widget, ctx, x, y, time) => {
-            Gdk.drag_status(ctx, Gdk.DragAction.COPY, time);
-            return true;
-        });
+        this._dropTarget = Gtk.DropTarget.new(Gio.File, Gdk.DragAction.COPY);
+        this.add_controller(this._dropTarget);
 
-        this.connect('drag-data-received', (widget, ctx, x, y, data, info, time) => {
-            let files = data.get_uris().map(Gio.file_new_for_uri);
-            if (this._mapView.openShapeLayers(files))
-                Gtk.drag_finish(ctx, true, false, time);
-            else
-                Gtk.drag_finish(ctx, false, false, time);
+        this._dropTarget.connect('drop', (target, value, x, y, data) => {
+            return this._mapView.openShapeLayers([value]);
         });
     }
 
@@ -256,17 +252,19 @@ export class MainWindow extends Gtk.ApplicationWindow {
     }
 
     _initSignals() {
-        this.connect('delete-event', this._quit.bind(this));
-        this.connect('configure-event',
-                     this._onConfigureEvent.bind(this));
+        this.connect('close-request', () => this._quit());
+        this.connect('notify::default-width', () => this._onSizeChanged());
+        this.connect('notify::default-height', () => this._onSizeChanged());
 
-        this.connect('window-state-event',
-                     this._onWindowStateEvent.bind(this));
+        this.connect('notify::is-maximized', () => this._onMaximizedChanged());
+        // TODO: GTK4, is this needed?
+        /*
         this._mapView.view.connect('button-press-event', () => {
             // Can not call something that will generate clutter events
             // from a clutter event-handler. So use an idle.
             GLib.idle_add(null, () => this._mapView.grab_focus());
         });
+        */
 
         /*
          * If the currently focused widget is an entry then we will
@@ -276,41 +274,44 @@ export class MainWindow extends Gtk.ApplicationWindow {
         /* TODO: GTK 4. This should probably be handled by something like
          * setting the map view as key capture widget for the search entry
          */
-        this.connect('key-press-event', (window, event) => {
-            let focusWidget = window.get_focus();
-            let keyval = event.get_keyval()[1];
-            let keys = [Gdk.KEY_plus, Gdk.KEY_KP_Add,
-                        Gdk.KEY_minus, Gdk.KEY_KP_Subtract,
-                        Gdk.KEY_equal];
-            let isPassThroughKey = keys.indexOf(keyval) !== -1;
-
-            /* if no entry is focused, and the key is not one we should treat
-             * as a zoom accelerator when no entry is focused, focus the
-             * main search entry in the headebar to propaget the keypress there
-             */
-            if (!(focusWidget instanceof Gtk.Entry) && !isPassThroughKey) {
-                /* if the search entry does not handle the event, pass it on
-                 * instead of activating the entry
-                 */
-                if (this._placeEntry.handle_event(event) === Gdk.EVENT_PROPAGATE)
-                    return false;
-
-                this._placeEntry.has_focus = true;
-                focusWidget = this._placeEntry;
-            }
-
-            if (focusWidget instanceof Gtk.Entry)
-                return focusWidget.event(event);
-
-            return false;
-        });
-
+        // this.connect('key-press-event', (window, event) => {
+        //     let focusWidget = window.get_focus();
+        //     let keyval = event.get_keyval()[1];
+        //     let keys = [Gdk.KEY_plus, Gdk.KEY_KP_Add,
+        //                 Gdk.KEY_minus, Gdk.KEY_KP_Subtract,
+        //                 Gdk.KEY_equal];
+        //     let isPassThroughKey = keys.indexOf(keyval) !== -1;
+
+        //     /* if no entry is focused, and the key is not one we should treat
+        //      * as a zoom accelerator when no entry is focused, focus the
+        //      * main search entry in the headebar to propaget the keypress there
+        //      */
+        //     if (!(focusWidget instanceof Gtk.Entry) && !isPassThroughKey) {
+        //         /* if the search entry does not handle the event, pass it on
+        //          * instead of activating the entry
+        //          */
+        //         if (this._placeEntry.handle_event(event) === Gdk.EVENT_PROPAGATE)
+        //             return false;
+
+        //         this._placeEntry.has_focus = true;
+        //         focusWidget = this._placeEntry;
+        //     }
+
+        //     if (focusWidget instanceof Gtk.Entry)
+        //         return focusWidget.event(event);
+
+        //     return false;
+        // });
+
+        // TODO: after converting the map view, fix this up...
+        /*
         this._mapView.view.connect('notify::zoom-level',
                                    this._updateZoomButtonsSensitivity.bind(this));
         this._mapView.view.connect('notify::max-zoom-level',
                                    this._updateZoomButtonsSensitivity.bind(this));
         this._mapView.view.connect('notify::min-zoom-level',
                                    this._updateZoomButtonsSensitivity.bind(this));
+        */
     }
 
     _updateZoomButtonsSensitivity() {
@@ -345,7 +346,7 @@ export class MainWindow extends Gtk.ApplicationWindow {
         this._headerBar.pack_end(this._headerBarRight);
 
         this._placeEntry = this._createPlaceEntry();
-        this._headerBar.custom_title = this._placeEntry;
+        this._headerBar.title_widget = this._placeEntry;
 
         Application.geoclue.connect('notify::state',
                                     this._updateLocationSensitivity.bind(this));
@@ -357,8 +358,8 @@ export class MainWindow extends Gtk.ApplicationWindow {
         this._actionBarRight = new HeaderBarRight({ mapView: this._mapView });
         this._actionBar.pack_end(this._actionBarRight);
 
-        this.connect('size-allocate', () => {
-            let [width, height] = this.get_size();
+        this.connect('notify::default-width', () => {
+            let width = this.default_width;
             if (width < _ADAPTIVE_VIEW_WIDTH) {
                 this.application.adaptive_mode = true;
                 this._headerBarLeft.hide();
@@ -378,18 +379,12 @@ export class MainWindow extends Gtk.ApplicationWindow {
     }
 
     _saveWindowGeometry() {
-        let window = this.get_window();
-        let state = window.get_state();
-
-        if (state & Gdk.WindowState.MAXIMIZED)
+        if (this.maximized)
             return;
 
         // GLib.Variant.new() can handle arrays just fine
-        let size = this.get_size();
-        Application.settings.set('window-size', size);
-
-        let position = this.get_position();
-        Application.settings.set('window-position', position);
+        Application.settings.set('window-size',
+                                 [this.default_width, this.default_height]);
     }
 
     _restoreWindowGeometry() {
@@ -399,18 +394,11 @@ export class MainWindow extends Gtk.ApplicationWindow {
             this.set_default_size(width, height);
         }
 
-        let position = Application.settings.get('window-position');
-        if (position.length === 2) {
-            let [x, y] = position;
-
-            this.move(x, y);
-        }
-
         if (Application.settings.get('window-maximized'))
             this.maximize();
     }
 
-    _onConfigureEvent(widget, event) {
+    _onSizeChanged() {
         if (this._configureId !== 0) {
             GLib.source_remove(this._configureId);
             this._configureId = 0;
@@ -423,15 +411,8 @@ export class MainWindow extends Gtk.ApplicationWindow {
         });
     }
 
-    _onWindowStateEvent(widget, event) {
-        let window = widget.get_window();
-        let state = window.get_state();
-
-        if (state & Gdk.WindowState.FULLSCREEN)
-            return;
-
-        let maximized = (state & Gdk.WindowState.MAXIMIZED);
-        Application.settings.set('window-maximized', maximized);
+    _onMaximizedChanged() {
+        Application.settings.set('window-maximized', this.maximized);
     }
 
     _quit() {
diff --git a/src/mapBubble.js b/src/mapBubble.js
index 88f29775..bc5e4118 100644
--- a/src/mapBubble.js
+++ b/src/mapBubble.js
@@ -22,6 +22,7 @@
 import Gio from 'gi://Gio';
 import GLib from 'gi://GLib';
 import GObject from 'gi://GObject';
+import Graphene from 'gi://Graphene';
 import Gtk from 'gi://Gtk';
 
 import {Application} from './application.js';
@@ -70,6 +71,26 @@ export class MapBubble extends Gtk.Popover {
 GObject.registerClass(MapBubble);
 
 export class MapBubbleScrolledWindow extends Gtk.ScrolledWindow {
+    vfunc_get_request_mode() {
+        return Gtk.SizeRequestMode.HEIGHT_FOR_WIDTH;
+    }
+
+    vfunc_measure(orientation, forSize) {
+        if (orientation === Gtk.Orientation.HORIZONTAL) {
+            let windowHeight = this.get_toplevel().get_allocated_height() - HEIGHT_MARGIN;
+            let [min, nat] = this.get_child().get_preferred_height_for_width(width);
+            min = Math.min(min, windowHeight);
+            nat = Math.min(nat, windowHeight);
+            return [min, nat, 0, 0];
+        } else {
+            let [min, nat] = this.get_child().get_preferred_width();
+            min = Math.min(min, MAX_CONTENT_WIDTH);
+            nat = Math.min(nat, MAX_CONTENT_WIDTH);
+            return [min, nat, 0, 0];
+        }
+    }
+
+    /*
     vfunc_get_preferred_width() {
         let [min, nat] = this.get_child().get_preferred_width();
         min = Math.min(min, MAX_CONTENT_WIDTH);
@@ -84,12 +105,17 @@ export class MapBubbleScrolledWindow extends Gtk.ScrolledWindow {
         nat = Math.min(nat, windowHeight);
         return [min, nat];
     }
+    */
 
-    vfunc_draw(cr) {
+    vfunc_snapshot(snapshot) {
         let popover = this.get_ancestor(Gtk.Popover);
         if (popover) {
-            let [{x, y, width, height}, baseline] = this.get_allocated_size();
+            let {x, y, width, height} = this.get_allocation();
+            let rect = new Graphene.Rect();
+
+            rect.init(x, y, width, height);
 
+            let cr = snapshot.append_cairo(rect);
             // clip the top corners to the rounded corner
             let radius = popover.get_style_context()
                                 .get_property(Gtk.STYLE_PROPERTY_BORDER_RADIUS, popover.get_state_flags())
@@ -103,9 +129,10 @@ export class MapBubbleScrolledWindow extends Gtk.ScrolledWindow {
             cr.lineTo(width, height);
 
             cr.clip();
+            cr.$dispose();
         }
 
-        return super.vfunc_draw(cr);
+        super.vfunc_snapshot(snapshot);
     }
 }
 
diff --git a/src/mapMarker.js b/src/mapMarker.js
index 8e3842cc..becaba10 100644
--- a/src/mapMarker.js
+++ b/src/mapMarker.js
@@ -20,19 +20,18 @@
  */
 
 import Cairo from 'cairo';
-import Champlain from 'gi://Champlain';
-import Clutter from 'gi://Clutter';
 import Gdk from 'gi://Gdk';
 import GLib from 'gi://GLib';
 import GObject from 'gi://GObject';
 import Gtk from 'gi://Gtk';
+import Shumate from 'gi://Shumate';
 
 import {Application} from './application.js';
 import {MapBubble} from './mapBubble.js';
 import {MapWalker} from './mapWalker.js';
 import * as Utils from './utils.js';
 
-export class MapMarker extends Champlain.Marker {
+export class MapMarker extends Shumate.Marker {
 
     constructor(params) {
         let place = params.place;
@@ -84,22 +83,6 @@ export class MapMarker extends Champlain.Marker {
         Application.application.connect('notify::adaptive-mode', this._onAdaptiveModeChanged.bind(this));
     }
 
-    get surface() {
-        return this._surface;
-    }
-
-    set surface(v) {
-        this._surface = v;
-    }
-
-    vfunc_get_surface() {
-        return this._surface;
-    }
-
-    vfunc_set_surface(surface) {
-        this._surface = surface;
-    }
-
     _actorFromIconName(name, size, color) {
         try {
             let theme = Gtk.IconTheme.get_default();
@@ -396,14 +379,11 @@ export class MapMarker extends Champlain.Marker {
 }
 
 GObject.registerClass({
-    Implements: [Champlain.Exportable],
     Abstract: true,
     Signals: {
         'gone-to': { }
     },
     Properties: {
-        'surface': GObject.ParamSpec.override('surface',
-                                              Champlain.Exportable),
         'view-latitude': GObject.ParamSpec.double('view-latitude', '', '',
                                                   GObject.ParamFlags.READABLE |
                                                   GObject.ParamFlags.WRITABLE,
diff --git a/src/mapSource.js b/src/mapSource.js
index 55c12a33..fe7f2c6c 100644
--- a/src/mapSource.js
+++ b/src/mapSource.js
@@ -17,7 +17,7 @@
  * Author: Jonas Danielsson <jonas threetimestwo org>
  */
 
-import Champlain from 'gi://Champlain';
+import Shumate from 'gi://Shumate';
 
 import * as Service from './service.js';
 import * as Utils from './utils.js';
diff --git a/src/mapView.js b/src/mapView.js
index 5aa43155..a46299b7 100644
--- a/src/mapView.js
+++ b/src/mapView.js
@@ -19,15 +19,13 @@
  * Author: Zeeshan Ali (Khattak) <zeeshanak gnome org>
  */
 
-import Champlain from 'gi://Champlain';
-import Clutter from 'gi://Clutter';
 import GObject from 'gi://GObject';
+import Gdk from 'gi://Gdk';
 import GeocodeGlib from 'gi://GeocodeGlib';
 import Gio from 'gi://Gio';
 import GLib from 'gi://GLib';
 import Gtk from 'gi://Gtk';
-import GtkChamplain from 'gi://GtkChamplain';
-import Handy from 'gi://Handy';
+import Shumate from 'gi://Shumate';
 
 import GnomeMaps from 'gi://GnomeMaps';
 
@@ -77,7 +75,7 @@ const DASHED_ROUTE_LINE_GAP_LENGTH = 5;
 // Maximum limit of file size (20 MB) that can be loaded without user confirmation
 const FILE_SIZE_LIMIT_MB = 20;
 
-export class MapView extends GtkChamplain.Embed {
+export class MapView extends Gtk.Overlay {
 
     static MapType = {
         LOCAL: 'MapsLocalSource',
@@ -102,8 +100,9 @@ export class MapView extends GtkChamplain.Embed {
         let isValid = Application.routeQuery.isValid();
 
         this._routingOpen = value && isValid;
-        this._routeLayers.forEach((routeLayer) => routeLayer.visible = value && isValid);
-        this._instructionMarkerLayer.visible = value && isValid;
+        // TODO: bring it back when the layers are working
+        //this._routeLayers.forEach((routeLayer) => routeLayer.visible = value && isValid);
+        //this._instructionMarkerLayer.visible = value && isValid;
         if (!value)
             this.routeShowing = false;
         this.notify('routingOpen');
@@ -128,13 +127,13 @@ export class MapView extends GtkChamplain.Embed {
         delete params.mainWindow;
 
         this._storeId = 0;
-        this.view = this._initView();
-        this._initLayers();
+        //this.view = this._initView();
+        //this._initLayers();
 
-        this.setMapType(mapType);
+        //this.setMapType(mapType);
 
-        if (Application.normalStartup)
-            this._goToStoredLocation();
+        //if (Application.normalStartup)
+        //    this._goToStoredLocation();
 
         this.shapeLayerStore = new Gio.ListStore(GObject.TYPE_OBJECT);
 
@@ -148,18 +147,14 @@ export class MapView extends GtkChamplain.Embed {
     _initScale(view) {
         let showScale = Application.settings.get('show-scale');
 
-        this._scale = new Champlain.Scale({ visible: showScale });
+        this._scale = new Shumate.Scale({ visible: showScale });
         this._scale.connect_view(view);
 
         if (Utils.getMeasurementSystem() === Utils.METRIC_SYSTEM)
-            this._scale.unit = Champlain.Unit.KM;
+            this._scale.unit = Shumate.Unit.KM;
         else
-            this._scale.unit = Champlain.Unit.MILES;
+            this._scale.unit = Shumate.Unit.MILES;
 
-        this._scale.set_x_expand(true);
-        this._scale.set_y_expand(true);
-        this._scale.set_x_align(Clutter.ActorAlign.START);
-        this._scale.set_y_align(Clutter.ActorAlign.END);
         view.add_child(this._scale);
     }
 
@@ -167,10 +162,6 @@ export class MapView extends GtkChamplain.Embed {
         let view = this.get_view();
 
         view.min_zoom_level = MapMinZoom;
-        view.goto_animation_mode = Clutter.AnimationMode.EASE_IN_OUT_CUBIC;
-        view.reactive = true;
-        view.kinetic_mode = true;
-        view.horizontal_wrap = true;
 
         view.connect('notify::latitude', this._onViewMoved.bind(this));
         // switching map type will set view min-zoom-level from map source
@@ -188,21 +179,28 @@ export class MapView extends GtkChamplain.Embed {
     }
 
     /* create and store a route layer, pass true to get a dashed line */
-    _createRouteLayer(dashed, lineColor, width) {
-        let red = Color.parseColor(lineColor, 0);
-        let green = Color.parseColor(lineColor, 1);
-        let blue = Color.parseColor(lineColor, 2);
-        // Clutter uses a 0-255 range for color components
-        let strokeColor = new Clutter.Color({ red: red * 255,
-                                              blue: blue * 255,
-                                              green: green * 255,
-                                              alpha: 255 });
-        let routeLayer = new Champlain.PathLayer({ stroke_width: width,
-                                                   stroke_color: strokeColor });
+    _createRouteLayer(dashed, lineColor, outlineColor, width) {
+        let strokeColor = new Gdk.RGBA({ red:   Color.parseColor(lineColor, 0),
+                                         blue:  Color.parseColor(lineColor, 1),
+                                         green: Color.parseColor(lineColor, 2),
+                                         alpha: 1.0 });
+        let routeLayer = new Shumate.PathLayer({ stroke_width: width,
+                                                 stroke_color: strokeColor });
         if (dashed)
             routeLayer.set_dash([DASHED_ROUTE_LINE_FILLED_LENGTH,
                                  DASHED_ROUTE_LINE_GAP_LENGTH]);
 
+        if (outlineColor) {
+            let outlineStrokeColor =
+                new Gdk.RGBA({ red:   Color.parseColor(outlineColor, 0),
+                               green: Color.parseColor(outlineColor, 1),
+                               blue:  Color.parseColor(outlineColor, 2),
+                               alpha: 1.0 });
+
+            routeLayer.outline_color = outlineStrokeColor;
+            routeLayer.outline_width = 1.0;
+        }
+
         this._routeLayers.push(routeLayer);
         this.view.add_layer(routeLayer);
 
@@ -210,6 +208,8 @@ export class MapView extends GtkChamplain.Embed {
     }
 
     _clearRouteLayers() {
+        // TODO: bring this back when the view is working again...
+        /*
         this._routeLayers.forEach((routeLayer) => {
             routeLayer.remove_all();
             routeLayer.visible = false;
@@ -217,18 +217,19 @@ export class MapView extends GtkChamplain.Embed {
         });
 
         this._routeLayers = [];
+        */
     }
 
     _initLayers() {
-        let mode = Champlain.SelectionMode.SINGLE;
+        let mode = Gtk.SelectionMode.SINGLE;
 
-        this._userLocationLayer = new Champlain.MarkerLayer({ selection_mode: mode });
+        this._userLocationLayer = new Shumate.MarkerLayer({ selection_mode: mode });
         this.view.add_layer(this._userLocationLayer);
 
-        this._placeLayer = new Champlain.MarkerLayer({ selection_mode: mode });
+        this._placeLayer = new Shumate.MarkerLayer({ selection_mode: mode });
         this.view.add_layer(this._placeLayer);
 
-        this._instructionMarkerLayer = new Champlain.MarkerLayer({ selection_mode: mode });
+        this._instructionMarkerLayer = new Shumate.MarkerLayer({ selection_mode: mode });
         this.view.add_layer(this._instructionMarkerLayer);
 
         ShapeLayer.SUPPORTED_TYPES.push(GeoJSONShapeLayer);
@@ -254,14 +255,16 @@ export class MapView extends GtkChamplain.Embed {
         });
         route.connect('reset', () => {
             this._clearRouteLayers();
-            this._instructionMarkerLayer.remove_all();
+            // TODO: bring it back when the layers are working
+            //this._instructionMarkerLayer.remove_all();
             this._turnPointMarker = null;
             this.routeShowing = false;
         });
         transitPlan.connect('update', () => this._showTransitPlan(transitPlan));
         transitPlan.connect('reset', () => {
             this._clearRouteLayers();
-            this._instructionMarkerLayer.remove_all();
+            // TODO: bring it back when the layers are working
+            //this._instructionMarkerLayer.remove_all();
             this._turnPointMarker = null;
             this.routeShowing = false;
         });
@@ -271,7 +274,8 @@ export class MapView extends GtkChamplain.Embed {
         });
         transitPlan.connect('itinerary-deselected', () => {
             this._clearRouteLayers();
-            this._instructionMarkerLayer.remove_all();
+            // TODO: bring it back when the layers are working
+            //this._instructionMarkerLayer.remove_all();
             this._turnPointMarker = null;
             this.routeShowing = false;
         });
@@ -314,7 +318,7 @@ export class MapView extends GtkChamplain.Embed {
 
             Application.settings.set('map-type', mapType);
         } else {
-            let renderer = new Champlain.ImageRenderer();
+            let renderer = new Shumate.RasterRenderer();
             let source = new GnomeMaps.FileTileSource({
                 path: Utils.getBufferText(Application.application.local_tile_path),
                 renderer: renderer,
@@ -525,7 +529,7 @@ export class MapView extends GtkChamplain.Embed {
         let zoom = this.view.zoom_level;
         let location = [this.view.latitude, this.view.longitude];
 
-        /* protect agains situations where the Champlain view was already
+        /* protect agains situations where the map view was already
          * disposed, in this case zoom will be set to the GObject property
          * getter
          */
@@ -686,7 +690,7 @@ export class MapView extends GtkChamplain.Embed {
         this._placeLayer.remove_all();
 
         routeLayer = this._createRouteLayer(false, TURN_BY_TURN_ROUTE_COLOR,
-                                            ROUTE_LINE_WIDTH);
+                                            null, ROUTE_LINE_WIDTH);
         route.path.forEach((polyline) => routeLayer.add_node(polyline));
         this.routingOpen = true;
 
@@ -729,15 +733,11 @@ export class MapView extends GtkChamplain.Embed {
             let hasOutline = Color.relativeLuminance(color) >
                              OUTLINE_LUMINANCE_THREASHHOLD;
             let routeLayer;
-            let outlineRouteLayer;
+            let lineWidth = ROUTE_LINE_WIDTH + hasOutline ? 2 : 0;
 
-            /* draw an outline by drawing a background path layer if needed
-             * TODO: maybe we should add support for outlined path layers in
-             * libchamplain */
-            if (hasOutline)
-                outlineRouteLayer = this._createRouteLayer(dashed, outlineColor,
-                                                           ROUTE_LINE_WIDTH + 2);
-            routeLayer = this._createRouteLayer(dashed, color, ROUTE_LINE_WIDTH);
+            routeLayer = this._createRouteLayer(dashed, color,
+                                                hasOutline ? outlineColor : null,
+                                                lineWidth);
 
             /* if this is a walking leg and not at the start, "stitch" it
              * together with the end point of the previous leg, as the walk
@@ -749,11 +749,6 @@ export class MapView extends GtkChamplain.Embed {
                 routeLayer.add_node(lastPoint);
             }
 
-            if (hasOutline) {
-                leg.polyline.forEach((function (polyline) {
-                    outlineRouteLayer.add_node(polyline);
-                }));
-            }
             leg.polyline.forEach((function (polyline) {
                 routeLayer.add_node(polyline);
             }));
@@ -839,7 +834,7 @@ GObject.registerClass({
         'going-to-user-location': {},
         'gone-to-user-location': {},
         'view-moved': {},
-        'marker-selected': { param_types: [Champlain.Marker] },
+        'marker-selected': { param_types: [Shumate.Marker] },
         'map-type-changed': { param_types: [GObject.TYPE_STRING] }
     },
 }, MapView);
diff --git a/src/mapWalker.js b/src/mapWalker.js
index 925ea5bb..811fc6c9 100644
--- a/src/mapWalker.js
+++ b/src/mapWalker.js
@@ -21,7 +21,6 @@
  *         Damián Nohales <damiannohales gmail com>
  */
 
-import Clutter from 'gi://Clutter';
 import GObject from 'gi://GObject';
 
 import {BoundingBox} from './boundingBox.js';
@@ -101,7 +100,6 @@ export class MapWalker extends GObject.Object {
         this._updateGoToDuration(fromLocation);
 
         if (linear) {
-            this._view.goto_animation_mode = Clutter.AnimationMode.LINEAR;
             Utils.once(this._view, 'animation-completed',
                        this.zoomToFit.bind(this));
             this._view.go_to(this.place.location.latitude,
@@ -114,17 +112,14 @@ export class MapWalker extends GObject.Object {
              * also give user a good idea of where the destination is compared to current
              * location.
              */
-            this._view.goto_animation_mode = Clutter.AnimationMode.EASE_IN_CUBIC;
             this._ensureVisible(fromLocation);
 
             Utils.once(this._view, 'animation-completed', () => {
-                this._view.goto_animation_mode = Clutter.AnimationMode.EASE_OUT_CUBIC;
                 this._view.go_to(this.place.location.latitude,
                                  this.place.location.longitude);
 
                 Utils.once(this._view, 'animation-completed::go-to', () => {
                     this.zoomToFit();
-                    this._view.goto_animation_mode = Clutter.AnimationMode.EASE_IN_OUT_CUBIC;
                     this.emit('gone-to');
                 });
             });
diff --git a/src/placeBar.js b/src/placeBar.js
index 6db74564..d4676aff 100644
--- a/src/placeBar.js
+++ b/src/placeBar.js
@@ -19,7 +19,6 @@
  * Author: James Westman <james flyingpimonster net>
  */
 
-import Clutter from 'gi://Clutter';
 import Gdk from 'gi://Gdk';
 import GeocodeGlib from 'gi://GeocodeGlib';
 import GObject from 'gi://GObject';
@@ -43,15 +42,19 @@ export class PlaceBar extends Gtk.Revealer {
         this._buttons = new PlaceButtons({ mapView: this._mapView });
         this._buttons.initSendToButton(this._altSendToButton);
         this._buttons.connect('place-edited', this._onPlaceEdited.bind(this));
-        this._box.add(this._buttons);
+        this._box.append(this._buttons);
 
-        this._multipress = new Gtk.GestureMultiPress({ widget: this._eventbox });
-        this._multipress.connect('released', this._onEventBoxClicked.bind(this));
+        this._click = new Gtk.GestureClick();
+        this._box.add_controller(this._click);
+        this._click.connect('released', this._onBoxClicked.bind(this));
 
         Application.application.connect('notify::adaptive-mode', this._updateVisibility.bind(this));
         this.connect('notify::place', this._updatePlace.bind(this));
 
-        this._mapView.view.connect('touch-event', this._onMapClickEvent.bind(this));
+        this._mapClick = new Gtk.GestureSingle();
+        this._mapView.add_controller(this._mapClick);
+
+        this._mapClick.connect('begin', this._onMapClickEvent.bind(this));
     }
 
     _updatePlace() {
@@ -83,7 +86,7 @@ export class PlaceBar extends Gtk.Revealer {
         }
     }
 
-    _onEventBoxClicked() {
+    _onBoxClicked() {
         if (this.place.isCurrentLocation) {
             if (this._currentLocationView) {
                 this._box.remove(this._currentLocationView);
@@ -136,7 +139,6 @@ GObject.registerClass({
     InternalChildren: [ 'actionbar',
                         'altSendToButton',
                         'box',
-                        'eventbox',
                         'title' ],
     Properties: {
         'place': GObject.ParamSpec.object('place',
@@ -147,3 +149,4 @@ GObject.registerClass({
                                           GeocodeGlib.Place)
     },
 }, PlaceBar);
+
diff --git a/src/placeEntry.js b/src/placeEntry.js
index 74f9df97..ea21f5e7 100644
--- a/src/placeEntry.js
+++ b/src/placeEntry.js
@@ -88,10 +88,6 @@ export class PlaceEntry extends Gtk.SearchEntry {
         let mapView = props.mapView;
         delete props.mapView;
 
-        if (!props.loupe)
-            props.primary_icon_name = null;
-        delete props.loupe;
-
         let maxChars = props.maxChars;
         delete props.maxChars;
 
@@ -112,9 +108,10 @@ export class PlaceEntry extends Gtk.SearchEntry {
         this._cache = {};
 
         // clear cache when view moves, as result are location-dependent
-        this._mapView.view.connect('notify::latitude', () => this._cache = {});
+        // TODO: connect to viewport lat/lon changes...
+        //this._mapView.view.connect('notify::latitude', () => this._cache = {});
         // clear cache when zoom level changes, to allow limiting location bias
-        this._mapView.view.connect('notify::zoom-level', () => this._cache =  {});
+        //this._mapView.view.connect('notify::zoom-level', () => this._cache =  {});
     }
 
     _onSearchChanged() {
@@ -164,21 +161,26 @@ export class PlaceEntry extends Gtk.SearchEntry {
 
     _createPopover(numVisible, maxChars) {
         let popover = new PlacePopover({ num_visible:   numVisible,
-                                         relative_to:   this,
+                                         entry:         this,
                                          maxChars:      maxChars });
 
+        // TODO: implement vfunc_size_allocate here?...
+        /*
         this.connect('size-allocate', (widget, allocation) => {
             // Magic number to make the alignment pixel perfect.
             let width_request = allocation.width + 20;
             // set at least 320 px width to avoid too narrow in the sidebar
             popover.width_request = Math.max(width_request, 320);
         });
+        */
 
         popover.connect('selected', (widget, place) => {
             this.place = place;
-            popover.hide();
+            popover.popdown();
         });
 
+        // TODO: set position...
+
         return popover;
     }
 
diff --git a/src/placePopover.js b/src/placePopover.js
index c2af1c33..adb4ca68 100644
--- a/src/placePopover.js
+++ b/src/placePopover.js
@@ -36,11 +36,9 @@ export class PlacePopover extends SearchPopover {
         let maxChars = props.maxChars;
         delete props.maxChars;
 
-        props.transitions_enabled = false;
         super(props);
 
         this._maxChars = maxChars;
-        this._entry = this.relative_to;
 
         this._list.connect('row-activated', (list, row) => {
             if (row)
diff --git a/src/placeViewImage.js b/src/placeViewImage.js
index 44670d59..cc50cf09 100644
--- a/src/placeViewImage.js
+++ b/src/placeViewImage.js
@@ -22,6 +22,7 @@
 import Cairo from 'cairo';
 import Gdk from 'gi://Gdk';
 import GObject from 'gi://GObject';
+import Graphene from 'gi://Graphene';
 import Gtk from 'gi://Gtk';
 
 /* The maximum aspect ratio, after which the image will be cropped vertically */
@@ -50,13 +51,19 @@ export class PlaceViewImage extends Gtk.DrawingArea {
         this.queue_resize();
     }
 
-    vfunc_draw(cr) {
-        let [{x, y, width, height}, baseline] = this.get_allocated_size();
+    vfunc_snapshot(snapshot) {
+        let {x, y, width, height} = this.get_allocation();
 
         if (this._pixbuf === null || width === 0 || height === 0) {
             return;
         }
 
+        let rect = new Graphene.Rect();
+
+        rect.init(x, y, width, height);
+
+        let cr = snapshot.append_cairo(rect);
+
         width *= this.scale_factor;
         height *= this.scale_factor;
 
@@ -82,13 +89,27 @@ export class PlaceViewImage extends Gtk.DrawingArea {
         cr.paint();
         cr.restore();
 
-        return false;
+        super.vfunc_snapshot(snapshot);
+        cr.$dispose();
     }
 
     vfunc_get_request_mode() {
         return Gtk.SizeRequestMode.HEIGHT_FOR_WIDTH;
     }
 
+    vfunc_measure(orientation, forSize) {
+        if (orientation === Gtk.Orientation.VERTICAL) {
+            if (this._pixbuf) {
+                let height = (this._pixbuf.height / this._pixbuf.width) * forSize;
+                return [height, height, 0, 0];
+            } else {
+                return [0, 0, 0, 0];
+            }
+        } else {
+            return [forSize, forSize, 0, 0];
+        }
+    }
+    /*
     vfunc_get_preferred_height_for_width(width) {
         if (this._pixbuf) {
             let height = (this._pixbuf.height / this._pixbuf.width) * width;
@@ -97,6 +118,7 @@ export class PlaceViewImage extends Gtk.DrawingArea {
             return [0, 0];
         }
     }
+    */
 }
 
 GObject.registerClass(PlaceViewImage);
diff --git a/src/printLayout.js b/src/printLayout.js
index 858e174f..4a7245a9 100644
--- a/src/printLayout.js
+++ b/src/printLayout.js
@@ -18,13 +18,12 @@
  */
 
 import Cairo from 'cairo';
-import Champlain from 'gi://Champlain';
-import Clutter from 'gi://Clutter';
 import Gdk from 'gi://Gdk';
 import GObject from 'gi://GObject';
 import Gtk from 'gi://Gtk';
 import Pango from 'gi://Pango';
 import PangoCairo from 'gi://PangoCairo';
+import Shumate from 'gi://Shumate';
 
 import {Application} from './application.js';
 import * as Color from './color.js';
@@ -33,10 +32,10 @@ import * as MapSource from './mapSource.js';
 import {TurnPointMarker} from './turnPointMarker.js';
 import * as Utils from './utils.js';
 
-const _STROKE_COLOR = new Clutter.Color({ red: 0,
-                                          blue: 255,
-                                          green: 0,
-                                          alpha: 255 });
+const _STROKE_COLOR = new Gdk.RGBA({ red:   0.0,
+                                     blue:  1.0,
+                                     green: 0.0,
+                                     alpha: 1.0 });
 const _STROKE_WIDTH = 5.0;
 
 /* All following constants are ratios of surface size to page size */
@@ -177,8 +176,8 @@ export class PrintLayout extends GObject.Object {
     }
 
     _addRouteLayer(view) {
-        let routeLayer = new Champlain.PathLayer({ stroke_width: _STROKE_WIDTH,
-                                                   stroke_color: _STROKE_COLOR });
+        let routeLayer = new Shumate.PathLayer({ stroke_width: _STROKE_WIDTH,
+                                                 stroke_color: _STROKE_COLOR });
         view.add_layer(routeLayer);
         this._route.path.forEach((node) => routeLayer.add_node(node));
     }
diff --git a/src/routeEntry.js b/src/routeEntry.js
index 5d2018ed..ed7e286a 100644
--- a/src/routeEntry.js
+++ b/src/routeEntry.js
@@ -56,18 +56,15 @@ export class RouteEntry extends Gtk.Grid {
         this._entryGrid.attach(this.entry, 0, 0, 1, 1);
 
         // There is no GdkWindow on the widget until it is realized
-        this._icon.connect('realize', function(icon) {
-            if (icon.window && icon.window.get_cursor())
-                return;
-
-            icon.window.set_cursor(Gdk.Cursor.new(Gdk.CursorType.HAND1));
+        this.icon.connect('realize', (icon) => {
+            icon.set_cursor(Gdk.Cursor.new_from_name('grab', null));
         });
 
         switch (this._type) {
         case RouteEntry.Type.FROM:
             let query = Application.routeQuery;
             this._buttonImage.icon_name = 'list-add-symbolic';
-            this._icon.icon_name = 'maps-point-start-symbolic';
+            this.icon.icon_name = 'maps-point-start-symbolic';
             /* Translators: this is add via location tooltip */
             this._button.tooltip_text = _("Add via location");
             query.connect('notify::points', () => {
@@ -77,13 +74,13 @@ export class RouteEntry extends Gtk.Grid {
             break;
         case RouteEntry.Type.VIA:
             this._buttonImage.icon_name = 'list-remove-symbolic';
-            this._icon.icon_name = 'maps-point-end-symbolic';
+            this.icon.icon_name = 'maps-point-end-symbolic';
             /* Translators: this is remove via location tooltip */
             this._button.tooltip_text = _("Remove via location");
             break;
         case RouteEntry.Type.TO:
             this._buttonImage.icon_name = 'route-reverse-symbolic';
-            this._icon.icon_name = 'maps-point-end-symbolic';
+            this.icon.icon_name = 'maps-point-end-symbolic';
             /* Translators: this is reverse route tooltip */
             this._button.tooltip_text = _("Reverse route");
             break;
@@ -117,9 +114,8 @@ export class RouteEntry extends Gtk.Grid {
 
 GObject.registerClass({
     Template: 'resource:///org/gnome/Maps/ui/route-entry.ui',
-    Children: [ 'iconEventBox' ],
+    Children: [ 'icon' ],
     InternalChildren: [ 'entryGrid',
-                        'icon',
                         'button',
                         'buttonImage' ]
 }, RouteEntry);
diff --git a/src/searchPopover.js b/src/searchPopover.js
index cb184b21..40399f0d 100644
--- a/src/searchPopover.js
+++ b/src/searchPopover.js
@@ -29,19 +29,23 @@ import Gtk from 'gi://Gtk';
 export class SearchPopover extends Gtk.Popover {
 
     constructor(props) {
+        let entry = props.entry;
+        delete props.entry;
+
         super(props);
 
-        this._entry = this.relative_to;
+        this._entry = entry;
 
         // We need to propagate events to the listbox so that we can
         // keep typing while selecting a place. But we do not want to
         // propagate the 'enter' key press if there is a selection.
-        this._keyController =
-            new Gtk.EventControllerKey({ widget: this._entry });
+        this._keyController = new Gtk.EventControllerKey();
+        this._entry.add_controller(this._keyController);
         this._keyController.connect('key-pressed',
                                     this._propagateKeys.bind(this));
 
-        this._buttonPressGesture = new Gtk.GestureSingle({ widget: this._entry });
+        this._buttonPressGesture = new Gtk.GestureSingle();
+        this._entry.add_controller(this._buttonPressGesture);
         this._buttonPressGesture.connect('begin',
                                          () => this._list.unselect_all());
     }
diff --git a/src/shapeLayer.js b/src/shapeLayer.js
index 18ac5170..ef793094 100644
--- a/src/shapeLayer.js
+++ b/src/shapeLayer.js
@@ -17,9 +17,10 @@
  * Author: Hashem Nasarat <hashem riseup net>
  */
 
-import Champlain from 'gi://Champlain';
 import Gio from 'gi://Gio';
 import GObject from 'gi://GObject';
+import Gtk from 'gi://Gtk';
+import Shumate from 'gi://Shumate';
 
 import {GeoJSONShapeLayer} from './geoJSONShapeLayer.js';
 import * as Utils from './utils.js';
@@ -53,8 +54,8 @@ export class ShapeLayer extends GObject.Object {
             null
         ).get_attribute_string(Gio.FILE_ATTRIBUTE_STANDARD_DISPLAY_NAME);
 
-        this._markerLayer = new Champlain.MarkerLayer({
-            selection_mode: Champlain.SelectionMode.SINGLE
+        this._markerLayer = new Shumate.MarkerLayer({
+            selection_mode: Gtk.SelectionMode.SINGLE
         });
         this._mapSource = null;
     }
diff --git a/src/sidebar.js b/src/sidebar.js
index 372ca737..a51d0db3 100644
--- a/src/sidebar.js
+++ b/src/sidebar.js
@@ -129,13 +129,16 @@ export class Sidebar extends Gtk.Revealer {
     }
 
     _initQuerySignals() {
+        this._numRouteEntries = 0;
         this._query.connect('point-added', (obj, point, index) => {
             this._createRouteEntry(index, point);
+            this._numRouteEntries++;
         });
 
         this._query.connect('point-removed', (obj, point, index) => {
             let row = this._entryList.get_row_at_index(index);
-            row.destroy();
+            this._entryList.remove(row);
+            this._numRouteEntries--;
         });
     }
 
@@ -148,7 +151,7 @@ export class Sidebar extends Gtk.Revealer {
         let type;
         if (index === 0)
             type = RouteEntry.Type.FROM;
-        else if (index === this._entryList.get_children().length)
+        else if (index === this._numRouteEntries)
             type = RouteEntry.Type.TO;
         else
             type = RouteEntry.Type.VIA;
@@ -158,7 +161,8 @@ export class Sidebar extends Gtk.Revealer {
                                           mapView: this._mapView });
 
         // add handler overriding tab focus behavior on route entries
-        routeEntry.entry.connect('focus', this._onRouteEntryFocus.bind(this));
+        // TODO: how to set up tab handling using GTK4?
+        //routeEntry.entry.connect('focus', this._onRouteEntryFocus.bind(this));
         // add handler for
         routeEntry.entry.connect('notify::place', () => {
             this._onRouteEntrySelectedPlace(routeEntry.entry);
@@ -167,7 +171,7 @@ export class Sidebar extends Gtk.Revealer {
 
         if (type === RouteEntry.Type.FROM) {
             routeEntry.button.connect('clicked', () => {
-                let lastIndex = this._entryList.get_children().length;
+                let lastIndex = this._numRouteEntries;
                 this._query.addPoint(lastIndex - 1);
                 // focus on the newly added point's entry
                 this._entryList.get_row_at_index(lastIndex - 1).get_child().entry.grab_focus();
@@ -175,7 +179,7 @@ export class Sidebar extends Gtk.Revealer {
 
             this.connect('notify::child-revealed', () => {
                 if (this.child_revealed)
-                    routeEntry.entry.grab_focus_without_selecting();
+                    routeEntry.entry.grab_focus();
             });
         } else if (type === RouteEntry.Type.VIA) {
             routeEntry.button.connect('clicked', () => {
@@ -469,7 +473,10 @@ export class Sidebar extends Gtk.Revealer {
 
     _clearInstructions() {
         let listBox = this._instructionList;
-        listBox.forall(listBox.remove.bind(listBox));
+
+        for (instruction of listBox) {
+            listBox.remove(instruction);
+        }
 
         this._instructionStack.visible_child = this._instructionWindow;
         this._timeInfo.label = '';
@@ -515,12 +522,12 @@ export class Sidebar extends Gtk.Revealer {
         this._query.thaw_notify();
     }
 
-    _onDragDrop(row, context, x, y, time) {
+    _onDragDrop(row) {
         let srcIndex = this._query.points.indexOf(this._draggedPoint);
         let destIndex = row.get_index();
 
         this._reorderRoutePoints(srcIndex, destIndex);
-        Gtk.drag_finish(context, true, false, time);
+
         return true;
     }
 
@@ -546,7 +553,7 @@ export class Sidebar extends Gtk.Revealer {
     }
 
     // Drag ends, show the dragged row again.
-    _onDragEnd(context, row) {
+    _onDragEnd(row) {
         this._draggedPoint = null;
 
         // Restore to natural height
@@ -555,14 +562,10 @@ export class Sidebar extends Gtk.Revealer {
     }
 
     // Drag begins, set the correct drag icon and hide the dragged row.
-    _onDragBegin(context, row) {
+    _onDragBegin(source, row) {
         let routeEntry = row.get_child();
-        let width = row.get_allocated_width();
-        let height = row.get_allocated_height();
-        let surface = new Cairo.ImageSurface(Cairo.Format.ARGB32, width, height);
-        let cr = new Cairo.Context(surface)
+        let {x, y, width, height} = row.get_allocation();
 
-        row.draw(cr);
         this._draggedPoint = routeEntry.point;
 
         // Set a fixed height on the row to prevent the sidebar height
@@ -570,32 +573,32 @@ export class Sidebar extends Gtk.Revealer {
         row.height_request = height;
         row.get_child().hide();
 
-        Gtk.drag_set_icon_surface(context, surface);
+        let paintable = new Gtk.WidgetPaintable(row);
+
+        source.set_icon(paintable, 0, 0);
     }
 
     // Set up drag and drop between RouteEntrys. The drag source is from a
     // GtkEventBox that contains the start/end icon next in the entry. And
     // the drag destination is the ListBox row.
     _initRouteDragAndDrop(routeEntry) {
-        let dragIcon = routeEntry.iconEventBox;
+        let dragIcon = routeEntry.icon;
         let row = routeEntry.get_parent();
+        let dragSource = new Gtk.DragSource();
+
+        dragIcon.add_controller(dragSource);
 
-        dragIcon.drag_source_set(Gdk.ModifierType.BUTTON1_MASK,
-                                 null,
-                                 Gdk.DragAction.MOVE);
-        dragIcon.drag_source_add_image_targets();
+        dragSource.connect('drag-begin',
+                           (source, drag, widget) => this._onDragBegin(source, row));
+        dragSource.connect('drag-end',
+                           (source, dele, data) => this._onDragEnd(row));
 
-        row.drag_dest_set(Gtk.DestDefaults.MOTION,
-                          null,
-                          Gdk.DragAction.MOVE);
-        row.drag_dest_add_image_targets();
+        let dropTarget = Gtk.DropTarget.new(RouteEntry, Gdk.DragAction.MOVE);
 
-        dragIcon.connect('drag-begin', (icon, context) => this._onDragBegin(context, row));
-        dragIcon.connect('drag-end', (icon, context) => this._onDragEnd(context, row));
+        row.add_controller(dropTarget);
 
-        row.connect('drag-leave', this._dragUnhighlightRow.bind(this, row));
-        row.connect('drag-motion', this._onDragMotion.bind(this));
-        row.connect('drag-drop', this._onDragDrop.bind(this));
+        dropTarget.connect('drop',
+                           (target, value, x, y, data) => this._onDragDrop(target));
     }
 }
 
diff --git a/src/storedRoute.js b/src/storedRoute.js
index 1fbfef3f..094657d0 100644
--- a/src/storedRoute.js
+++ b/src/storedRoute.js
@@ -20,9 +20,9 @@
  * Author: Jonas Danielsson <jonas threetimestwo org>
  */
 
-import Champlain from 'gi://Champlain';
 import GObject from 'gi://GObject';
 import Gtk from 'gi://Gtk';
+import Shumate from 'gi://Shumate';
 
 import {Place} from './place.js';
 import {Route, TurnPoint} from './route.js';
@@ -175,15 +175,15 @@ export class StoredRoute extends Place {
                 prop.path = prop.path.map((coordinate) => {
                     let lat = coordinate.latitude;
                     let lon = coordinate.longitude;
-                    return new Champlain.Coordinate({ latitude: lat,
-                                                      longitude: lon });
+                    return new Shumate.Coordinate({ latitude: lat,
+                                                    longitude: lon });
                 });
                 prop.turnPoints = prop.turnPoints.map((turnPoint) => {
                     let lat = turnPoint.coordinate.latitude;
                     let lon = turnPoint.coordinate.longitude;
 
-                    let coordinate = new Champlain.Coordinate({ latitude: lat,
-                                                                longitude: lon });
+                    let coordinate = new Shumate.Coordinate({ latitude: lat,
+                                                              longitude: lon });
 
                     return new TurnPoint({
                         coordinate: coordinate,
diff --git a/src/transitBoardMarker.js b/src/transitBoardMarker.js
index 462727c4..3894025b 100644
--- a/src/transitBoardMarker.js
+++ b/src/transitBoardMarker.js
@@ -20,7 +20,6 @@
  */
 
 import Cairo from 'cairo';
-import Clutter from 'gi://Clutter';
 import Gdk from 'gi://Gdk';
 import GObject from 'gi://GObject';
 import Gtk from 'gi://Gtk';
diff --git a/src/transitOptionsPanel.js b/src/transitOptionsPanel.js
index ee2abaa2..49e08c0e 100644
--- a/src/transitOptionsPanel.js
+++ b/src/transitOptionsPanel.js
@@ -63,17 +63,19 @@ export class TransitOptionsPanel extends Gtk.Grid {
             this._onTransitTimeOptionsComboboxChanged.bind(this));
         this._transitTimeEntry.connect('activate',
             this._onTransitTimeEntryActivated.bind(this));
+        this._eventControllerFocus = new Gtk.EventControllerFocus();
+        this._transitTimeEntry.add_controller(this._eventControllerFocus);
         /* trigger an update of the query time as soon as focus leave the time
          * entry, to allow the user to enter a time before selecting start
          * and destination without having to press enter */
-        this._transitTimeEntry.connect('focus-out-event',
+        this._eventControllerFocus.connect('leave',
             this._onTransitTimeEntryActivated.bind(this));
-        this._transitDateButton.popover.get_child().connect('day-selected-double-click',
+        this._transitDateButton.popover.get_child().connect('day-selected',
             this._onTransitDateCalenderDaySelected.bind(this));
-        this._transitDateButton.connect('toggled',
-            this._onTransitDateButtonToogled.bind(this));
-        this._transitParametersMenuButton.connect('toggled',
-            this._onTransitParametersToggled.bind(this))
+        this._transitDateButton.popover.connect('closed',
+            this._onTransitDateClosed.bind(this));
+        this._transitParametersMenuButton.popover.connect('closed',
+            this._onTransitParametersClosed.bind(this))
     }
 
     _onTransitTimeOptionsComboboxChanged() {
@@ -153,9 +155,8 @@ export class TransitOptionsPanel extends Gtk.Grid {
         }
     }
 
-    _onTransitDateButtonToogled() {
-        if (!this._transitDateButton.active)
-            this._onTransitDateCalenderDaySelected();
+    _onTransitDateClosed() {
+        this._onTransitDateCalenderDaySelected();
     }
 
     _createTransitOptions() {
@@ -188,14 +189,12 @@ export class TransitOptionsPanel extends Gtk.Grid {
         return options;
     }
 
-    _onTransitParametersToggled() {
-        if (!this._transitParametersMenuButton.active) {
-            let options = this._createTransitOptions();
+    _onTransitParametersClosed() {
+        let options = this._createTransitOptions();
 
-            if (!TransitOptions.equals(options, this._lastOptions)) {
-                this._query.transitOptions = options;
-                this._lastOptions = options;
-            }
+        if (!TransitOptions.equals(options, this._lastOptions)) {
+            this._query.transitOptions = options;
+            this._lastOptions = options;
         }
     }
  }
diff --git a/src/transitPrintLayout.js b/src/transitPrintLayout.js
index 1339b62e..cd05a505 100644
--- a/src/transitPrintLayout.js
+++ b/src/transitPrintLayout.js
@@ -20,10 +20,10 @@
  */
 
 import Cairo from 'cairo';
-import Champlain from 'gi://Champlain';
-import Clutter from 'gi://Clutter';
+import Gdk from 'gi://Gdk';
 import GObject from 'gi://GObject';
 import Pango from 'gi://Pango';
+import Shumate from 'gi://Shumate';
 
 import * as Color from './color.js';
 import * as Gfx from './gfx.js';
@@ -35,10 +35,10 @@ import {TransitBoardMarker} from './transitBoardMarker.js';
 import {TransitWalkMarker} from './transitWalkMarker.js';
 
 // stroke color for walking paths
-const _STROKE_COLOR = new Clutter.Color({ red: 0,
-                                          blue: 0,
-                                          green: 0,
-                                          alpha: 255 });
+const _STROKE_COLOR = new Gdk.RGBA({ red: 0.0,
+                                     blue: 0.0,
+                                     green: 0.0,
+                                     alpha: 1.0 });
 const _STROKE_WIDTH = 5.0;
 
 // All following constants are ratios of surface size to page size
diff --git a/src/transitplugins/openTripPlanner.js b/src/transitplugins/openTripPlanner.js
index 9c80e910..ad2160fc 100644
--- a/src/transitplugins/openTripPlanner.js
+++ b/src/transitplugins/openTripPlanner.js
@@ -21,8 +21,8 @@
 
 import gettext from 'gettext';
 
-import Champlain from 'gi://Champlain';
 import GLib from 'gi://GLib';
+import Shumate from 'gi://Shumate';
 import Soup from 'gi://Soup';
 
 import {Application} from '../application.js';
@@ -1140,8 +1140,8 @@ export class OpenTripPlanner {
     }
 
     _createTurnpoint(step) {
-        let coordinate = new Champlain.Coordinate({ latitude: step.lat,
-                                                    longitude: step.lon });
+        let coordinate = new Shumate.Coordinate({ latitude: step.lat,
+                                                  longitude: step.lon });
         let turnpoint = new TurnPoint({
             coordinate: coordinate,
             type: this._getTurnpointType(step),
diff --git a/src/transitplugins/opendataCH.js b/src/transitplugins/opendataCH.js
index e9431005..6ccab174 100644
--- a/src/transitplugins/opendataCH.js
+++ b/src/transitplugins/opendataCH.js
@@ -27,8 +27,8 @@
  * https://transport.opendata.ch/docs.html
  */
 
-import Champlain from 'gi://Champlain';
 import GLib from 'gi://GLib';
+import Shumate from 'gi://Shumate';
 import Soup from 'gi://Soup';
 
 import {Application} from '../application.js';
@@ -391,18 +391,18 @@ export class OpendataCH {
 
             section.journey.passList.forEach((pass) => {
                 let coordinate = pass.location.coordinate;
-                polyline.push(new Champlain.Coordinate({ latitude:  coordinate.x,
-                                                         longitude: coordinate.y }));
+                polyline.push(new Shumate.Coordinate({ latitude:  coordinate.x,
+                                                       longitude: coordinate.y }));
             });
         } else {
             let [departureX, departureY, arrivalX, arrivalY] =
                 this._getCoordsForSection(section, index, sections);
 
             polyline =
-                [new Champlain.Coordinate({ latitude:  departureX,
-                                            longitude: departureY }),
-                 new Champlain.Coordinate({ latitude:  arrivalX,
-                                            longitude: arrivalY })];
+                [new Shumate.Coordinate({ latitude:  departureX,
+                                          longitude: departureY }),
+                 new Shumate.Coordinate({ latitude:  arrivalX,
+                                          longitude: arrivalY })];
         }
 
         return polyline;
diff --git a/src/transitplugins/resrobot.js b/src/transitplugins/resrobot.js
index d019640e..57931818 100644
--- a/src/transitplugins/resrobot.js
+++ b/src/transitplugins/resrobot.js
@@ -27,8 +27,8 @@
  * https://www.trafiklab.se/api/resrobot-reseplanerare/dokumentation/sokresa
  */
 
-import Champlain from 'gi://Champlain';
 import GLib from 'gi://GLib';
+import Shumate from 'gi://Shumate';
 import Soup from 'gi://Soup';
 
 import {Application} from '../application.js';
@@ -487,15 +487,15 @@ export class Resrobot {
             polyline = [];
 
             leg.Stops.Stop.forEach((stop) => {
-                polyline.push(new Champlain.Coordinate({ latitude:  stop.lat,
-                                                         longitude: stop.lon }));
+                polyline.push(new Shumate.Coordinate({ latitude:  stop.lat,
+                                                       longitude: stop.lon }));
             });
         } else {
             polyline =
-                [new Champlain.Coordinate({ latitude:  leg.Origin.lat,
-                                            longitude: leg.Origin.lon }),
-                 new Champlain.Coordinate({ latitude:  leg.Destination.lat,
-                                            longitude: leg.Destination.lon })];
+                [new Shumate.Coordinate({ latitude:  leg.Origin.lat,
+                                          longitude: leg.Origin.lon }),
+                 new Shumate.Coordinate({ latitude:  leg.Destination.lat,
+                                          longitude: leg.Destination.lon })];
         }
 
         return polyline;
diff --git a/src/turnPointMarker.js b/src/turnPointMarker.js
index 5f0d9fda..a722fd1a 100644
--- a/src/turnPointMarker.js
+++ b/src/turnPointMarker.js
@@ -19,7 +19,6 @@
  * Author: Dario Di Nucci <linkin88mail gmail com>
  */
 
-import Clutter from 'gi://Clutter';
 import Gdk from 'gi://Gdk';
 import GObject from 'gi://GObject';
 
@@ -102,7 +101,6 @@ export class TurnPointMarker extends MapMarker {
         let latitude = this.latitude;
         let longitude = this.longitude;
 
-        view.goto_animation_mode = Clutter.AnimationMode.LINEAR;
         view.goto_duration = 0;
 
         Utils.once(view, 'animation-completed', () => {
diff --git a/src/userLocationMarker.js b/src/userLocationMarker.js
index 55d9d85f..8672a0f0 100644
--- a/src/userLocationMarker.js
+++ b/src/userLocationMarker.js
@@ -19,25 +19,27 @@
  * Author: Damián Nohales <damiannohales gmail com>
  */
 
-import Champlain from 'gi://Champlain';
-import Clutter from 'gi://Clutter';
 import GObject from 'gi://GObject';
+import Shumate from 'gi://Shumate';
 
 import {MapMarker} from './mapMarker.js';
 
-export class AccuracyCircleMarker extends Champlain.Point {
+export class AccuracyCircleMarker extends Shumate.Point {
 
     constructor(params) {
         let place = params.place;
         delete params.place;
 
+        // TODO: set color using CSS...
+        /*
         params.color = new Clutter.Color({ red: 0,
                                            blue: 255,
                                            green: 0,
                                            alpha: 25 });
+        */
         params.latitude = place.location.latitude;
         params.longitude = place.location.longitude;
-        params.reactive = false;
+        params.draggable = false;
 
         super(params);
 
@@ -101,11 +103,6 @@ export class UserLocationMarker extends MapMarker {
     }
 
     _updateLocation() {
-        if (this._actor) {
-            this._actor.destroy();
-            delete this._actor;
-        }
-
         if (this.place.location.heading > -1) {
             this._actor = this._actorFromIconName('user-location-compass', 0);
             this._actor.set_pivot_point(0.5, 0.5);
diff --git a/src/utils.js b/src/utils.js
index 2cf6920b..da5da46b 100644
--- a/src/utils.js
+++ b/src/utils.js
@@ -88,9 +88,9 @@ export function once(obj, signal, callback) {
 export function loadStyleSheet(file) {
     let provider = new Gtk.CssProvider();
     provider.load_from_file(file);
-    Gtk.StyleContext.add_provider_for_screen(Gdk.Screen.get_default(),
-                                             provider,
-                                             Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION);
+    Gtk.StyleContext.add_provider_for_display(Gdk.Display.get_default(),
+                                              provider,
+                                              Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION);
 }
 
 export function addActions(actionMap, entries, settings = null) {
@@ -302,11 +302,15 @@ function _load_icon(icon, loadCompleteCallback) {
 }
 
 function _load_themed_icon(icon, size, loadCompleteCallback) {
-    let theme = Gtk.IconTheme.get_default();
-    let info = theme.lookup_by_gicon(icon, size, 0);
+    let display = Gdk.Display.get_default();
+    let theme = Gtk.IconTheme.get_for_display(display);
+    // TODO: find the scale factor?
+    let paintable = theme.lookup_by_gicon(icon, size, 1,
+                                          Gtk.TextDirection.NONE, 0);
+    let filename = paintable.file.get_path();
 
     try {
-        let pixbuf = info.load_icon();
+        let pixbuf = GdkPixbuf.Pixbuf.new_from_file(filename);
         loadCompleteCallback(pixbuf);
     } catch(e) {
         log("Failed to load pixbuf: " + e);


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