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




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

    Port to GTK 4 and libshumate
    
    Update from using GTK 3 and libchamplain
    to GTK 4 and libshumate.
    
    Some missing features so far (that was
    available in the GTK 3 version):
    
    - Minimaps when printing routes
    - The ability to select to not include
      markers and layers when exporting the
      view as an image
    - Map type preview thumbnails in the layers
      popover (though they are currently not shown
      as we only only have the street style available
      now)

 data/gnome-maps.css                   |   9 +-
 data/ui/context-menu.ui               |  85 ++---
 data/ui/export-view-dialog.ui         | 122 +++----
 data/ui/favorites-popover.ui          |  34 +-
 data/ui/headerbar-left.ui             |  71 ++--
 data/ui/headerbar-right.ui            |  28 +-
 data/ui/instruction-row.ui            |  31 +-
 data/ui/layers-popover.ui             |  96 +++--
 data/ui/main-window.ui                |  67 +---
 data/ui/osm-account-dialog.ui         | 441 ++++++++++++-----------
 data/ui/osm-edit-address.ui           |  60 ++--
 data/ui/osm-edit-dialog.ui            | 465 ++++++++++--------------
 data/ui/osm-type-list-row.ui          |  29 +-
 data/ui/osm-type-popover.ui           |  10 +-
 data/ui/place-bar.ui                  |  59 ++--
 data/ui/place-buttons.ui              |  55 +--
 data/ui/place-dialog.ui               |  13 +-
 data/ui/place-list-row.ui             |  72 ++--
 data/ui/place-popover.ui              |  31 +-
 data/ui/place-view.ui                 |  61 ++--
 data/ui/route-entry.ui                |  38 +-
 data/ui/send-to-dialog.ui             |  96 ++---
 data/ui/sidebar.ui                    | 348 ++++++++----------
 data/ui/transit-arrival-row.ui        | 122 +++----
 data/ui/transit-itinerary-row.ui      |  75 ++--
 data/ui/transit-leg-row.ui            | 339 ++++++++----------
 data/ui/transit-more-row.ui           |  51 +--
 data/ui/transit-options-panel.ui      | 113 +++---
 data/ui/transit-route-label.ui        |   4 +-
 data/ui/transit-stop-row.ui           |  52 ++-
 data/ui/zoom-in-dialog.ui             |  39 +--
 lib/maps-file-data-source.c           | 531 ++++++++++++++++++++++++++++
 lib/maps-file-data-source.h           |  73 ++++
 lib/maps-file-tile-source.c           | 640 ----------------------------------
 lib/maps-file-tile-source.h           |  73 ----
 lib/maps-sync-map-source.c            |  91 +++++
 lib/maps-sync-map-source.h            |  73 ++++
 lib/meson.build                       |  14 +-
 meson.build                           |   6 +-
 org.gnome.Maps.json                   |  47 +--
 src/application.js                    |  32 +-
 src/contextMenu.js                    |  99 +++---
 src/epaf.js                           |   4 +-
 src/exportViewDialog.js               |  83 +++--
 src/favoritesPopover.js               |  12 +-
 src/geoJSONShapeLayer.js              |   4 +
 src/geoJSONSource.js                  | 163 ++++-----
 src/gpxShapeLayer.js                  |   4 +
 src/graphHopperTransit.js             |  10 +-
 src/kmlShapeLayer.js                  |   4 +
 src/layersPopover.js                  |  26 +-
 src/main.js                           |  12 +-
 src/mainWindow.js                     | 203 +++++------
 src/mapBubble.js                      |  64 +++-
 src/mapMarker.js                      | 216 +++++-------
 src/mapSource.js                      |  75 ++--
 src/mapView.js                        | 337 +++++++++++-------
 src/mapWalker.js                      |  80 ++---
 src/osmEditDialog.js                  |  59 ++--
 src/osmTypeListRow.js                 |   3 +-
 src/osmTypePopover.js                 |  17 +-
 src/osmTypeSearchEntry.js             |  10 +-
 src/placeBar.js                       |  42 +--
 src/placeButtons.js                   |  38 +-
 src/placeDialog.js                    |   6 +-
 src/placeEntry.js                     |  44 ++-
 src/placeMarker.js                    |   4 +-
 src/placePopover.js                   |  22 +-
 src/placeView.js                      |  29 +-
 src/placeViewImage.js                 |  28 +-
 src/printLayout.js                    | 175 +++++++---
 src/printOperation.js                 |  16 +-
 src/routeEntry.js                     |  16 +-
 src/searchPopover.js                  |  54 +--
 src/sendToDialog.js                   |  22 +-
 src/shapeLayer.js                     |  20 +-
 src/shortPrintLayout.js               |   4 +-
 src/sidebar.js                        | 113 +++---
 src/storedRoute.js                    |  10 +-
 src/transitArrivalMarker.js           |  11 +-
 src/transitArrivalRow.js              |  19 +-
 src/transitBoardMarker.js             |  80 ++---
 src/transitLegRow.js                  |  34 +-
 src/transitOptionsPanel.js            |  34 +-
 src/transitPrintLayout.js             | 136 +-------
 src/transitRouteLabel.js              |  20 +-
 src/transitWalkMarker.js              |  11 +-
 src/transitplugins/openTripPlanner.js |   6 +-
 src/transitplugins/opendataCH.js      |  14 +-
 src/transitplugins/resrobot.js        |  14 +-
 src/turnPointMarker.js                |  23 +-
 src/userLocationMarker.js             |  87 +++--
 src/utils.js                          |  18 +-
 src/zoomInDialog.js                   |  11 +-
 94 files changed, 3448 insertions(+), 3894 deletions(-)
---
diff --git a/data/gnome-maps.css b/data/gnome-maps.css
index 55e60fb9..949f62e6 100644
--- a/data/gnome-maps.css
+++ b/data/gnome-maps.css
@@ -92,10 +92,10 @@
 }
 
 .route-label {
-    padding-left: 2px;
-    padding-right: 2px;
-    padding-top: 2px;
-    padding-bottom: 2px;
+    padding-left: 3px;
+    padding-right: 3px;
+    padding-top: 3px;
+    padding-bottom: 3px;
     min-width: 16px;
     min-height: 16px;
     font-size: smaller;
@@ -107,6 +107,5 @@
   min-height: 14px;
   padding: 0px 0px;
   background-color: transparent;
-  -gtk-outline-radius: 14px;
 }
 
diff --git a/data/ui/context-menu.ui b/data/ui/context-menu.ui
index 7fcb6734..07100c3e 100644
--- a/data/ui/context-menu.ui
+++ b/data/ui/context-menu.ui
@@ -1,56 +1,37 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <interface>
-  <!-- interface-requires gtk+ 3.0 -->
-  <template class="Gjs_ContextMenu" parent="GtkMenu">
-    <property name="visible">False</property>
-    <child>
-      <object class="GtkMenuItem" id="routeFromHereItem">
-        <property name="name">route-from-here-item</property>
-        <property name="label" translatable="yes">Route from here</property>
-        <property name="visible">True</property>
-      </object>
-    </child>
-    <child>
-      <object class="GtkMenuItem" id="addIntermediateDestinationItem">
-        <property name="name">add-itermediate-destination-item</property>
-        <property name="label" translatable="yes">Add intermediate destination</property>
-        <property name="visible">True</property>
-        <property name="sensitive">False</property>
-      </object>
-    </child>
-    <child>
-      <object class="GtkMenuItem" id="routeToHereItem">
-        <property name="name">route-to-here-item</property>
-        <property name="label" translatable="yes">Route to here</property>
-        <property name="visible">True</property>
-      </object>
-    </child>
-    <child>
-      <object class="GtkSeparatorMenuItem">
-        <property name="visible">True</property>
-      </object>
-    </child>
-    <child>
-      <object class="GtkMenuItem" id="whatsHereItem">
-        <property name="name">whats-here-item</property>
-        <property name="label" translatable="yes">What’s here?</property>
-        <property name="visible">True</property>
-      </object>
-    </child>
-    <child>
-      <object class="GtkMenuItem" id="geoURIItem">
-        <property name="name">geo-uri-item</property>
-        <property name="label" translatable="yes">Copy Location</property>
-        <property name="visible">True</property>
-      </object>
-    </child>
-
-    <child>
-      <object class="GtkMenuItem" id="addOSMLocationItem">
-        <property name="name">add-osm-location-item</property>
-        <property name="label" translatable="yes">Add to OpenStreetMap</property>
-        <property name="visible">True</property>
-      </object>
-    </child>
+  <menu id="context-menu">
+    <section>
+      <item>
+        <attribute name="label" translatable="yes">Route from here</attribute>
+        <attribute name="action">win.route-from-here</attribute>
+      </item>
+      <item>
+        <attribute name="label" translatable="yes">Add intermediate destination</attribute>
+        <attribute name="action">win.add-intermediate-destination</attribute>
+      </item>
+      <item>
+        <attribute name="label" translatable="yes">Route to here</attribute>
+        <attribute name="action">win.route-to-here</attribute>
+      </item>
+    </section>
+    <section>
+      <item>
+        <attribute name="label" translatable="yes">What's here?</attribute>
+        <attribute name="action">win.whats-here</attribute>
+      </item>
+      <item>
+        <attribute name="label" translatable="yes">Copy location</attribute>
+        <attribute name="action">win.copy-location</attribute>
+      </item>
+      <item>
+        <attribute name="label" translatable="yes">Add to OpenStreetMap</attribute>
+        <attribute name="action">win.add-osm-location</attribute>
+      </item>
+    </section>
+  </menu>
+  <template class="Gjs_ContextMenu" parent="GtkPopoverMenu">
+    <property name="menu-model">context-menu</property>
+    <property name="has-arrow">False</property>
   </template>
 </interface>
diff --git a/data/ui/export-view-dialog.ui b/data/ui/export-view-dialog.ui
index 040e390e..c2824de1 100644
--- a/data/ui/export-view-dialog.ui
+++ b/data/ui/export-view-dialog.ui
@@ -1,61 +1,45 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <interface>
-  <requires lib="gtk+" version="3.12"/>
+  <requires lib="gtk" version="4.0"/>
   <template class="Gjs_ExportViewDialog" parent="GtkDialog">
-    <property name="visible">False</property>
-    <property name="can_focus">False</property>
     <property name="use_header_bar">1</property>
-    <property name="resizable">False</property>
+    <property name="resizable">0</property>
+    <property name="title" translatable="1">Export view</property>
     <child internal-child="headerbar">
       <object class="GtkHeaderBar" id="headerBar">
-        <property name="visible">True</property>
-        <property name="can_focus">False</property>
-        <property name="show_close_button">False</property>
-        <property name="title" translatable="yes">Export view</property>
+        <property name="show-title-buttons">0</property>
         <style>
           <class name="titlebar"/>
         </style>
-        <child>
+        <child type="start">
           <object class="GtkButton" id="cancelButton">
-            <property name="label" translatable="yes">_Cancel</property>
-            <property name="visible">True</property>
-            <property name="can_focus">True</property>
-            <property name="use_underline">True</property>
+            <property name="label" translatable="1">_Cancel</property>
+            <property name="focusable">1</property>
+            <property name="use_underline">1</property>
             <style>
               <class name="text-button"/>
             </style>
           </object>
-          <packing>
-            <property name="pack_type">start</property>
-          </packing>
         </child>
-        <child>
+        <child type="end">
           <object class="GtkButton" id="exportButton">
-            <property name="label" translatable="yes">_Export</property>
-            <property name="visible">True</property>
-            <property name="can_focus">True</property>
-            <property name="can_default">True</property>
-            <property name="has_default">True</property>
-            <property name="receives_default">True</property>
+            <property name="label" translatable="1">_Export</property>
+            <property name="focusable">1</property>
+            <property name="receives_default">1</property>
             <property name="valign">center</property>
-            <property name="use_underline">True</property>
+            <property name="use_underline">1</property>
             <style>
               <class name="suggested-action"/>
               <class name="text-button"/>
             </style>
           </object>
-          <packing>
-            <property name="pack_type">end</property>
-          </packing>
         </child>
       </object>
     </child>
-    <child internal-child="vbox">
+    <child>
       <object class="GtkBox" id="contentArea">
         <child>
           <object class="GtkGrid" id="grid">
-            <property name="visible">True</property>
-            <property name="can_focus">False</property>
             <property name="margin_start">5</property>
             <property name="margin_end">5</property>
             <property name="margin_top">5</property>
@@ -64,72 +48,48 @@
             <property name="column_spacing">8</property>
             <child>
               <object class="GtkFrame" id="frame">
-                <property name="visible">True</property>
-                <property name="can_focus">False</property>
-                <property name="label_xalign">0</property>
-                <property name="shadow_type">out</property>
                 <property name="valign">start</property>
-                <child>
-                  <object class="GtkDrawingArea" id="previewArea">
-                    <property name="visible">True</property>
-                    <property name="can_focus">False</property>
+                <property name="child">
+                  <object class="GtkImage" id="previewArea">
+                    <property name="vexpand">True</property>
+                    <property name="hexpand">True</property>
                   </object>
-                </child>
+                </property>
+                <layout>
+                  <property name="column">0</property>
+                  <property name="row">0</property>
+                  <property name="row-span">3</property>
+                </layout>
               </object>
-              <packing>
-                <property name="left_attach">0</property>
-                <property name="top_attach">0</property>
-                <property name="width">1</property>
-                <property name="height">3</property>
-              </packing>
             </child>
             <child>
               <object class="GtkEntry" id="filenameEntry">
-                <property name="visible">True</property>
-                <property name="can_focus">True</property>
+                <property name="focusable">1</property>
                 <property name="valign">start</property>
-                <property name="activates_default">True</property>
+                <property name="activates_default">1</property>
                 <property name="width_chars">32</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>
-                <property name="width">1</property>
-                <property name="height">1</property>
-              </packing>
             </child>
             <child>
-              <object class="GtkFileChooserButton" id="fileChooserButton">
-                <property name="visible">True</property>
+              <object class="GtkButton" id="fileChooserButton">
                 <property name="can_focus">False</property>
                 <property name="valign">start</property>
                 <property name="vexpand">True</property>
-                <property name="action">select-folder</property>
-                <property name="local_only">False</property>
-              </object>
-              <packing>
-                <property name="left_attach">2</property>
-                <property name="top_attach">1</property>
-                <property name="width">1</property>
-                <property name="height">1</property>
-              </packing>
-            </child>
-            <child>
-              <object class="GtkCheckButton" id="layersCheckButton">
-                <property name="label" translatable="yes">Include route and markers</property>
-                <property name="visible">True</property>
-                <property name="can_focus">True</property>
-                <property name="receives_default">False</property>
-                <property name="xalign">0</property>
-                <property name="draw_indicator">True</property>
-                <property name="active">True</property>
+                <child>
+                  <object class="GtkImage" id="favorites-button-image">
+                    <property name="icon-size">normal</property>
+                    <property name="icon-name">folder-open-symbolic</property>
+                  </object>
+                </child>
+                <layout>
+                  <property name="column">2</property>
+                  <property name="row">1</property>
+                </layout>
               </object>
-              <packing>
-                <property name="left_attach">2</property>
-                <property name="top_attach">2</property>
-                <property name="width">1</property>
-                <property name="height">1</property>
-              </packing>
             </child>
           </object>
         </child>
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/instruction-row.ui b/data/ui/instruction-row.ui
index 1e3cf1d1..92bf52f5 100644
--- a/data/ui/instruction-row.ui
+++ b/data/ui/instruction-row.ui
@@ -1,24 +1,19 @@
-<?xml version="1.0" encoding="utf-8"?>
+<?xml version="1.0" encoding="UTF-8"?>
 <interface>
-  <requires lib="gtk+" version="3.14"/>
+  <requires lib="gtk" version="4.0"/>
   <template class="Gjs_InstructionRow" parent="GtkListBoxRow">
-    <property name="visible">True</property>
-    <child>
+    <property name="child">
       <object class="GtkBox" id="instructionBox">
         <property name="name">instruction-box</property>
         <property name="height_request">48</property>
-        <property name="visible">True</property>
-        <property name="can_focus">False</property>
         <property name="spacing">6</property>
         <property name="baseline_position">top</property>
         <child>
           <object class="GtkImage" id="directionImage">
-            <property name="visible">True</property>
-            <property name="can_focus">False</property>
             <property name="margin-start">2</property>
             <property name="margin-end">2</property>
-            <!-- width: 32 + spacing * 2 -->
             <property name="width-request">44</property>
+            <property name="icon-size">GTK_ICON_SIZE_LARGE</property>
             <style>
               <class name="sidebar-icon"/>
             </style>
@@ -26,35 +21,29 @@
         </child>
         <child>
           <object class="GtkLabel" id="instructionLabel">
-            <property name="visible">True</property>
-            <property name="can_focus">False</property>
             <property name="halign">start</property>
-            <!-- Somehow we need this xalign, otherwise multi-line -->
-            <!-- labels does not get left-aligned. -->
             <property name="xalign">0</property>
             <property name="margin-top">3</property>
             <property name="margin-bottom">3</property>
-            <property name="use_underline">True</property>
-            <property name="wrap">True</property>
+            <property name="use_underline">1</property>
+            <property name="wrap">1</property>
             <property name="ellipsize">end</property>
             <property name="width_chars">20</property>
             <property name="max_width_chars">20</property>
             <property name="lines">3</property>
-            <property name="expand">True</property>
+            <property name="hexpand">1</property>
+            <property name="vexpand">1</property>
           </object>
         </child>
         <child>
           <object class="GtkLabel" id="distanceLabel">
-            <property name="visible">True</property>
-            <property name="can_focus">False</property>
             <property name="halign">end</property>
-            <property name="use_underline">True</property>
-            <property name="wrap">True</property>
+            <property name="use_underline">1</property>
             <property name="lines">3</property>
             <property name="margin_end">5</property>
           </object>
         </child>
       </object>
-    </child>
+    </property>
   </template>
 </interface>
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..33bea934 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>
-          <object class="GtkMenuButton">
-            <property name="visible">True</property>
+        <child type="end">
+          <object class="GtkMenuButton" id="mainMenuButton">
             <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/osm-account-dialog.ui b/data/ui/osm-account-dialog.ui
index ee30d3d9..31d636ee 100644
--- a/data/ui/osm-account-dialog.ui
+++ b/data/ui/osm-account-dialog.ui
@@ -1,247 +1,240 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <interface>
-  <requires lib="gtk+" version="3.12"/>
+  <requires lib="gtk" version="4.0"/>
   <template class="Gjs_OSMAccountDialog" parent="GtkDialog">
-    <property name="can_focus">False</property>
-    <property name="type">popup</property>
-    <property name="type_hint">dialog</property>
     <property name="width_request">500</property>
-    <property name="title" translatable="yes">OpenStreetMap Account</property>
-    <child internal-child="vbox">
+    <property name="title" translatable="1">OpenStreetMap Account</property>
+    <child>
       <object class="GtkBox" id="contentArea">
         <child>
           <object class="GtkStack" id="stack">
-            <property name="visible">True</property>
-            <property name="transition-type">GTK_STACK_TRANSITION_TYPE_SLIDE_RIGHT</property>
+            <property name="transition-type">slide-right</property>
             <child>
-              <object class="GtkGrid">
-                <property name="visible">True</property>
-                <property name="row-spacing">10</property>
-                <property name="margin">20</property>
-                <child>
-                  <object class="GtkLabel">
-                    <property name="label" translatable="yes">&lt;span weight="bold" size="x-large"&gt;Sign 
in to edit maps&lt;/span&gt;</property>
-                    <property name="use_markup">True</property>
-                    <property name="visible">True</property>
-                    <property name="hexpand">True</property>
-                    <property name="halign">GTK_ALIGN_CENTER</property>
-                    <property name="justify">GTK_JUSTIFY_CENTER</property>
-                  </object>
-                  <packing>
-                    <property name="left_attach">0</property>
-                    <property name="top_attach">0</property>
-                    <property name="width">3</property>
-                  </packing>
-                </child>
-                <child>
-                  <object class="GtkLabel">
-                    <property name="label" translatable="yes">Help to improve the map, using an
+              <object class="GtkStackPage">
+                <property name="name">sign-in</property>
+                <property name="child">
+                  <object class="GtkGrid">
+                    <property name="row-spacing">10</property>
+                    <property name="margin-start">20</property>
+                    <property name="margin-end">20</property>
+                    <property name="margin-top">20</property>
+                    <property name="margin-bottom">20</property>
+                    <child>
+                      <object class="GtkLabel">
+                        <property name="label" translatable="1">&lt;span weight=&quot;bold&quot; 
size=&quot;x-large&quot;&gt;Sign in to edit maps&lt;/span&gt;</property>
+                        <property name="use_markup">1</property>
+                        <property name="hexpand">1</property>
+                        <property name="halign">center</property>
+                        <property name="justify">center</property>
+                        <layout>
+                          <property name="column">0</property>
+                          <property name="row">0</property>
+                          <property name="column-span">3</property>
+                        </layout>
+                      </object>
+                    </child>
+                    <child>
+                      <object class="GtkLabel">
+                        <property name="label" translatable="1">Help to improve the map, using an
 OpenStreetMap account.</property>
-                    <property name="visible">True</property>
-                    <property name="hexpand">True</property>
-                    <property name="halign">GTK_ALIGN_CENTER</property>
-                    <property name="justify">GTK_JUSTIFY_CENTER</property>
-                  </object>
-                  <packing>
-                    <property name="left_attach">0</property>
-                    <property name="top_attach">1</property>
-                    <property name="width">3</property>
-                  </packing>
-                </child>
-                <child>
-                  <object class="GtkLabel">
-                    <property name="label" translatable="yes">Sign in to authorize access in a web browser.
+                        <property name="hexpand">1</property>
+                        <property name="halign">center</property>
+                        <property name="justify">center</property>
+                        <layout>
+                          <property name="column">0</property>
+                          <property name="row">1</property>
+                          <property name="column-span">3</property>
+                        </layout>
+                      </object>
+                    </child>
+                    <child>
+                      <object class="GtkLabel">
+                        <property name="label" translatable="1">Sign in to authorize access in a web browser.
 Then fill in the obtained verification code here in the next step.</property>
-                    <property name="visible">True</property>
-                    <property name="hexpand">True</property>
-                    <property name="wrap">True</property>
-                    <property name="halign">GTK_ALIGN_CENTER</property>
-                    <property name="justify">GTK_JUSTIFY_CENTER</property>
-                  </object>
-                  <packing>
-                    <property name="left_attach">0</property>
-                    <property name="top_attach">2</property>
-                    <property name="width">3</property>
-                  </packing>
-                </child>
-                <child>
-                  <object class="GtkSpinner" id="signInSpinner">
-                    <property name="visible">False</property>
-                    <property name="height_request">16</property>
-                    <property name="width_request">16</property>
-                    <property name="can_focus">False</property>
-                    <property name="active">True</property>
-                    <property name="halign">GTK_ALIGN_END</property>
-                    <property name="hexpand">True</property>
-                  </object>
-                  <packing>
-                    <property name="left_attach">0</property>
-                    <property name="top_attach">3</property>
-                  </packing>
-                </child>
-                <child>
-                  <object class="GtkLinkButton">
-                    <property name="visible">True</property>
-                    <property name="can_focus">True</property>
-                    <property name="label" translatable="yes">Sign up</property>
-                    <property name="uri">https://www.openstreetmap.org/user/new</property>
-                    <property name="halign">GTK_ALIGN_END</property>
-                    <property name="hexpand">True</property>
+                        <property name="hexpand">1</property>
+                        <property name="wrap">1</property>
+                        <property name="halign">center</property>
+                        <property name="justify">center</property>
+                        <layout>
+                          <property name="column">0</property>
+                          <property name="row">2</property>
+                          <property name="column-span">3</property>
+                        </layout>
+                      </object>
+                    </child>
+                    <child>
+                      <object class="GtkSpinner" id="signInSpinner">
+                        <property name="height_request">16</property>
+                        <property name="width_request">16</property>
+                        <property name="spinning">True</property>
+                        <property name="halign">end</property>
+                        <property name="hexpand">1</property>
+                        <layout>
+                          <property name="column">0</property>
+                          <property name="row">3</property>
+                        </layout>
+                      </object>
+                    </child>
+                    <child>
+                      <object class="GtkLinkButton">
+                        <property name="focusable">1</property>
+                        <property name="label" translatable="1">Sign up</property>
+                        <property name="uri">https://www.openstreetmap.org/user/new</property>
+                        <property name="halign">end</property>
+                        <property name="hexpand">1</property>
+                        <layout>
+                          <property name="column">1</property>
+                          <property name="row">3</property>
+                        </layout>
+                      </object>
+                    </child>
+                    <child>
+                      <object class="GtkButton" id="signInButton">
+                        <property name="halign">end</property>
+                        <property name="label" translatable="1">Sign In</property>
+                        <style>
+                          <class name="suggested-action"/>
+                        </style>
+                        <layout>
+                          <property name="column">2</property>
+                          <property name="row">3</property>
+                        </layout>
+                      </object>
+                    </child>
+                    <child>
+                      <object class="GtkLabel" id="errorLabel">
+                        <property name="visible">0</property>
+                        <property name="focusable">1</property>
+                        <property name="use-markup">1</property>
+                        <style>
+                          <class name="warning"/>
+                        </style>
+                        <layout>
+                          <property name="column">0</property>
+                          <property name="row">4</property>
+                        </layout>
+                      </object>
+                    </child>
                   </object>
-                  <packing>
-                    <property name="left_attach">1</property>
-                    <property name="top_attach">3</property>
-                  </packing>
-                </child>
-                <child>
-                  <object class="GtkButton" id="signInButton">
-                    <property name="visible">True</property>
-                    <property name="halign">GTK_ALIGN_END</property>
-                    <property name="label" translatable="yes">Sign In</property>
-                    <style>
-                      <class name="suggested-action"/>
-                    </style>
-                  </object>
-                  <packing>
-                    <property name="left_attach">2</property>
-                    <property name="top_attach">3</property>
-                  </packing>
-                </child>
-                <child>
-                  <object class="GtkLabel" id="errorLabel">
-                    <property name="visible">False</property>
-                    <property name="can_focus">True</property>
-                    <property name="use-markup">True</property>
-                    <style>
-                      <class name="warning"/>
-                    </style>
-                  </object>
-                  <packing>
-                    <property name="left_attach">0</property>
-                    <property name="top_attach">4</property>
-                  </packing>
-                </child>
+                </property>
               </object>
-              <packing>
-                <property name="name">sign-in</property>
-              </packing>
             </child>
             <child>
-              <object class="GtkGrid">
-                <property name="visible">True</property>
-                <property name="row-spacing">10</property>
-                <property name="column-spacing">10</property>
-                <property name="margin">20</property>
-                <child>
-                  <object class="GtkLabel">
-                    <property name="visible">True</property>
-                    <property name="wrap">True</property>
-                    <property name="label" translatable="yes">Copy verification code shown when authorizing 
access in the browser</property>
-                  </object>
-                  <packing>
-                    <property name="left-attach">0</property>
-                    <property name="top-attach">0</property>
-                    <property name="width">2</property>
-                  </packing>
-                </child>
-                <child>
-                  <object class="GtkEntry" id="verificationEntry">
-                    <property name="visible">True</property>
-                    <property name="placeholder-text" translatable="yes">Verification code</property>
-                    <property name="hexpand">True</property>
-                  </object>
-                  <packing>
-                    <property name="left_attach">0</property>
-                    <property name="top_attach">1</property>
-                  </packing>
-                </child>
-                <child>
-                  <object class="GtkButton" id="verifyButton">
-                    <property name="visible">True</property>
-                    <property name="sensitive">False</property>
-                    <property name="label" translatable="yes">Verify</property>
-                    <property name="hexpand">False</property>
-                    <property name="halign">GTK_ALIGN_END</property>
-                    <style>
-                      <class name="suggested-action"/>
-                    </style>
+              <object class="GtkStackPage">
+                <property name="name">verify</property>
+                <property name="child">
+                  <object class="GtkGrid">
+                    <property name="row-spacing">10</property>
+                    <property name="column-spacing">10</property>
+                    <property name="margin-start">20</property>
+                    <property name="margin-end">20</property>
+                    <property name="margin-top">20</property>
+                    <property name="margin-bottom">20</property>
+                    <child>
+                      <object class="GtkLabel">
+                        <property name="wrap">1</property>
+                        <property name="label" translatable="1">Copy verification code shown when 
authorizing access in the browser</property>
+                        <layout>
+                          <property name="column">0</property>
+                          <property name="row">0</property>
+                          <property name="column-span">2</property>
+                        </layout>
+                      </object>
+                    </child>
+                    <child>
+                      <object class="GtkEntry" id="verificationEntry">
+                        <property name="placeholder-text" translatable="1">Verification code</property>
+                        <property name="hexpand">1</property>
+                        <layout>
+                          <property name="column">0</property>
+                          <property name="row">1</property>
+                        </layout>
+                      </object>
+                    </child>
+                    <child>
+                      <object class="GtkButton" id="verifyButton">
+                        <property name="sensitive">0</property>
+                        <property name="label" translatable="1">Verify</property>
+                        <property name="hexpand">0</property>
+                        <property name="halign">end</property>
+                        <style>
+                          <class name="suggested-action"/>
+                        </style>
+                        <layout>
+                          <property name="column">1</property>
+                          <property name="row">1</property>
+                        </layout>
+                      </object>
+                    </child>
                   </object>
-                  <packing>
-                    <property name="left_attach">1</property>
-                    <property name="top-attach">1</property>
-                  </packing>
-                </child>
+                </property>
               </object>
-              <packing>
-                <property name="name">verify</property>
-              </packing>
             </child>
             <child>
-              <object class="GtkGrid">
-                <property name="visible">True</property>
-                <property name="row-spacing">10</property>
-                <property name="margin">20</property>
-                <child>
-                  <object class="GtkLabel">
-                    <property name="label" translatable="yes">&lt;span weight="bold" 
size="x-large"&gt;Signed In&lt;/span&gt;</property>
-                    <property name="use_markup">True</property>
-                    <property name="visible">True</property>
-                    <property name="hexpand">True</property>
-                    <property name="halign">GTK_ALIGN_CENTER</property>
-                  </object>
-                  <packing>
-                    <property name="left_attach">0</property>
-                    <property name="top_attach">0</property>
-                  </packing>
-                </child>
-                <child>
-                  <object class="GtkLabel">
-                    <property name="label" translatable="yes">Your OpenStreetMap account is 
active.</property>
-                    <property name="visible">True</property>
-                    <property name="hexpand">True</property>
-                    <property name="halign">GTK_ALIGN_CENTER</property>
-                  </object>
-                  <packing>
-                    <property name="left_attach">0</property>
-                    <property name="top_attach">1</property>
-                  </packing>
-                </child>
-                <child>
-                  <object class="GtkImage">
-                    <property name="visible">True</property>
-                    <property name="icon-name">avatar-default-symbolic</property>
-                    <property name="pixel-size">64</property>
-                    <property name="opacity">0.33</property>
-                  </object>
-                  <packing>
-                    <property name="left_attach">0</property>
-                    <property name="top_attach">2</property>
-                  </packing>
-                </child>
-                <child>
-                  <object class="GtkLabel" id="signedInUserLabel">
-                    <property name="visible">True</property>
-                  </object>
-                  <packing>
-                    <property name="left_attach">0</property>
-                    <property name="top_attach">3</property>
-                  </packing>
-                </child>
-                <child>
-                  <object class="GtkButton" id="signOutButton">
-                    <property name="visible">True</property>
-                    <property name="label" translatable="yes">Sign Out</property>
-                    <property name="halign">GTK_ALIGN_CENTER</property>
+              <object class="GtkStackPage">
+                <property name="name">logged-in</property>
+                <property name="child">
+                  <object class="GtkGrid">
+                    <property name="row-spacing">10</property>
+                    <property name="margin-start">20</property>
+                    <property name="margin-end">20</property>
+                    <property name="margin-top">20</property>
+                    <property name="margin-bottom">20</property>
+                    <child>
+                      <object class="GtkLabel">
+                        <property name="label" translatable="1">&lt;span weight=&quot;bold&quot; 
size=&quot;x-large&quot;&gt;Signed In&lt;/span&gt;</property>
+                        <property name="use_markup">1</property>
+                        <property name="hexpand">1</property>
+                        <property name="halign">center</property>
+                        <layout>
+                          <property name="column">0</property>
+                          <property name="row">0</property>
+                        </layout>
+                      </object>
+                    </child>
+                    <child>
+                      <object class="GtkLabel">
+                        <property name="label" translatable="1">Your OpenStreetMap account is 
active.</property>
+                        <property name="hexpand">1</property>
+                        <property name="halign">center</property>
+                        <layout>
+                          <property name="column">0</property>
+                          <property name="row">1</property>
+                        </layout>
+                      </object>
+                    </child>
+                    <child>
+                      <object class="GtkImage">
+                        <property name="icon-name">avatar-default-symbolic</property>
+                        <property name="pixel-size">64</property>
+                        <property name="opacity">0.33</property>
+                        <layout>
+                          <property name="column">0</property>
+                          <property name="row">2</property>
+                        </layout>
+                      </object>
+                    </child>
+                    <child>
+                      <object class="GtkLabel" id="signedInUserLabel">
+                        <layout>
+                          <property name="column">0</property>
+                          <property name="row">3</property>
+                        </layout>
+                      </object>
+                    </child>
+                    <child>
+                      <object class="GtkButton" id="signOutButton">
+                        <property name="label" translatable="1">Sign Out</property>
+                        <property name="halign">center</property>
+                        <layout>
+                          <property name="column">0</property>
+                          <property name="row">4</property>
+                        </layout>
+                      </object>
+                    </child>
                   </object>
-                  <packing>
-                    <property name="left_attach">0</property>
-                    <property name="top_attach">4</property>
-                  </packing>
-                </child>
+                </property>
               </object>
-              <packing>
-                <property name="name">logged-in</property>
-              </packing>
             </child>
           </object>
         </child>
diff --git a/data/ui/osm-edit-address.ui b/data/ui/osm-edit-address.ui
index 1a36f87a..b69c42f5 100644
--- a/data/ui/osm-edit-address.ui
+++ b/data/ui/osm-edit-address.ui
@@ -1,59 +1,53 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <interface>
-  <requires lib="gtk+" version="3.12"/>
+  <requires lib="gtk" version="4.0"/>
   <template class="Gjs_OSMEditAddress" parent="GtkGrid">
-    <property name="visible">True</property>
-    <property name="can_focus">False</property>
     <style>
       <class name="linked"/>
     </style>
     <child>
       <object class="GtkEntry" id="street">
-        <property name="visible">True</property>
-        <property name="hexpand">True</property>
-        <property name="placeholder_text" translatable="yes">Street</property>
+        <property name="hexpand">1</property>
+        <property name="placeholder_text" translatable="1">Street</property>
+        <layout>
+          <property name="column">0</property>
+          <property name="row">0</property>
+          <property name="column-span">2</property>
+        </layout>
       </object>
-      <packing>
-        <property name="left_attach">0</property>
-        <property name="top_attach">0</property>
-        <property name="width">2</property>
-      </packing>
     </child>
     <child>
       <object class="GtkEntry" id="number">
-        <property name="visible">True</property>
-        <property name="hexpand">False</property>
-        <property name="placeholder_text" translatable="yes">House number</property>
+        <property name="hexpand">0</property>
+        <property name="placeholder_text" translatable="1">House number</property>
         <property name="width_chars">3</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="GtkEntry" id="post">
-        <property name="visible">True</property>
-        <property name="hexpand">False</property>
-        <property name="placeholder_text" translatable="yes">Postal code</property>
+        <property name="hexpand">0</property>
+        <property name="placeholder_text" translatable="1">Postal code</property>
         <property name="width_chars">5</property>
+        <layout>
+          <property name="column">0</property>
+          <property name="row">1</property>
+        </layout>
       </object>
-      <packing>
-        <property name="left_attach">0</property>
-        <property name="top_attach">1</property>
-      </packing>
     </child>
     <child>
       <object class="GtkEntry" id="city">
-        <property name="visible">True</property>
-        <property name="hexpand">True</property>
-        <property name="placeholder_text" translatable="yes" comments="This is the place name as it would be 
written in a postal address (typically coming after the postal code)">City</property>
+        <property name="hexpand">1</property>
+        <property name="placeholder_text" translatable="1" comments="This is the place name as it would be 
written in a postal address (typically coming after the postal code)">City</property>
+        <layout>
+          <property name="column">1</property>
+          <property name="row">1</property>
+          <property name="column-span">2</property>
+        </layout>
       </object>
-      <packing>
-        <property name="left_attach">1</property>
-        <property name="top_attach">1</property>
-        <property name="width">2</property>
-      </packing>
     </child>
   </template>
 </interface>
diff --git a/data/ui/osm-edit-dialog.ui b/data/ui/osm-edit-dialog.ui
index ba059ee6..07742386 100644
--- a/data/ui/osm-edit-dialog.ui
+++ b/data/ui/osm-edit-dialog.ui
@@ -1,139 +1,120 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <interface>
-  <requires lib="gtk+" version="3.12"/>
+  <requires lib="gtk" version="4.0"/>
   <template class="Gjs_OSMEditDialog" parent="GtkDialog">
-    <property name="can_focus">False</property>
-    <property name="type">popup</property>
-    <property name="type_hint">dialog</property>
     <property name="width_request">500</property>
     <property name="height_request">500</property>
-    <child internal-child="vbox">
+    <property name="use-header-bar">True</property>
+    <property name="title" translatable="True" context="dialog title">Edit on OpenStreetMap</property>
+    <child>
       <object class="GtkBox" id="contentArea">
         <child>
           <object class="GtkStack" id="stack">
-            <property name="visible">True</property>
-            <property name="can_focus">False</property>
-            <property name="homogeneous">True</property>
             <property name="transition_type">crossfade</property>
             <child>
-              <object class="GtkGrid" id="loadingGrid">
-                <property name="visible">True</property>
-                <property name="can_focus">False</property>
-                <child>
-                  <object class="GtkSpinner" id="loadingSpinner">
-                    <property name="height_request">32</property>
-                    <property name="visible">True</property>
-                    <property name="can_focus">False</property>
-                    <property name="hexpand">True</property>
-                    <property name="vexpand">True</property>
-                    <property name="active">True</property>
-                  </object>
-                  <packing>
-                    <property name="left_attach">0</property>
-                    <property name="top_attach">0</property>
-                  </packing>
-                </child>
-              </object>
-              <packing>
+              <object class="GtkStackPage">
                 <property name="name">loading</property>
-              </packing>
-            </child>
-            <child>
-              <object class="GtkGrid">
-                <property name="visible">True</property>
-                <property name="can_focus">False</property>
-                <property name="orientation">vertical</property>
-                <property name="margin">20</property>
-                <child>
-                  <object class="GtkGrid" id="editorGrid">
-                    <property name="visible">True</property>
-                    <property name="can_focus">False</property>
-                    <property name="row-spacing">12</property>
-                    <property name="column-spacing">6</property>
-                    <property name="margin-bottom">12</property>
+                <property name="child">
+                  <object class="GtkGrid" id="loadingGrid">
                     <child>
-                      <object class="GtkLabel" id="typeLabel">
-                        <property name="visible">False</property>
-                        <property name="can_focus">False</property>
-                        <property name="label" translatable="yes">Type</property>
-                        <property name="halign">GTK_ALIGN_END</property>
-                        <style>
-                          <class name="dim-label"/>
-                        </style>
+                      <object class="GtkSpinner" id="loadingSpinner">
+                        <property name="height_request">32</property>
+                        <property name="hexpand">1</property>
+                        <property name="vexpand">1</property>
+                        <property name="spinning">True</property>
+                        <layout>
+                          <property name="column">0</property>
+                          <property name="row">0</property>
+                        </layout>
                       </object>
-                      <packing>
-                        <property name="left_attach">0</property>
-                        <property name="top_attach">0</property>
-                      </packing>
                     </child>
+                  </object>
+                </property>
+              </object>
+            </child>
+            <child>
+              <object class="GtkStackPage">
+                <property name="name">editor</property>
+                <property name="child">
+                  <object class="GtkGrid">
+                    <property name="orientation">vertical</property>
+                    <property name="margin-start">20</property>
+                    <property name="margin-end">20</property>
+                    <property name="margin-top">20</property>
+                    <property name="margin-bottom">20</property>
                     <child>
-                      <object class="GtkButton" id="typeButton">
-                        <property name="visible">False</property>
-                        <property name="can_focus">True</property>
-                        <property name="hexpand">True</property>
+                      <object class="GtkGrid" id="editorGrid">
+                        <property name="row-spacing">12</property>
+                        <property name="column-spacing">6</property>
+                        <property name="margin-bottom">12</property>
                         <child>
-                          <object class="GtkGrid">
-                            <property name="visible">True</property>
-                            <property name="can_focus">False</property>
-                            <property name="row-spacing">5</property>
-                            <property name="column-spacing">5</property>
-                            <child>
-                              <object class="GtkLabel" id="typeValueLabel">
-                                <property name="visible">True</property>
-                                <property name="can_focus">False</property>
-                                <property name="label" translatable="yes">None</property>
-                              </object>
-                            </child>
+                          <object class="GtkLabel" id="typeLabel">
+                            <property name="visible">0</property>
+                            <property name="label" translatable="1">Type</property>
+                            <property name="halign">end</property>
+                            <style>
+                              <class name="dim-label"/>
+                            </style>
+                            <layout>
+                              <property name="column">0</property>
+                              <property name="row">0</property>
+                            </layout>
+                          </object>
+                        </child>
+                        <child>
+                          <object class="GtkButton" id="typeButton">
+                            <property name="visible">0</property>
+                            <property name="focusable">1</property>
+                            <property name="hexpand">1</property>
                             <child>
-                              <object class="GtkImage">
-                                <property name="visible">True</property>
-                                <property name="can_focus">False</property>
-                                <property name="halign">GTK_ALIGN_END</property>
-                                <property name="hexpand">True</property>
-                                <property name="icon-name">go-next-symbolic</property>
+                              <object class="GtkGrid">
+                                <property name="row-spacing">5</property>
+                                <property name="column-spacing">5</property>
+                                <child>
+                                  <object class="GtkLabel" id="typeValueLabel">
+                                    <property name="label" translatable="1">None</property>
+                                  </object>
+                                </child>
+                                <child>
+                                  <object class="GtkImage">
+                                    <property name="halign">end</property>
+                                    <property name="hexpand">1</property>
+                                    <property name="icon-name">go-next-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>
-                  </object>
-                </child>
-                <child>
-                  <object class="GtkGrid">
-                    <property name="visible">True</property>
-                    <property name="can_focus">False</property>
-                    <property name="vexpand">True</property>
-                    <property name="valign">GTK_ALIGN_END</property>
                     <child>
-                      <object class="GtkMenuButton" id="addFieldButton">
-                        <property name="visible">True</property>
-                        <property name="can_focus">True</property>
-                        <property name="receives_default">False</property>
-                        <property name="popover">addFieldPopover</property>
-                        <property name="direction">GTK_ARROW_UP</property>
+                      <object class="GtkGrid">
+                        <property name="vexpand">1</property>
+                        <property name="valign">end</property>
                         <child>
-                          <object class="GtkGrid">
-                            <property name="visible">True</property>
-                            <property name="can_focus">False</property>
-                            <property name="row-spacing">5</property>
-                            <property name="column-spacing">5</property>
+                          <object class="GtkMenuButton" id="addFieldButton">
+                            <property name="focusable">1</property>
+                            <property name="popover">addFieldPopover</property>
+                            <property name="direction">up</property>
                             <child>
-                              <object class="GtkLabel">
-                                <property name="visible">True</property>
-                                <property name="can_focus">False</property>
-                                <property name="label" translatable="yes">Add Field</property>
-                              </object>
-                            </child>
-                            <child>
-                              <object class="GtkImage">
-                                <property name="visible">True</property>
-                                <property name="can_focus">False</property>
-                                <property name="icon-name">go-up-symbolic</property>
+                              <object class="GtkGrid">
+                                <property name="row-spacing">5</property>
+                                <property name="column-spacing">5</property>
+                                <child>
+                                  <object class="GtkLabel">
+                                    <property name="label" translatable="1">Add Field</property>
+                                  </object>
+                                </child>
+                                <child>
+                                  <object class="GtkImage">
+                                    <property name="icon-name">go-up-symbolic</property>
+                                  </object>
+                                </child>
                               </object>
                             </child>
                           </object>
@@ -141,132 +122,104 @@
                       </object>
                     </child>
                   </object>
-                </child>
+                </property>
               </object>
-              <packing>
-                <property name="name">editor</property>
-              </packing>
             </child>
             <child>
-              <object class="GtkGrid" id="uploadGrid">
-                <property name="visible">True</property>
-                <property name="can_focus">False</property>
-                <property name="margin_start">15</property>
-                <property name="margin_end">15</property>
-                <property name="margin_top">15</property>
-                <property name="margin_bottom">15</property>
-                <property name="row-spacing">5</property>
-                <child>
-                  <object class="GtkLabel" id="commentLabel">
-                    <property name="visible">True</property>
-                    <property name="can_focus">False</property>
-                    <property name="label" translatable="yes">Comment</property>
-                    <property name="halign">GTK_ALIGN_START</property>
-                    <style>
-                      <class name="dim-label"/>
-                    </style>
-                  </object>
-                  <packing>
-                    <property name="left_attach">0</property>
-                    <property name="top_attach">1</property>
-                  </packing>
-                </child>
-                <child>
-                  <object class="GtkFrame">
-                    <property name="visible">True</property>
+              <object class="GtkStackPage">
+                <property name="name">upload</property>
+                <property name="child">
+                  <object class="GtkGrid" id="uploadGrid">
+                    <property name="margin_start">15</property>
+                    <property name="margin_end">15</property>
+                    <property name="margin_top">15</property>
+                    <property name="margin_bottom">15</property>
+                    <property name="row-spacing">5</property>
                     <child>
-                      <object class="GtkTextView" id="commentTextView">
-                        <property name="visible">True</property>
-                        <property name="can_focus">True</property>
-                        <property name="hexpand">True</property>
-                        <property name="vexpand">True</property>
+                      <object class="GtkLabel" id="commentLabel">
+                        <property name="label" translatable="1">Comment</property>
+                        <property name="halign">start</property>
+                        <style>
+                          <class name="dim-label"/>
+                        </style>
+                        <layout>
+                          <property name="column">0</property>
+                          <property name="row">1</property>
+                        </layout>
                       </object>
                     </child>
-                  </object>
-                  <packing>
-                    <property name="left_attach">0</property>
-                    <property name="top_attach">2</property>
-                  </packing>
-                </child>
-                <child>
-                  <object class="GtkLabel" id="uploadInfoLabel">
-                    <property name="visible">True</property>
-                    <property name="can_focus">False</property>
-                    <property name="label" translatable="yes">Map changes will be visible on all maps that 
use
+                    <child>
+                      <object class="GtkFrame">
+                        <property name="child">
+                          <object class="GtkTextView" id="commentTextView">
+                            <property name="focusable">1</property>
+                            <property name="hexpand">1</property>
+                            <property name="vexpand">1</property>
+                          </object>
+                        </property>
+                        <layout>
+                          <property name="column">0</property>
+                          <property name="row">2</property>
+                        </layout>
+                      </object>
+                    </child>
+                    <child>
+                      <object class="GtkLabel" id="uploadInfoLabel">
+                        <property name="label" translatable="1">Map changes will be visible on all maps that 
use
 OpenStreetMap data.</property>
-                    <property name="halign">GTK_ALIGN_START</property>
-                    <style>
-                      <class name="dim-label"/>
-                    </style>
+                        <property name="halign">start</property>
+                        <style>
+                          <class name="dim-label"/>
+                        </style>
+                        <layout>
+                          <property name="column">0</property>
+                          <property name="row">3</property>
+                        </layout>
+                      </object>
+                    </child>
                   </object>
-                  <packing>
-                    <property name="left_attach">0</property>
-                    <property name="top_attach">3</property>
-                  </packing>
-                </child>
+                </property>
               </object>
-              <packing>
-                <property name="name">upload</property>
-              </packing>
             </child>
             <child>
-              <object class="GtkGrid" id="typeSearchGrid">
-                <property name="visible">True</property>
-                <property name="can_focus">False</property>
-                <property name="margin_start">60</property>
-                <property name="margin_end">60</property>
-                <property name="margin_top">15</property>
-                <property name="margin_bottom">30</property>
-                <property name="row-spacing">5</property>
-                <!--
-                <child>
-                  <object class="Gjs_OSMTypeSearchEntry" id="typeSearchEntry">
-                    <property name="visible">True</property>
-                    <property name="can_focus">True</property>
-                    <property name="hexpand">True</property>
-                    <property name="margin_start">10</property>
-                    <property name="margin_end">10</property>
-                    <property name="margin_bottom">10</property>
-                  </object>
-                  <packing>
-                    <property name="left_attach">0</property>
-                    <property name="top_attach">0</property>
-                  </packing>
-                </child>
-                -->
-                <child>
-                  <object class="GtkLabel" id="recentTypesLabel">
-                    <property name="visible">True</property>
-                    <property name="can_focus">False</property>
-                    <property name="label" translatable="yes">Recently Used</property>
-                    <property name="halign">GTK_ALIGN_START</property>
-                    <style>
-                      <class name="dim-label"/>
-                    </style>
-                  </object>
-                  <packing>
-                    <property name="left_attach">0</property>
-                    <property name="top_attach">1</property>
-                  </packing>
-                </child>
-                <child>
-                  <object class="GtkListBox" id="recentTypesListBox">
-                    <property name="visible">True</property>
-                    <property name="can_focus">True</property>
-                    <property name="selection-mode">none</property>
-                    <style>
-                      <class name="frame"/>
-                    </style>
+              <object class="GtkStackPage">
+                <property name="name">select-type</property>
+                <property name="child">
+                  <object class="GtkGrid" id="typeSearchGrid">
+                    <property name="margin_start">60</property>
+                    <property name="margin_end">60</property>
+                    <property name="margin_top">15</property>
+                    <property name="margin_bottom">30</property>
+                    <property name="row-spacing">5</property>
+                    <child>
+                      <object class="GtkLabel" id="recentTypesLabel">
+                        <property name="label" translatable="1">Recently Used</property>
+                        <property name="halign">start</property>
+                        <style>
+                          <class name="dim-label"/>
+                        </style>
+                        <layout>
+                          <property name="column">0</property>
+                          <property name="row">1</property>
+                        </layout>
+                      </object>
+                    </child>
+                    <child>
+                      <object class="GtkListBox" id="recentTypesListBox">
+                        <property name="focusable">1</property>
+                        <property name="selection-mode">none</property>
+                        <style>
+                          <class name="frame"/>
+                        </style>
+                        <layout>
+                          <property name="column">0</property>
+                          <property name="row">2</property>
+                        </layout>
+                      </object>
+                    </child>
                   </object>
-                  <packing>
-                    <property name="left_attach">0</property>
-                    <property name="top_attach">2</property>
-                  </packing>
-                </child>
+                </property>
               </object>
-              <packing>
-                <property name="name">select-type</property>
-              </packing>
             </child>
           </object>
         </child>
@@ -274,80 +227,44 @@ OpenStreetMap data.</property>
     </child>
     <child type="titlebar">
       <object class="GtkHeaderBar" id="headerBar">
-        <property name="visible">True</property>
-        <property name="can-focus">False</property>
-        <property name="show-close-button">False</property>
-        <property name="title" translatable="yes" context="dialog title">Edit on OpenStreetMap</property>
-        <child>
+        <property name="show-title-buttons">0</property>
+        <child type="start">
           <object class="GtkButton" id="cancelButton">
-            <property name="label" translatable="yes">Cancel</property>
-            <property name="visible">True</property>
-            <property name="can_focus">True</property>
+            <property name="label" translatable="1">Cancel</property>
+            <property name="focusable">1</property>
           </object>
-          <packing>
-            <property name="pack-type">start</property>
-          </packing>
         </child>
-        <child>
+        <child type="start">
           <object class="GtkButton" id="backButton">
-            <property name="visible">False</property>
-            <property name="can_focus">True</property>
+            <property name="visible">0</property>
+            <property name="focusable">1</property>
             <child>
               <object class="GtkImage">
-                <property name="visible">True</property>
-                <property name="can_focus">False</property>
                 <property name="icon-name">go-previous-symbolic</property>
                 <property name="pixel_size">16</property>
               </object>
             </child>
           </object>
-          <packing>
-            <property name="pack-type">start</property>
-          </packing>
         </child>
-        <child>
+        <child type="end">
           <object class="GtkButton" id="nextButton">
-            <property name="label" translatable="yes">Next</property>
-            <property name="visible">True</property>
-            <property name="sensitive">False</property>
-            <property name="can_focus">True</property>
-            <property name="receives_default">True</property>
+            <property name="label" translatable="1">Next</property>
+            <property name="sensitive">0</property>
+            <property name="focusable">1</property>
+            <property name="receives_default">1</property>
             <style>
               <class name="default"/>
             </style>
           </object>
-          <packing>
-            <property name="pack-type">end</property>
-          </packing>
         </child>
       </object>
     </child>
   </template>
   <object class="GtkPopover" id="addFieldPopover">
-    <property name="visible">False</property>
-    <child>
+    <property name="child">
       <object class="GtkGrid" id="addFieldPopoverGrid">
-        <property name="visible">True</property>
-        <property name="orientation">GTK_ORIENTATION_VERTICAL</property>
+        <property name="orientation">vertical</property>
       </object>
-    </child>
-  </object>
-  <object class="GtkPopover" id="hintPopover">
-    <property name="visible">False</property>
-    <property name="position">GTK_POS_BOTTOM</property>
-    <child>
-      <object class="GtkGrid">
-        <property name="visible">True</property>
-        <property name="margin">5</property>
-        <child>
-          <object class="GtkLabel" id="hintLabel">
-            <property name="visible">True</property>
-            <property name="wrap">True</property>
-            <property name="width-chars">20</property>
-            <property name="max-width-chars">40</property>
-          </object>
-        </child>
-      </object>
-    </child>
+    </property>
   </object>
 </interface>
diff --git a/data/ui/osm-type-list-row.ui b/data/ui/osm-type-list-row.ui
index 05fbb6ac..79c7dcec 100644
--- a/data/ui/osm-type-list-row.ui
+++ b/data/ui/osm-type-list-row.ui
@@ -1,29 +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_OSMTypeListRow" 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="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="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">False</property>
+            <property name="hexpand">1</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>
       </object>
-    </child>
+    </property>
   </template>
 </interface>
diff --git a/data/ui/osm-type-popover.ui b/data/ui/osm-type-popover.ui
index 4ae9e7fb..74c5ed71 100644
--- a/data/ui/osm-type-popover.ui
+++ b/data/ui/osm-type-popover.ui
@@ -1,15 +1,11 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <interface>
-  <requires lib="gtk+" version="3.12"/>
+  <requires lib="gtk" version="4.0"/>
   <template class="Gjs_OSMTypePopover" parent="Gjs_SearchPopover">
-    <property name="position">GTK_POS_BOTTOM</property>
-    <property name="modal">False</property>
     <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>
   </template>
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-dialog.ui b/data/ui/place-dialog.ui
index 9c49c767..3ca5d028 100644
--- a/data/ui/place-dialog.ui
+++ b/data/ui/place-dialog.ui
@@ -1,15 +1,12 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <interface>
+  <requires lib="gtk" version="4.0"/>
   <template class="Gjs_PlaceDialog" parent="GtkDialog">
-    <property name="visible">False</property>
-    <property name="can_focus">False</property>
     <property name="use_header_bar">1</property>
     <child type="action">
       <object class="GtkButton" id="closeButton">
-        <property name="visible">True</property>
         <child>
           <object class="GtkImage">
-            <property name="visible">True</property>
             <property name="icon_name">go-previous-symbolic</property>
           </object>
         </child>
@@ -18,13 +15,11 @@
     <action-widgets>
       <action-widget response="cancel">closeButton</action-widget>
     </action-widgets>
-    <child internal-child="vbox">
+    <child>
       <object class="GtkBox">
-         <property name="visible">True</property>
         <child>
           <object class="GtkScrolledWindow" id="scroll">
-            <property name="visible">True</property>
-            <property name="propagate_natural_height">True</property>
+            <property name="propagate_natural_height">1</property>
             <property name="hscrollbar_policy">never</property>
           </object>
         </child>
@@ -32,5 +27,3 @@
     </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/place-view.ui b/data/ui/place-view.ui
index 14421bbf..1dbd9e16 100644
--- a/data/ui/place-view.ui
+++ b/data/ui/place-view.ui
@@ -1,13 +1,9 @@
 <?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"/>
   <object class="GtkStack" id="bubble-main-stack">
-    <property name="visible">True</property>
     <child>
       <object class="GtkBox" id="bubble-main-box">
-        <property name="visible">True</property>
-        <property name="can_focus">False</property>
         <property name="orientation">vertical</property>
         <child>
           <object class="Gjs_PlaceViewImage" id="bubble-thumbnail">
@@ -17,8 +13,7 @@
         </child>
         <child>
           <object class="GtkSeparator" id="thumbnail-separator">
-            <property name="visible">False</property>
-            <property name="can_focus">False</property>
+            <property name="visible">0</property>
             <style>
               <class name="no-margin-separator"/>
             </style>
@@ -26,29 +21,24 @@
         </child>
         <child>
           <object class="GtkBox" id="title-box">
-            <property name="visible">True</property>
-            <property name="can_focus">False</property>
             <property name="orientation">vertical</property>
-            <property name="margin">18</property>
+            <property name="margin-start">18</property>
+            <property name="margin-end">18</property>
+            <property name="margin-top">18</property>
+            <property name="margin-bottom">18</property>
             <property name="spacing">12</property>
             <child>
               <object class="GtkBox">
-                <property name="visible">True</property>
-                <property name="can_focus">False</property>
                 <property name="orientation">vertical</property>
                 <child>
                   <object class="GtkBox">
-                    <property name="visible">True</property>
-                    <property name="can_focus">False</property>
-                    <property name="orientation">horizontal</property>
                     <property name="spacing">12</property>
                     <child>
                       <object class="GtkLabel" id="label-title">
-                        <property name="visible">True</property>
-                        <property name="can_focus">False</property>
                         <property name="halign">start</property>
-                        <property name="expand">True</property>
-                        <property name="wrap">True</property>
+                        <property name="hexpand">1</property>
+                        <property name="vexpand">1</property>
+                        <property name="wrap">1</property>
                         <property name="max_width_chars">30</property>
                         <property name="xalign">0</property>
                         <style>
@@ -58,14 +48,11 @@
                     </child>
                     <child>
                       <object class="GtkButton" id="send-to-button-alt">
-                        <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">Share location</property>
+                        <property name="visible">0</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">
-                            <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>
@@ -76,11 +63,11 @@
                 </child>
                 <child>
                   <object class="GtkLabel" id="native-name">
-                    <property name="visible">False</property>
-                    <property name="can_focus">False</property>
+                    <property name="visible">0</property>
                     <property name="halign">start</property>
-                    <property name="expand">True</property>
-                    <property name="wrap">True</property>
+                    <property name="hexpand">1</property>
+                    <property name="vexpand">1</property>
+                    <property name="wrap">1</property>
                     <property name="max_width_chars">30</property>
                     <property name="xalign">0</property>
                     <property name="margin-top">6</property>
@@ -93,35 +80,27 @@
             </child>
             <child>
               <object class="GtkLabel" id="address-label">
-                <property name="visible">True</property>
-                <property name="can_focus">False</property>
                 <property name="halign">start</property>
                 <property name="xalign">0</property>
-                <property name="use_markup">True</property>
-                <property name="wrap">True</property>
+                <property name="use_markup">1</property>
+                <property name="wrap">1</property>
               </object>
             </child>
             <child>
               <object class="GtkBox" id="place-buttons">
-                <property name="visible">True</property>
-                <property name="can_focus">False</property>
                 <property name="orientation">vertical</property>
               </object>
             </child>
           </object>
         </child>
         <child>
-          <object class="GtkGrid" id="bubble-content-area">
-            <property name="visible">True</property>
-            <property name="can_focus">False</property>
-          </object>
+          <object class="GtkGrid" id="bubble-content-area"/>
         </child>
       </object>
     </child>
     <child>
       <object class="GtkSpinner" id="bubble-spinner">
-        <property name="visible">True</property>
-        <property name="active">False</property>
+        <property name="spinning">False</property>
       </object>
     </child>
   </object>
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/send-to-dialog.ui b/data/ui/send-to-dialog.ui
index 433643fb..0c474d26 100644
--- a/data/ui/send-to-dialog.ui
+++ b/data/ui/send-to-dialog.ui
@@ -1,19 +1,15 @@
 <?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_SendToDialog" parent="GtkDialog">
-    <property name="visible">False</property>
-    <property name="can_focus">False</property>
     <property name="use_header_bar">1</property>
     <property name="width-request">360</property>
-    <property name="title" translatable="yes">Open Location</property>
+    <property name="title" translatable="1">Open Location</property>
     <child type="action">
       <object class="GtkButton" id="cancelButton">
-        <property name="label" translatable="yes">_Cancel</property>
-        <property name="visible">True</property>
-        <property name="can_focus">True</property>
-        <property name="use_underline">True</property>
+        <property name="label" translatable="1">_Cancel</property>
+        <property name="focusable">1</property>
+        <property name="use_underline">1</property>
         <style>
           <class name="text-button"/>
         </style>
@@ -22,38 +18,35 @@
     <action-widgets>
       <action-widget response="cancel">cancelButton</action-widget>
     </action-widgets>
-    <child internal-child="vbox">
+    <child>
       <object class="GtkBox" id="contentArea">
-        <property name="visible">True</property>
         <property name="orientation">vertical</property>
-        <property name="margin">18</property>
+        <property name="margin-start">18</property>
+        <property name="margin-end">18</property>
+        <property name="margin-top">18</property>
+        <property name="margin-bottom">18</property>
         <property name="spacing">12</property>
         <child>
           <object class="GtkBox">
-            <property name="visible">True</property>
             <property name="orientation">vertical</property>
             <property name="spacing">12</property>
             <child>
               <object class="GtkBox">
-                <property name="visible">True</property>
                 <property name="orientation">vertical</property>
                 <child>
                   <object class="GtkLabel" id="summaryLabel">
-                    <property name="visible">True</property>
-                    <property name="selectable">true</property>
-                    <property name="label">&#x2026;</property>
-                    <property name="justify">left</property>
+                    <property name="selectable">1</property>
+                    <property name="label">…</property>
                     <property name="halign">start</property>
-                    <property name="wrap">True</property>
+                    <property name="wrap">1</property>
                     <property name="xalign">0</property>
                   </object>
                 </child>
                 <child>
                   <object class="GtkLabel" id="summaryUrl">
-                    <property name="visible">True</property>
-                    <property name="selectable">true</property>
-                    <property name="use-markup">true</property>
-                    <property name="label">&#x2026;</property>
+                    <property name="selectable">1</property>
+                    <property name="use-markup">1</property>
+                    <property name="label">…</property>
                     <property name="xalign">0</property>
                     <property name="width-request">0</property>
                     <property name="ellipsize">end</property>
@@ -63,21 +56,17 @@
             </child>
             <child>
               <object class="GtkBox">
-                <property name="visible">True</property>
-                <property name="orientation">horizontal</property>
                 <property name="spacing">6</property>
                 <child>
                   <object class="GtkButton" id="copyButton">
-                    <property name="visible">True</property>
-                    <property name="label" translatable="yes">Copy</property>
-                    <property name="hexpand">True</property>
+                    <property name="label" translatable="1">Copy</property>
+                    <property name="hexpand">1</property>
                   </object>
                 </child>
                 <child>
                   <object class="GtkButton" id="emailButton">
-                    <property name="visible">True</property>
-                    <property name="label" translatable="yes">Send To&#8230;</property>
-                    <property name="hexpand">True</property>
+                    <property name="label" translatable="1">Send To…</property>
+                    <property name="hexpand">1</property>
                   </object>
                 </child>
               </object>
@@ -86,71 +75,62 @@
         </child>
         <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="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="selection_mode">single</property>
                 <child>
                   <object class="GtkListBoxRow" id="weatherRow">
-                    <property name="visible">True</property>
-                    <property name="can_focus">True</property>
-                    <child>
+                    <property name="focusable">1</property>
+                    <property name="child">
                       <object class="GtkGrid" id="weatherGrid">
-                        <property name="visible">True</property>
-                        <property name="orientation">horizontal</property>
                         <property name="column_spacing">12</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="GtkImage" id="weatherIcon">
-                            <property name="visible">True</property>
                             <property name="pixel_size">32</property>
                             <property name="halign">start</property>
                           </object>
                         </child>
                         <child>
-                          <object class="GtkLabel" id='weatherLabel'>
-                            <property name="visible">True</property>
+                          <object class="GtkLabel" id="weatherLabel">
                             <property name="margin-end">10</property>
                           </object>
                         </child>
                       </object>
-                    </child>
+                    </property>
                   </object>
                 </child>
                 <child>
                   <object class="GtkListBoxRow" id="clocksRow">
-                    <property name="visible">True</property>
-                    <property name="can_focus">True</property>
-                    <child>
+                    <property name="focusable">1</property>
+                    <property name="child">
                       <object class="GtkGrid" id="clocksGrid">
-                        <property name="visible">True</property>
-                        <property name="orientation">horizontal</property>
                         <property name="column_spacing">12</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="GtkImage" id="clocksIcon">
-                            <property name="visible">True</property>
                             <property name="pixel_size">32</property>
                             <property name="halign">start</property>
                           </object>
                         </child>
                         <child>
                           <object class="GtkLabel" id="clocksLabel">
-                            <property name="visible">True</property>
                             <property name="margin-end">10</property>
                           </object>
                         </child>
                       </object>
-                    </child>
+                    </property>
                   </object>
                 </child>
               </object>
-            </child>
+            </property>
           </object>
         </child>
       </object>
diff --git a/data/ui/sidebar.ui b/data/ui/sidebar.ui
index ab7b3890..e7ff872c 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>
@@ -116,44 +88,32 @@
                 </style>
               </object>
             </child>
-            <style>
-              <class name="linked"/>
-            </style>
           </object>
         </child>
         <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 +123,187 @@
         </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="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>
+                    <property name="halign">center</property>
+                    <property name="valign">center</property>
+                    <property name="width_request">16</property>
+                    <property name="height_request">16</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">
             <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 +313,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-arrival-row.ui b/data/ui/transit-arrival-row.ui
index 73a42bb1..2c8a1cbf 100644
--- a/data/ui/transit-arrival-row.ui
+++ b/data/ui/transit-arrival-row.ui
@@ -1,77 +1,59 @@
-<?xml version="1.0" encoding="utf-8"?>
+<?xml version="1.0" encoding="UTF-8"?>
 <interface>
-  <requires lib="gtk+" version="3.14"/>
+  <requires lib="gtk" version="4.0"/>
   <template class="Gjs_TransitArrivalRow" parent="GtkListBoxRow">
-    <property name="visible">True</property>
-    <property name="can-focus">False</property>
     <child>
-      <object class="GtkEventBox" id="eventBox">
-        <property name="visible">True</property>
-        <property name="can-focus">False</property>
+      <object class="GtkGrid" id="grid">
+        <property name="margin-top">6</property>
+        <property name="row-spacing">12</property>
         <child>
-          <object class="GtkGrid" id="grid">
-            <property name="visible">True</property>
-            <property name="margin-top">6</property>
-            <property name="margin-bottom">0</property>
-            <property name="row-spacing">12</property>
-            <child>
-              <object class="GtkImage">
-                <property name="visible">True</property>
-                <property name="can-focus">False</property>
-                <property name="margin-start">12</property>
-                <property name="margin-end">12</property>
-                <property name="icon-name">maps-point-end-symbolic</property>
-                <style>
-                  <class name="sidebar-icon"/>
-                </style>
-              </object>
-              <packing>
-                <property name="left-attach">0</property>
-                <property name="top-attach">0</property>
-              </packing>
-            </child>
-            <child>
-              <object class="GtkLabel" id="arrivalLabel">
-                <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="max-width-chars">25</property>
-                <property name="ellipsize">PANGO_ELLIPSIZE_END</property>
-              </object>
-              <packing>
-                <property name="left-attach">1</property>
-                <property name="top-attach">0</property>
-              </packing>
-            </child>
-            <child>
-              <object class="GtkLabel" id="timeLabel">
-                <property name="visible">True</property>
-                <property name="can-focus">False</property>
-                <property name="halign">GTK_ALIGN_END</property>
-                <property name="margin-start">6</property>
-                <property name="margin-end">18</property>
-                <attributes>
-                  <attribute name="font-features" value="tnum"/>
-                </attributes>
-              </object>
-              <packing>
-                <property name="left-attach">2</property>
-                <property name="top-attach">0</property>
-              </packing>
-            </child>
-            <child>
-              <object class="GtkSeparator" id="separator">
-                <property name="visible">True</property>
-                <property name="can-focus">False</property>
-                <property name="valign">GTK_ALIGN_CENTER</property>
-              </object>
-              <packing>
-                <property name="left-attach">0</property>
-                <property name="top-attach">1</property>
-                <property name="width">3</property>
-              </packing>
-            </child>
+          <object class="GtkImage">
+            <property name="margin-start">12</property>
+            <property name="margin-end">12</property>
+            <property name="icon-name">maps-point-end-symbolic</property>
+            <style>
+              <class name="sidebar-icon"/>
+            </style>
+            <layout>
+              <property name="column">0</property>
+              <property name="row">0</property>
+            </layout>
+          </object>
+        </child>
+        <child>
+          <object class="GtkLabel" id="arrivalLabel">
+            <property name="hexpand">1</property>
+            <property name="halign">start</property>
+            <property name="max-width-chars">25</property>
+            <property name="ellipsize">end</property>
+            <layout>
+              <property name="column">1</property>
+              <property name="row">0</property>
+            </layout>
+          </object>
+        </child>
+        <child>
+          <object class="GtkLabel" id="timeLabel">
+            <property name="halign">end</property>
+            <property name="margin-start">6</property>
+            <property name="margin-end">18</property>
+            <attributes>
+              <attribute name="font-features" value="tnum"></attribute>
+            </attributes>
+            <layout>
+              <property name="column">2</property>
+              <property name="row">0</property>
+            </layout>
+          </object>
+        </child>
+        <child>
+          <object class="GtkSeparator" id="separator">
+            <property name="valign">center</property>
+            <layout>
+              <property name="column">0</property>
+              <property name="row">1</property>
+              <property name="column-span">3</property>
+            </layout>
           </object>
         </child>
       </object>
diff --git a/data/ui/transit-itinerary-row.ui b/data/ui/transit-itinerary-row.ui
index 9ef1ddaf..8a238a32 100644
--- a/data/ui/transit-itinerary-row.ui
+++ b/data/ui/transit-itinerary-row.ui
@@ -1,72 +1,71 @@
-<?xml version="1.0" encoding="utf-8"?>
+<?xml version="1.0" encoding="UTF-8"?>
 <interface>
-  <requires lib="gtk+" version="3.14"/>
+  <requires lib="gtk" version="4.0"/>
   <template class="Gjs_TransitItineraryRow" parent="GtkListBoxRow">
-    <property name="visible">True</property>
-    <child>
+    <property name="child">
       <object class="GtkGrid">
-        <property name="visible">True</property>
-        <property name="margin">13</property>
+        <property name="margin-start">13</property>
+        <property name="margin-end">13</property>
+        <property name="margin-top">13</property>
+        <property name="margin-bottom">13</property>
         <property name="column_spacing">13</property>
         <property name="row-spacing">2</property>
         <child>
           <object class="GtkLabel" id="timeLabel">
-            <property name="visible">True</property>
-            <property name="expand">False</property>
-            <property name="halign">GTK_ALIGN_START</property>
+            <property name="hexpand">0</property>
+            <property name="vexpand">0</property>
+            <property name="halign">start</property>
             <attributes>
-              <attribute name="font-features" value="tnum"/>
+              <attribute name="font-features" value="tnum"></attribute>
             </attributes>
+            <layout>
+              <property name="row">0</property>
+              <property name="column">0</property>
+            </layout>
           </object>
-          <packing>
-            <property name="top-attach">0</property>
-            <property name="left-attach">0</property>
-          </packing>
         </child>
         <child>
           <object class="GtkLabel" id="durationLabel">
-            <property name="visible">True</property>
-            <property name="expand">True</property>
-            <property name="halign">GTK_ALIGN_START</property>
+            <property name="hexpand">1</property>
+            <property name="vexpand">1</property>
+            <property name="halign">start</property>
             <attributes>
-              <attribute name="font-features" value="tnum"/>
+              <attribute name="font-features" value="tnum"></attribute>
             </attributes>
             <style>
               <class name="dim-label"/>
             </style>
+            <layout>
+              <property name="row">0</property>
+              <property name="column">1</property>
+            </layout>
           </object>
-          <packing>
-            <property name="top-attach">0</property>
-            <property name="left-attach">1</property>
-          </packing>
         </child>
         <child>
           <object class="GtkGrid" id="summaryGrid">
-            <property name="visible">True</property>
-            <property name="expand">True</property>
+            <property name="hexpand">1</property>
+            <property name="vexpand">1</property>
             <property name="column-spacing">5</property>
+            <layout>
+              <property name="row">1</property>
+              <property name="column">0</property>
+              <property name="column-span">2</property>
+            </layout>
           </object>
-          <packing>
-            <property name="top-attach">1</property>
-            <property name="left-attach">0</property>
-            <property name="width">2</property>
-          </packing>
         </child>
         <child>
           <object class="GtkImage">
-            <property name="visible">True</property>
-            <property name="can-focus">False</property>
             <property name="icon-name">go-next-symbolic</property>
             <property name="pixel-size">16</property>
+            <layout>
+              <property name="row">0</property>
+              <property name="column">2</property>
+              <property name="row-span">2</property>
+              <property name="column-span">2</property>
+            </layout>
           </object>
-          <packing>
-            <property name="top-attach">0</property>
-            <property name="left-attach">2</property>
-            <property name="height">2</property>
-            <property name="width">2</property>
-          </packing>
         </child>
       </object>
-    </child>
+    </property>
   </template>
 </interface>
diff --git a/data/ui/transit-leg-row.ui b/data/ui/transit-leg-row.ui
index 2d27feb4..9a9747dd 100644
--- a/data/ui/transit-leg-row.ui
+++ b/data/ui/transit-leg-row.ui
@@ -1,258 +1,211 @@
-<?xml version="1.0" encoding="utf-8"?>
+<?xml version="1.0" encoding="UTF-8"?>
 <interface>
-  <requires lib="gtk+" version="3.14"/>
+  <requires lib="gtk" version="4.0"/>
   <template class="Gjs_TransitLegRow" parent="GtkListBoxRow">
-    <property name="visible">True</property>
-    <property name="can-focus">False</property>
-    <child>
+    <property name="child">
       <object class="GtkGrid">
-        <property name="visible">True</property>
         <child>
-          <object class="GtkEventBox" id="eventBox">
-            <property name="visible">True</property>
-            <property name="can-focus">False</property>
+          <object class="GtkGrid" id="grid">
+            <property name="row-spacing">3</property>
             <child>
-              <object class="GtkGrid" id="grid">
-                <property name="visible">True</property>
-                <property name="margin-top">0</property>
-                <property name="margin-bottom">0</property>
-                <property name="row-spacing">3</property>
-                <child>
-                  <object class="GtkImage" id="modeImage">
-                    <property name="visible">True</property>
-                    <property name="can-focus">False</property>
-                    <property name="margin-start">12</property>
-                    <property name="margin-end">12</property>
-                    <property name="halign">GTK_ALIGN_START</property>
-                    <style>
-                      <class name="sidebar-icon"/>
-                    </style>
-                  </object>
-                  <packing>
-                    <property name="left-attach">0</property>
-                    <property name="top-attach">0</property>
-                    <property name="height">2</property>
-                  </packing>
-                </child>
-                <child>
-                  <object class="GtkLabel" id="fromLabel">
-                    <property name="visible">True</property>
-                    <property name="can-focus">False</property>
-                    <property name="halign">GTK_ALIGN_START</property>
-                    <property name="max-width-chars">25</property>
-                    <property name="ellipsize">PANGO_ELLIPSIZE_END</property>
-                  </object>
-                  <packing>
-                    <property name="left-attach">1</property>
-                    <property name="top-attach">0</property>
-                  </packing>
-                </child>
-                <child>
-                  <object class="GtkGrid" id="routeGrid">
-                    <property name="visible">True</property>
-                    <property name="can-focus">False</property>
-                    <property name="halign">GTK_ALIGN_START</property>
-                    <property name="hexpand">True</property>
-                  </object>
-                  <packing>
-                    <property name="left-attach">1</property>
-                    <property name="top-attach">1</property>
-                  </packing>
-                </child>
-                <child>
-                  <object class="GtkLabel" id="timeLabel">
-                    <property name="visible">True</property>
-                    <property name="can-focus">False</property>
-                    <property name="margin-start">6</property>
-                    <property name="margin-end">18</property>
-                    <property name="hexpand">False</property>
-                    <property name="halign">GTK_ALIGN_END</property>
-                    <attributes>
-                      <attribute name="font-features" value="tnum"/>
-                    </attributes>
-                  </object>
-                  <packing>
-                    <property name="left-attach">2</property>
-                    <property name="top-attach">0</property>
-                    <property name="height">2</property>
-                  </packing>
-                </child>
+              <object class="GtkImage" id="modeImage">
+                <property name="margin-start">12</property>
+                <property name="margin-end">12</property>
+                <property name="halign">start</property>
+                <style>
+                  <class name="sidebar-icon"/>
+                </style>
+                <layout>
+                  <property name="column">0</property>
+                  <property name="row">0</property>
+                  <property name="row-span">2</property>
+                </layout>
+              </object>
+            </child>
+            <child>
+              <object class="GtkLabel" id="fromLabel">
+                <property name="halign">start</property>
+                <property name="max-width-chars">25</property>
+                <property name="ellipsize">end</property>
+                <layout>
+                  <property name="column">1</property>
+                  <property name="row">0</property>
+                </layout>
+              </object>
+            </child>
+            <child>
+              <object class="GtkGrid" id="routeGrid">
+                <property name="halign">start</property>
+                <property name="hexpand">1</property>
+                <layout>
+                  <property name="column">1</property>
+                  <property name="row">1</property>
+                </layout>
+              </object>
+            </child>
+            <child>
+              <object class="GtkLabel" id="timeLabel">
+                <property name="margin-start">6</property>
+                <property name="margin-end">18</property>
+                <property name="hexpand">0</property>
+                <property name="halign">end</property>
+                <attributes>
+                  <attribute name="font-features" value="tnum"></attribute>
+                </attributes>
+                <layout>
+                  <property name="column">2</property>
+                  <property name="row">0</property>
+                  <property name="row-span">2</property>
+                </layout>
               </object>
             </child>
           </object>
-          <packing>
-            <property name="left-attach">0</property>
-            <property name="top-attach">0</property>
-          </packing>
         </child>
-
         <child>
           <object class="GtkRevealer" id="detailsRevealer">
-            <property name="visible">True</property>
-            <child>
+            <property name="child">
               <object class="GtkGrid">
-                <property name="visible">True</property>
-                <property name="can-focus">False</property>
                 <property name="margin-start">15</property>
                 <property name="margin-end">15</property>
                 <property name="row-spacing">1</property>
                 <child>
                   <object class="GtkLabel" id="agencyLabel">
-                    <property name="visible">False</property>
-                    <property name="use-markup">True</property>
-                    <property name="halign">GTK_ALIGN_START</property>
+                    <property name="visible">0</property>
+                    <property name="use-markup">1</property>
+                    <property name="halign">start</property>
                     <property name="max_width_chars">30</property>
-                    <property name="ellipsize">PANGO_ELLIPSIZE_END</property>
+                    <property name="ellipsize">end</property>
+                    <layout>
+                      <property name="row">0</property>
+                      <property name="column">0</property>
+                      <property name="column-span">3</property>
+                    </layout>
                   </object>
-                  <packing>
-                    <property name="top-attach">0</property>
-                    <property name="left-attach">0</property>
-                    <property name="width">3</property>
-                  </packing>
                 </child>
                 <child>
                   <object class="GtkSeparator">
-                    <property name="visible">True</property>
-                    <property name="can-focus">False</property>
-                    <property name="hexpand">True</property>
-                    <property name="valign">GTK_ALIGN_CENTER</property>
+                    <property name="hexpand">1</property>
+                    <property name="valign">center</property>
+                    <layout>
+                      <property name="row">1</property>
+                      <property name="column">0</property>
+                    </layout>
                   </object>
-                  <packing>
-                    <property name="top-attach">1</property>
-                    <property name="left-attach">0</property>
-                  </packing>
                 </child>
                 <child>
                   <object class="GtkButton" id="collapsButton">
-                    <property name="visible">True</property>
-                    <property name="can-focus">True</property>
-                    <property name="tooltip-text" translatable="yes" comments="Translators: This is a 
tooltip">Hide intermediate stops and information</property>
+                    <property name="focusable">1</property>
+                    <property name="tooltip-text" translatable="1" comments="Translators: This is a 
tooltip">Hide intermediate stops and information</property>
                     <style>
                       <class name="small-circular"/>
                     </style>
                     <child>
                       <object class="GtkImage">
-                        <property name="visible">True</property>
-                        <property name="can-focus">False</property>
                         <property name="icon-name">go-up-symbolic</property>
                         <property name="pixel-size">8</property>
                       </object>
                     </child>
+                    <layout>
+                      <property name="row">1</property>
+                      <property name="column">1</property>
+                    </layout>
                   </object>
-                  <packing>
-                    <property name="top-attach">1</property>
-                    <property name="left-attach">1</property>
-                  </packing>
                 </child>
                 <child>
                   <object class="GtkSeparator">
-                    <property name="visible">True</property>
-                    <property name="can-focus">False</property>
-                    <property name="hexpand">True</property>
-                    <property name="valign">GTK_ALIGN_CENTER</property>
+                    <property name="hexpand">1</property>
+                    <property name="valign">center</property>
+                    <layout>
+                      <property name="row">1</property>
+                      <property name="column">2</property>
+                    </layout>
                   </object>
-                  <packing>
-                    <property name="top-attach">1</property>
-                    <property name="left-attach">2</property>
-                  </packing>
                 </child>
                 <child>
                   <object class="GtkListBox" id="instructionList">
-                    <property name="visible">True</property>
-                    <property name="can-focus">False</property>
+                    <layout>
+                      <property name="row">2</property>
+                      <property name="column">0</property>
+                      <property name="column-span">3</property>
+                    </layout>
                   </object>
-                  <packing>
-                    <property name="top-attach">2</property>
-                    <property name="left-attach">0</property>
-                    <property name="width">3</property>
-                  </packing>
                 </child>
-
               </object>
-            </child>
+            </property>
+            <layout>
+              <property name="row">1</property>
+              <property name="column">0</property>
+            </layout>
           </object>
-          <packing>
-            <property name="top-attach">1</property>
-            <property name="left-attach">0</property>
-          </packing>
         </child>
         <child>
           <object class="GtkStack" id="footerStack">
-            <property name="visible">True</property>
-            <property name="can-focus">False</property>
             <child>
-              <object class="GtkGrid">
-                <property name="visible">True</property>
-                <property name="can-focus">False</property>
-                <child>
-                  <object class="GtkSeparator">
-                    <property name="visible">True</property>
-                    <property name="can-focus">False</property>
-                    <property name="hexpand">True</property>
-                    <property name="valign">GTK_ALIGN_CENTER</property>
-                  </object>
-                  <packing>
-                    <property name="top-attach">0</property>
-                    <property name="left-attach">0</property>
-                  </packing>
-                </child>
-                <child>
-                  <object class="GtkButton" id="expandButton">
-                    <property name="visible">True</property>
-                    <property name="can-focus">True</property>
-                    <property name="tooltip-text" translatable="yes" comments="Translators: This is a 
tooltip">Show intermediate stops and information</property>
-                    <style>
-                      <class name="small-circular"/>
-                    </style>
+              <object class="GtkStackPage">
+                <property name="name">expander</property>
+                <property name="child">
+                  <object class="GtkGrid">
                     <child>
-                      <object class="GtkImage">
-                        <property name="visible">True</property>
-                        <property name="can-focus">False</property>
-                        <property name="icon-name">go-down-symbolic</property>
-                        <property name="pixel-size">8</property>
+                      <object class="GtkSeparator">
+                        <property name="hexpand">1</property>
+                        <property name="valign">center</property>
+                        <layout>
+                          <property name="row">0</property>
+                          <property name="column">0</property>
+                        </layout>
+                      </object>
+                    </child>
+                    <child>
+                      <object class="GtkButton" id="expandButton">
+                        <property name="focusable">1</property>
+                        <property name="tooltip-text" translatable="1" comments="Translators: This is a 
tooltip">Show intermediate stops and information</property>
+                        <style>
+                          <class name="small-circular"/>
+                        </style>
+                        <child>
+                          <object class="GtkImage">
+                            <property name="icon-name">go-down-symbolic</property>
+                            <property name="pixel-size">8</property>
+                          </object>
+                        </child>
+                        <layout>
+                          <property name="row">0</property>
+                          <property name="column">1</property>
+                        </layout>
+                      </object>
+                    </child>
+                    <child>
+                      <object class="GtkSeparator">
+                        <property name="hexpand">1</property>
+                        <property name="valign">center</property>
+                        <layout>
+                          <property name="row">0</property>
+                          <property name="column">2</property>
+                        </layout>
                       </object>
                     </child>
                   </object>
-                  <packing>
-                    <property name="top-attach">0</property>
-                    <property name="left-attach">1</property>
-                  </packing>
-                </child>
-                <child>
-                  <object class="GtkSeparator">
-                    <property name="visible">True</property>
-                    <property name="can-focus">False</property>
-                    <property name="hexpand">True</property>
-                    <property name="valign">GTK_ALIGN_CENTER</property>
-                  </object>
-                  <packing>
-                    <property name="top-attach">0</property>
-                    <property name="left-attach">2</property>
-                  </packing>
-                </child>
+                </property>
               </object>
-              <packing>
-                <property name="name">expander</property>
-              </packing>
             </child>
             <child>
-              <object class="GtkSeparator">
-                <property name="visible">True</property>
-                <property name="can-focus">False</property>
-                <property name="hexpand">True</property>
-                <property name="valign">GTK_ALIGN_CENTER</property>
-              </object>
-              <packing>
+              <object class="GtkStackPage">
                 <property name="name">separator</property>
-              </packing>
+                <property name="child">
+                  <object class="GtkSeparator">
+                    <property name="hexpand">1</property>
+                    <property name="valign">center</property>
+                  </object>
+                </property>
+              </object>
             </child>
+            <layout>
+              <property name="row">2</property>
+              <property name="column">0</property>
+            </layout>
           </object>
-          <packing>
-            <property name="top-attach">2</property>
-            <property name="left-attach">0</property>
-          </packing>
         </child>
       </object>
-    </child>
+    </property>
   </template>
 </interface>
diff --git a/data/ui/transit-more-row.ui b/data/ui/transit-more-row.ui
index 7de5b98a..fd5f823d 100644
--- a/data/ui/transit-more-row.ui
+++ b/data/ui/transit-more-row.ui
@@ -1,40 +1,43 @@
-<?xml version="1.0" encoding="utf-8"?>
+<?xml version="1.0" encoding="UTF-8"?>
 <interface>
+  <requires lib="gtk" version="4.0"/>
   <template class="Gjs_TransitMoreRow" parent="GtkListBoxRow">
-    <property name="visible">True</property>
-    <child>
+    <property name="child">
       <object class="GtkGrid">
-        <property name="visible">True</property>
-        <property name="margin">13</property>
+        <property name="margin-start">13</property>
+        <property name="margin-end">13</property>
+        <property name="margin-top">13</property>
+        <property name="margin-bottom">13</property>
         <child>
           <object class="GtkStack" id="stack">
-            <property name="visible">True</property>
             <child>
-              <object class="GtkLabel" id="label">
-                <property name="visible">True</property>
-                <property name="expand">True</property>
-              </object>
-              <packing>
+              <object class="GtkStackPage">
                 <property name="name">label</property>
-              </packing>
+                <property name="child">
+                  <object class="GtkLabel" id="label">
+                    <property name="hexpand">1</property>
+                    <property name="vexpand">1</property>
+                  </object>
+                </property>
+              </object>
             </child>
             <child>
-              <object class="GtkSpinner">
-                <property name="visible">True</property>
-                <property name="can-focus">False</property>
-                <property name="active">True</property>
-              </object>
-              <packing>
+              <object class="GtkStackPage">
                 <property name="name">spinner</property>
-              </packing>
+                <property name="child">
+                  <object class="GtkSpinner">
+                    <property name="spinning">True</property>
+                  </object>
+                </property>
+              </object>
             </child>
+            <layout>
+              <property name="row">0</property>
+              <property name="column">0</property>
+            </layout>
           </object>
-          <packing>
-            <property name="top-attach">0</property>
-            <property name="left-attach">0</property>
-          </packing>
         </child>
       </object>
-    </child>
+    </property>
   </template>
 </interface>
diff --git a/data/ui/transit-options-panel.ui b/data/ui/transit-options-panel.ui
index a11acfa0..8169ab13 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,83 @@
           <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="width_chars">5</property>
+        <property name="max-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="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>
+        <property name="hexpand">true</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 +102,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/data/ui/transit-route-label.ui b/data/ui/transit-route-label.ui
index 423f7826..c9f67f07 100644
--- a/data/ui/transit-route-label.ui
+++ b/data/ui/transit-route-label.ui
@@ -1,6 +1,6 @@
 <?xml version="1.0" encoding="utf-8"?>
 <interface>
-  <requires lib="gtk+" version="3.14"/>
+  <requires lib="gtk" version="4.0"/>
   <template class="Gjs_TransitRouteLabel" parent="GtkLabel">
     <property name="visible">True</property>
     <property name="use-markup">True</property>
@@ -13,6 +13,8 @@
     <property name="margin-end">3</property>
     <property name="hexpand">False</property>
     <property name="halign">GTK_ALIGN_START</property>
+    <property name="yalign">1.0</property>
+    <property name="xalign">0.6</property>
     <style>
       <class name="route-label"/>
     </style>
diff --git a/data/ui/transit-stop-row.ui b/data/ui/transit-stop-row.ui
index 1cd02ba9..6b7e4fdd 100644
--- a/data/ui/transit-stop-row.ui
+++ b/data/ui/transit-stop-row.ui
@@ -1,58 +1,48 @@
-<?xml version="1.0" encoding="utf-8"?>
+<?xml version="1.0" encoding="UTF-8"?>
 <interface>
+  <requires lib="gtk" version="4.0"/>
   <template class="Gjs_TransitStopRow" parent="GtkListBoxRow">
-    <property name="visible">True</property>
-    <property name="can-focus">False</property>
-    <child>
+    <property name="child">
       <object class="GtkGrid" id="grid">
-        <property name="visible">True</property>
         <child>
           <object class="GtkImage">
-            <property name="visible">True</property>
-            <property name="can-focus">False</property>
-            <property name="margin-start">0</property>
             <property name="margin-end">6</property>
             <property name="icon-name">maps-point-end-symbolic</property>
             <style>
               <class name="sidebar-icon"/>
             </style>
+            <layout>
+              <property name="column">0</property>
+              <property name="row">0</property>
+            </layout>
           </object>
-          <packing>
-            <property name="left-attach">0</property>
-            <property name="top-attach">0</property>
-          </packing>
         </child>
         <child>
           <object class="GtkLabel" id="nameLabel">
-            <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="hexpand">1</property>
+            <property name="halign">start</property>
             <property name="max-width-chars">25</property>
-            <property name="ellipsize">PANGO_ELLIPSIZE_END</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="timeLabel">
-            <property name="visible">True</property>
-            <property name="can-focus">False</property>
-            <property name="halign">GTK_ALIGN_END</property>
+            <property name="halign">end</property>
             <property name="margin-start">6</property>
-            <property name="margin-end">0</property>
             <attributes>
-              <attribute name="font-features" value="tnum"/>
+              <attribute name="font-features" value="tnum"></attribute>
             </attributes>
+            <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>
-    </child>
+    </property>
   </template>
 </interface>
diff --git a/data/ui/zoom-in-dialog.ui b/data/ui/zoom-in-dialog.ui
index 359dd46c..bb6753d3 100644
--- a/data/ui/zoom-in-dialog.ui
+++ b/data/ui/zoom-in-dialog.ui
@@ -1,53 +1,38 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <interface>
-  <requires lib="gtk+" version="3.12"/>
+  <requires lib="gtk" version="4.0"/>
   <template class="Gjs_ZoomInDialog" parent="GtkDialog">
-    <property name="can_focus">False</property>
-    <property name="type">popup</property>
-    <property name="type_hint">dialog</property>
     <property name="width_request">400</property>
     <property name="height_request">150</property>
-    <child internal-child="vbox">
+    <child>
       <object class="GtkBox" id="contentArea">
-        <property name="visible">True</property>
         <child>
           <object class="GtkLabel">
-            <property name="visible">True</property>
-            <property name="label" translatable="yes">Zoom in to add location!</property>
+            <property name="label" translatable="1">Zoom in to add location!</property>
           </object>
         </child>
       </object>
     </child>
     <child type="titlebar">
       <object class="GtkHeaderBar" id="headerBar">
-        <property name="visible">True</property>
-        <property name="can-focus">False</property>
-        <property name="show-close-button">False</property>
-        <child>
+        <property name="show-title-buttons">0</property>
+        <child type="start">
           <object class="GtkButton" id="cancelButton">
-            <property name="label" translatable="yes">Cancel</property>
-            <property name="visible">True</property>
-            <property name="can_focus">True</property>
+            <property name="label" translatable="1">Cancel</property>
+            <property name="focusable">1</property>
           </object>
-          <packing>
-            <property name="pack-type">start</property>
-          </packing>
         </child>
-        <child>
+        <child type="end">
           <object class="GtkButton" id="zoomInButton">
-            <property name="label" translatable="yes">Zoom In</property>
-            <property name="visible">True</property>
-            <property name="can_focus">True</property>
-            <property name="receives_default">True</property>
+            <property name="label" translatable="1">Zoom In</property>
+            <property name="focusable">1</property>
+            <property name="receives_default">1</property>
             <style>
               <class name="default"/>
             </style>
           </object>
-          <packing>
-            <property name="pack-type">end</property>
-          </packing>
         </child>
       </object>
     </child>
   </template>
-</interface>
\ No newline at end of file
+</interface>
diff --git a/lib/maps-file-data-source.c b/lib/maps-file-data-source.c
new file mode 100644
index 00000000..2e326f58
--- /dev/null
+++ b/lib/maps-file-data-source.c
@@ -0,0 +1,531 @@
+/*
+ * Copyright (c) 2015 Jonas Danielsson
+ *
+ * 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: Jonas Danielsson <jonas threetimestwo org>
+ */
+
+#include <shumate/shumate.h>
+#include <gio/gio.h>
+#include <glib.h>
+#include <glib-object.h>
+#include <libsoup/soup.h>
+#include <stdlib.h>
+
+#include "mapsintl.h"
+#include "maps-file-data-source.h"
+
+#define MAPS_FILE_DATA_SOURCE_ERROR maps_file_data_source_error_quark ()
+
+GQuark
+maps_file_data_source_error_quark (void)
+{
+  return g_quark_from_static_string ("maps-file-data-source-error");
+}
+
+enum {
+  PROP_0,
+
+  PROP_PATH,
+  PROP_MAX_ZOOM,
+  PROP_MIN_ZOOM
+};
+
+struct _MapsFileDataSourcePrivate
+{
+  gchar *path;
+  gchar *extension;
+  gint max_zoom;
+  gint min_zoom;
+
+  long min_x;
+  long min_y;
+  long max_x;
+  long max_y;
+};
+
+G_DEFINE_TYPE_WITH_PRIVATE (MapsFileDataSource, maps_file_data_source, SHUMATE_TYPE_DATA_SOURCE)
+
+static void
+maps_file_data_source_set_property (GObject      *object,
+                                    guint         prop_id,
+                                    const GValue *value,
+                                    GParamSpec   *pspec)
+{
+  MapsFileDataSource *data_source = MAPS_FILE_DATA_SOURCE (object);
+
+  switch (prop_id)
+    {
+    case PROP_PATH:
+      data_source->priv->path = g_strdup ((char *) g_value_get_string (value));
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
+static void
+maps_file_data_source_get_property (GObject *object,
+    guint prop_id,
+    GValue *value,
+    GParamSpec *pspec)
+{
+  MapsFileDataSource *data_source = MAPS_FILE_DATA_SOURCE (object);
+
+  switch (prop_id)
+    {
+    case PROP_PATH:
+      g_value_set_string (value, data_source->priv->path);
+      break;
+
+    case PROP_MIN_ZOOM:
+      g_value_set_uint (value, data_source->priv->min_zoom);
+      break;
+
+    case PROP_MAX_ZOOM:
+      g_value_set_uint (value, data_source->priv->max_zoom);
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
+static void
+maps_file_data_source_dispose (GObject *object)
+{
+  G_OBJECT_CLASS (maps_file_data_source_parent_class)->dispose (object);
+}
+
+static void
+maps_file_data_source_finalize (GObject *object)
+{
+  MapsFileDataSource *data_source = MAPS_FILE_DATA_SOURCE (object);
+
+  if (data_source->priv->path)
+    g_free (data_source->priv->path);
+
+  if (data_source->priv->extension)
+    g_free (data_source->priv->extension);
+
+  G_OBJECT_CLASS (maps_file_data_source_parent_class)->finalize (object);
+}
+
+static void
+get_tile_data_async (ShumateDataSource     *source,
+                     int x,
+                     int y,
+                     int zoom_level,
+                     GCancellable         *cancellable,
+                     GAsyncReadyCallback   callback,
+                     gpointer              user_data);
+
+static void
+maps_file_data_source_class_init (MapsFileDataSourceClass *klass)
+{
+  ShumateDataSourceClass *data_source_class = SHUMATE_DATA_SOURCE_CLASS (klass);
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+  GParamSpec *pspec;
+
+  object_class->finalize = maps_file_data_source_finalize;
+  object_class->dispose = maps_file_data_source_dispose;
+  object_class->get_property = maps_file_data_source_get_property;
+  object_class->set_property = maps_file_data_source_set_property;
+  data_source_class->get_tile_data_async = get_tile_data_async;
+
+  /**
+   * MapsFileDataSource:path:
+   *
+   * The path to the tile source.
+   *
+   */
+  pspec = g_param_spec_string ("path",
+                               "Path",
+                               "The path to the tile source",
+                               "",
+                               G_PARAM_READWRITE | G_PARAM_CONSTRUCT);
+  g_object_class_install_property (object_class, PROP_PATH, pspec);
+
+  /**
+   * MapsFileDataSource:min-zoom:
+   *
+   * The minimum zoom level of the tile source.
+   *
+   */
+  pspec = g_param_spec_uint ("min-zoom",
+                             "Minimum zoom",
+                             "The minimum zoom level of the tile source",
+                             0,
+                             20,
+                             2,
+                             G_PARAM_READABLE);
+  g_object_class_install_property (object_class, PROP_MIN_ZOOM, pspec);
+
+  /**
+   * MapsFileDataSource:max-zoom:
+   *
+   * The maximum zoom level of the tile source.
+   *
+   */
+  pspec = g_param_spec_uint ("max-zoom",
+                             "Maximum zoom",
+                             "The maximum zoom level of the tile source",
+                             0,
+                             20,
+                             2,
+                             G_PARAM_READABLE);
+  g_object_class_install_property (object_class, PROP_MAX_ZOOM, pspec);
+}
+
+static void
+maps_file_data_source_init (MapsFileDataSource *data_source)
+{
+  data_source->priv = maps_file_data_source_get_instance_private (data_source);
+  data_source->priv->path = NULL;
+  data_source->priv->extension = NULL;
+  data_source->priv->max_zoom = -1;
+  data_source->priv->min_zoom = 21;
+  data_source->priv->min_x = G_MAXLONG;
+  data_source->priv->min_y = G_MAXLONG;
+  data_source->priv->max_x = 0;
+  data_source->priv->max_y = 0;
+
+}
+
+static gboolean
+get_zoom_levels (MapsFileDataSource *data_source,
+                 GError            **error)
+{
+  GFile *file;
+  GFileEnumerator *enumerator;
+  gboolean ret = TRUE;
+  long orig_min = data_source->priv->min_zoom;
+  long orig_max = data_source->priv->max_zoom;
+
+  file = g_file_new_for_path (data_source->priv->path);
+  enumerator = g_file_enumerate_children (file, "standard::*",
+                                               G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
+                                               NULL,
+                                               error);
+  if (!enumerator)
+    return FALSE;
+
+  while (TRUE)
+    {
+      GFileInfo *info;
+      const char *name;
+      char *endptr;
+      long val;
+
+      if (!g_file_enumerator_iterate (enumerator, &info, NULL, NULL, error)) {
+        ret = FALSE;
+        goto out;
+      }
+
+      if (!info)
+        break;
+
+      if (g_file_info_get_file_type (info) != G_FILE_TYPE_DIRECTORY)
+        continue;
+
+      name = g_file_info_get_name (info);
+      val = strtol (name, &endptr, 0);
+      if (endptr == name || *endptr != '\0')
+        continue;
+
+      if (val > data_source->priv->max_zoom)
+        data_source->priv->max_zoom = val;
+
+      if (val < data_source->priv->min_zoom)
+        data_source->priv->min_zoom = val;
+    }
+
+    if (data_source->priv->min_zoom == orig_min ||
+        data_source->priv->max_zoom == orig_max) {
+      ret = FALSE;
+      if (error)
+        {
+          *error = g_error_new_literal (MAPS_FILE_DATA_SOURCE_ERROR, 0,
+                                        _("Failed to find tile structure in directory"));
+        }
+    }
+
+ out:
+  g_object_unref (file);
+  g_object_unref (enumerator);
+
+  return ret;
+}
+
+static gboolean
+get_y_bounds (MapsFileDataSource *data_source,
+              const char         *path,
+              GError            **error)
+{
+  GFileEnumerator *enumerator;
+  GFile *file;
+  gboolean ret = TRUE;
+  gboolean found = FALSE;
+
+  file = g_file_new_for_path (path);
+  enumerator = g_file_enumerate_children (file, "standard::*",
+                                          G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
+                                          NULL,
+                                          error);
+  if (!enumerator)
+    return FALSE;
+
+  while (TRUE)
+    {
+      GFileInfo *info;
+      char **names;
+      char *endptr;
+      long y;
+
+      if (!g_file_enumerator_iterate (enumerator, &info,
+                                      NULL, NULL, error)) {
+        ret = FALSE;
+        goto out;
+      }
+
+      if (!info)
+        break;
+
+      if (g_file_info_get_file_type (info) != G_FILE_TYPE_REGULAR)
+        continue;
+
+      names = g_strsplit (g_file_info_get_name (info), ".", 2);
+      if (!data_source->priv->extension)
+          data_source->priv->extension = g_strdup (names[1]);
+
+      y = strtol (names[0], &endptr, 0);
+      if (endptr == names[0] || *endptr != '\0') {
+        g_strfreev (names);
+        continue;
+      }
+
+      if (!found)
+        found = TRUE;
+
+      g_strfreev (names);
+
+      if (y > data_source->priv->max_y)
+        data_source->priv->max_y = y;
+
+      if (y < data_source->priv->min_y)
+        data_source->priv->min_y = y;
+    }
+
+  if (!found)
+    {
+      ret = FALSE;
+      if (error)
+        {
+          *error = g_error_new_literal (MAPS_FILE_DATA_SOURCE_ERROR, 0,
+                                        _("Failed to find tile structure in directory"));
+        }
+    }
+
+ out:
+  g_object_unref (file);
+  g_object_unref (enumerator);
+  return ret;
+}
+
+static gboolean
+get_bounds (MapsFileDataSource *data_source,
+            GError            **error)
+{
+  GFileEnumerator *enumerator;
+  GFile *file;
+  char *path;
+  gboolean ret = TRUE;
+  char min_zoom[3];
+  gboolean found = FALSE;
+
+  sprintf (min_zoom, "%u", data_source->priv->min_zoom);
+  path = g_build_filename (data_source->priv->path, min_zoom, NULL);
+  file = g_file_new_for_path (path);
+
+  enumerator = g_file_enumerate_children (file, "standard::*",
+                                          G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
+                                          NULL,
+                                          error);
+  if (!enumerator)
+    return FALSE;
+
+  while (TRUE)
+    {
+      char *y_path;
+      GFileInfo *info;
+      const char *name;
+      char *endptr;
+      long x;
+
+      if (!g_file_enumerator_iterate (enumerator, &info, NULL, NULL, error)) {
+        ret = FALSE;
+        goto out;
+      }
+
+      if (!info)
+        break;
+
+      if (g_file_info_get_file_type (info) != G_FILE_TYPE_DIRECTORY)
+        continue;
+
+      name = g_file_info_get_name (info);
+      x = strtol (name, &endptr, 0);
+      if (endptr == name || *endptr != '\0')
+        continue;
+
+      if (!found)
+        found = TRUE;
+
+      if (x > data_source->priv->max_x)
+        data_source->priv->max_x = x;
+
+      if (x < data_source->priv->min_x)
+        data_source->priv->min_x = x;
+
+      y_path = g_build_filename (path, name, NULL);
+      if (!get_y_bounds (data_source, y_path, error)) {
+        g_free (y_path);
+        ret = FALSE;
+        goto out;
+      }
+      g_free (y_path);
+    }
+
+  if (!found)
+    {
+      ret = FALSE;
+      if (error)
+        {
+          *error = g_error_new_literal (MAPS_FILE_DATA_SOURCE_ERROR, 0,
+                                        _("Failed to find tile structure in directory"));
+        }
+    }
+
+ out:
+  g_free (path);
+  g_object_unref (file);
+  g_object_unref (enumerator);
+  return ret;
+}
+
+gboolean
+maps_file_data_source_prepare (MapsFileDataSource *data_source,
+                               GError            **error)
+{
+  g_return_val_if_fail (MAPS_IS_FILE_DATA_SOURCE (data_source), FALSE);
+  g_return_val_if_fail (data_source->priv->path != NULL, FALSE);
+
+  if (!get_zoom_levels (data_source, error)) {
+    return FALSE;
+  }
+
+  if (!get_bounds (data_source, error)) {
+    return FALSE;
+  }
+
+  return TRUE;
+}
+
+typedef struct {
+  MapsFileDataSource *self;
+  int x;
+  int y;
+  int z;
+  GBytes *bytes;
+  GFile *file;
+} FillTileData;
+
+static void
+fill_tile_data_free (FillTileData *data)
+{
+  g_clear_object (&data->self);
+  g_clear_pointer (&data->file, g_object_unref);
+  g_free (data);
+}
+
+static void
+on_file_load (GObject      *source_object,
+              GAsyncResult *res,
+              gpointer      user_data)
+{
+  g_autoptr(GTask) task = user_data;
+  FillTileData *data = g_task_get_task_data (task);
+  char *contents;
+  gsize length;
+  GBytes *bytes;
+
+  g_file_load_contents_finish (data->file, res, &contents, &length, NULL, NULL);
+
+  if (contents != NULL)
+    {
+      bytes = g_bytes_new_take (contents, length);
+      g_signal_emit_by_name (data->self, "received-data", data->x, data->y, data->z, data->bytes);
+      g_task_return_pointer (task, g_steal_pointer (&bytes), (GDestroyNotify)g_bytes_unref);
+    }
+}
+
+static void
+get_tile_data_async (ShumateDataSource     *source,
+                     int                   x,
+                     int                   y,
+                     int                   zoom_level,
+                     GCancellable         *cancellable,
+                     GAsyncReadyCallback   callback,
+                     gpointer              user_data)
+{
+  g_return_if_fail (MAPS_IS_FILE_DATA_SOURCE (source));
+
+  MapsFileDataSource *data_source = MAPS_FILE_DATA_SOURCE (source);
+  GFile *file;
+  gchar *path = NULL;
+  g_autoptr(GTask) task = NULL;
+  FillTileData *data;
+
+  path = g_strdup_printf("%s/%d/%d/%d.%s",
+                         data_source->priv->path,
+                         zoom_level,
+                         x,
+                         y,
+                         data_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, get_tile_data_async);
+
+  data = g_new0 (FillTileData, 1);
+  data->self = g_object_ref (data_source);
+  data->x = x;
+  data->y = y;
+  data->z = zoom_level;
+  data->file = g_object_ref (file);
+  g_task_set_task_data (task, data, (GDestroyNotify) fill_tile_data_free);
+
+  if (g_file_query_exists(file, NULL))
+    {
+      g_file_load_contents_async (file, cancellable,
+                                  on_file_load,
+                                  g_object_ref (task));
+    }
+
+  g_object_unref (file);
+  g_free (path);
+}
+
diff --git a/lib/maps-file-data-source.h b/lib/maps-file-data-source.h
new file mode 100644
index 00000000..126fff03
--- /dev/null
+++ b/lib/maps-file-data-source.h
@@ -0,0 +1,73 @@
+/*
+ * Copyright (c) 2015 Jonas Danielsson
+ *
+ * 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: Jonas Danielsson <jonas threetimestwo org>
+ */
+
+#ifndef _MAPS_FILE_DATA_SOURCE_H_
+#define _MAPS_FILE_DATA_SOURCE_H_
+
+#include <shumate/shumate.h>
+
+G_BEGIN_DECLS
+
+#define MAPS_TYPE_FILE_DATA_SOURCE maps_file_data_source_get_type ()
+
+#define MAPS_FILE_DATA_SOURCE(obj) \
+  (G_TYPE_CHECK_INSTANCE_CAST ((obj), MAPS_TYPE_FILE_DATA_SOURCE, MapsFileDataSource))
+
+#define MAPS_FILE_DATA_SOURCE_CLASS(klass) \
+  (G_TYPE_CHECK_CLASS_CAST ((klass), MAPS_TYPE_FILE_DATA_SOURCE, MapsFileDataSourceClass))
+
+#define MAPS_IS_FILE_DATA_SOURCE(obj) \
+  (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MAPS_TYPE_FILE_DATA_SOURCE))
+
+#define MAPS_IS_FILE_DATA_SOURCE_CLASS(klass) \
+  (G_TYPE_CHECK_CLASS_TYPE ((klass), MAPS_TYPE_FILE_DATA_SOURCE))
+
+#define MAPS_FILE_DATA_SOURCE_GET_CLASS(obj) \
+  (G_TYPE_INSTANCE_GET_CLASS ((obj), MAPS_TYPE_FILE_TILE_SOURCE, MapsFileDataSourceClass))
+
+typedef struct _MapsFileDataSourcePrivate MapsFileDataSourcePrivate;
+
+typedef struct _MapsFileDataSource MapsFileDataSource;
+typedef struct _MapsFileDataSourceClass MapsFileDataSourceClass;
+
+/**
+ * MapsFileDataSource:
+ *
+ * The #MapsFileDataSource structure contains only private data
+ * and should be accessed using the provided API
+ *
+ */
+struct _MapsFileDataSource
+{
+  ShumateDataSource parent_instance;
+
+  MapsFileDataSourcePrivate *priv;
+};
+
+struct _MapsFileDataSourceClass
+{
+  ShumateDataSourceClass parent_class;
+};
+
+GType maps_file_data_source_get_type (void);
+
+gboolean maps_file_data_source_prepare (MapsFileDataSource *data_source, GError **error);
+G_END_DECLS
+
+#endif /* _MAPS_FILE_DATA_SOURCE_H_ */
diff --git a/lib/maps-sync-map-source.c b/lib/maps-sync-map-source.c
new file mode 100644
index 00000000..34a16488
--- /dev/null
+++ b/lib/maps-sync-map-source.c
@@ -0,0 +1,91 @@
+/*
+ * 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"
+
+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)
+{
+  g_return_if_fail (MAPS_IS_SYNC_MAP_SOURCE (source));
+  MapsSyncMapSource *self = MAPS_SYNC_MAP_SOURCE(source);
+
+  g_autoptr(GTask) task = NULL;
+
+ // TODO: this gives a null gobject cast runtime error...
+  MAPS_SYNC_MAP_SOURCE_GET_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..8f0d6463
--- /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..ada8239d 100644
--- a/lib/meson.build
+++ b/lib/meson.build
@@ -1,23 +1,25 @@
 headers_private = files(
-       'maps-file-tile-source.h',
+       'maps-file-data-source.h',
        'maps-osm.h',
        'maps-osm-changeset.h',
        'maps-osm-node.h',
        '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(
-       'maps-file-tile-source.c',
+       'maps-file-data-source.c',
        'maps-osm.c',
        'maps-osm-changeset.c',
        'maps-osm-node.c',
        '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..5e217201 100644
--- a/src/application.js
+++ b/src/application.js
@@ -20,13 +20,13 @@
  *         Zeeshan Ali (Khattak) <zeeshanak gnome org>
  */
 
+import Adw from 'gi://Adw';
+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 {Geoclue} from './geoclue.js';
 import * as GeocodeFactory from './geocode.js';
@@ -44,7 +44,7 @@ const Format = imports.format;
 
 const _ensuredTypes = [OSMTypeSearchEntry];
 
-export class Application extends Gtk.Application {
+export class Application extends Adw.Application {
 
     // used globally
     static application = null;
@@ -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;
@@ -162,12 +159,10 @@ export class Application extends Gtk.Application {
             }
         }, Application.settings);
 
+        let display = Gdk.Display.get_default();
 
-        this._styleManager = Hdy.StyleManager.get_default();
-        this._styleManager.set_color_scheme(Hdy.ColorScheme.PREFER_LIGHT);
-
-        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();
     }
 
@@ -207,7 +202,7 @@ export class Application extends Gtk.Application {
     }
 
     _openInternal(files) {
-        if (!this._mainWindow || !this._mainWindow.mapView.view.realized)
+        if (!this._mainWindow || !this._mainWindow.mapView.map.get_realized())
             return;
 
         let uri = files[0].get_uri();
@@ -297,10 +292,10 @@ export class Application extends Gtk.Application {
         this.activate();
 
         let mapView = this._mainWindow.mapView;
-        if (mapView.view.realized)
+        if (mapView.map.get_realized())
             this._openInternal(files);
         else
-            mapView.view.connect('notify::realized',
+            mapView.map.connect('realize',
                                  this._openInternal.bind(this, files));
     }
 
@@ -357,6 +352,15 @@ export class Application extends Gtk.Application {
     _onWindowDestroy(window) {
         this._mainWindow = null;
     }
+
+    vfunc_shutdown() {
+        // need to unparent popover children to avoid GTK warnings on exit
+        if (this._mainWindow) {
+            this._mainWindow.placeEntry.popover.unparent();
+        }
+        this._mainWindow.sidebar.unparentSearchPopovers();
+        super.vfunc_shutdown();
+    }
 }
 
 GObject.registerClass({
diff --git a/src/contextMenu.js b/src/contextMenu.js
index 0b71b08e..e8e71c20 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;
@@ -49,44 +49,63 @@ export class ContextMenu extends Gtk.Menu {
         this._mapView = mapView;
         this._mainWindow = mainWindow;
         this._buttonGesture =
-            new Gtk.GestureSingle({ widget: this._mapView,
-                                    button: Gdk.BUTTON_SECONDARY });
-        this._buttonGesture.connect('end', this._onButtonRelease.bind(this));
-
-        this._whatsHereItem.connect('activate',
-                                    this._onWhatsHereActivated.bind(this));
-        this._geoURIItem.connect('activate',
-                                 this._onGeoURIActivated.bind(this));
-        this._addOSMLocationItem.connect('activate',
-                                         this._onAddOSMLocationActivated.bind(this));
-        this._routeFromHereItem.connect('activate',
-                                        this._onRouteFromHereActivated.bind(this));
-        this._addIntermediateDestinationItem.connect('activate',
-                                        this._onAddIntermediateDestinationActivated.bind(this));
-        this._routeToHereItem.connect('activate',
-                                      this._onRouteToHereActivated.bind(this));
+            new Gtk.GestureSingle({ button: Gdk.BUTTON_SECONDARY });
+        this._mapView.add_controller(this._buttonGesture);
+        this._buttonGesture.connect('begin', (g, s) => this._onOpenMenu(g, s));
+
+        this._routeFromHereAction =
+            this._mainWindow.lookup_action('route-from-here');
+        this._addIntermediateDestinationAction =
+            this._mainWindow.lookup_action('add-intermediate-destination');
+        this._routeToHereAction =
+            this._mainWindow.lookup_action('route-to-here');
+
+        let whatsHereAction =
+            this._mainWindow.lookup_action('whats-here');
+        let copyLocationAction =
+            this._mainWindow.lookup_action('copy-location');
+        let addOSMLocationAction =
+            this._mainWindow.lookup_action('add-osm-location');
+
+        whatsHereAction.connect('activate',
+                                 () => this._onWhatsHereActivated());
+        copyLocationAction.connect('activate',
+                                   () => this._onCopyLocationActivated());
+        addOSMLocationAction.connect('activate',
+                                     () => this._onAddOSMLocationActivated());
+        this._routeFromHereAction.connect('activate',
+                                     () => this._onRouteFromHereActivated());
+        this._addIntermediateDestinationAction.connect('activate',
+                         () => this._onAddIntermediateDestinationActivated());
+        this._routeToHereAction.connect('activate',
+                                        () => this._onRouteToHereActivated());
         Application.routeQuery.connect('notify::points',
-                                       this._routingUpdate.bind(this));
+                                       () => this._routingUpdate());
         this._routingUpdate();
+        this.set_parent(this._mapView);
     }
 
-    _onButtonRelease(gesture, sequence) {
-        let event = gesture.get_last_event(sequence);
-        let [, x, y] = event.get_coords();
-        this._longitude = this._mapView.view.x_to_longitude(x);
-        this._latitude = this._mapView.view.y_to_latitude(y);
+    _onOpenMenu(gesture, sequence) {
+        let [_, x, y] = gesture.get_point(sequence);
+        let viewport = this._mapView.map.viewport;
+        /* we can't get the allocated width before showing, so use a
+         * best-effort offset to get the top-left corner close to the pointer
+         */
+        let rect = new Gdk.Rectangle({ x: x, y: y, width: 200, height: 0 });
 
-        // Need idle to avoid Clutter dead-lock on re-entrance
-        GLib.idle_add(null, () => this.popup_at_pointer(event));
+        [this._latitude, this._longitude] = viewport.widget_coords_to_location(this._mapView, x, y);
+
+        this.pointing_to = rect;
+        this.popup();
     }
 
     _routingUpdate() {
         let query = Application.routeQuery;
         let numPoints = query.points.length;
 
-        this._routeFromHereItem.sensitive = numPoints < RouteQuery.MAX_QUERY_POINTS;
-        this._routeToHereItem.sensitive = numPoints < RouteQuery.MAX_QUERY_POINTS;
-        this._addIntermediateDestinationItem.sensitive =
+        this._routeFromHereAction.enabled = numPoints < RouteQuery.MAX_QUERY_POINTS;
+        this._routeToHereAction.enabled = numPoints < RouteQuery.MAX_QUERY_POINTS;
+        this._addIntermediateDestinationAction.enabled =
             query.filledPoints.length >= 2 && numPoints < RouteQuery.MAX_QUERY_POINTS;
     }
 
@@ -124,7 +143,7 @@ export class ContextMenu extends Gtk.Menu {
         GeocodeFactory.getGeocoder().reverse(this._latitude, this._longitude,
                                       (place) => {
             if (place) {
-                this._mapView.showPlace(place, false);
+                this._mapView.showPlace(place, true);
             } else {
                 let msg = _("Nothing found here!");
 
@@ -133,15 +152,14 @@ export class ContextMenu extends Gtk.Menu {
         });
     }
 
-    _onGeoURIActivated() {
+    _onCopyLocationActivated() {
         let location = new Location({ latitude: this._latitude,
                                       longitude: this._longitude,
                                       accuracy: 0 });
-        let display = Gdk.Display.get_default();
-        let clipboard = Gtk.Clipboard.get_default(display);
+        let clipboard = this.get_clipboard();
         let uri = location.to_uri(GeocodeGlib.LocationURIScheme.GEO);
 
-        clipboard.set_text(uri, uri.length);
+        clipboard.set(uri);
     }
 
     _onAddOSMLocationActivated() {
@@ -165,17 +183,18 @@ export class ContextMenu extends Gtk.Menu {
 
     _addOSMLocation() {
         let osmEdit = Application.osmEdit;
+        let viewport = this._mapView.map.viewport;
 
-        if (this._mapView.view.get_zoom_level() < OSMEdit.MIN_ADD_LOCATION_ZOOM_LEVEL) {
+        if (viewport.zoom_level < OSMEdit.MIN_ADD_LOCATION_ZOOM_LEVEL) {
             let zoomInDialog =
                 new ZoomInDialog({ longitude: this._longitude,
                                    latitude: this._latitude,
-                                   view: this._mapView.view,
+                                   map: this._mapView.map,
                                    transient_for: this._mainWindow,
                                    modal: true });
 
             zoomInDialog.connect('response', () => zoomInDialog.destroy());
-            zoomInDialog.show_all();
+            zoomInDialog.show();
             return;
         }
 
@@ -195,11 +214,5 @@ export class ContextMenu extends Gtk.Menu {
 }
 
 GObject.registerClass({
-    Template: 'resource:///org/gnome/Maps/ui/context-menu.ui',
-    InternalChildren: [ 'whatsHereItem',
-                        'geoURIItem',
-                        'addOSMLocationItem',
-                        'routeFromHereItem',
-                        'addIntermediateDestinationItem',
-                        'routeToHereItem' ],
+    Template: 'resource:///org/gnome/Maps/ui/context-menu.ui'
 }, ContextMenu);
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/exportViewDialog.js b/src/exportViewDialog.js
index baffe2e1..02143893 100644
--- a/src/exportViewDialog.js
+++ b/src/exportViewDialog.js
@@ -22,6 +22,8 @@ import Gdk from 'gi://Gdk';
 import GLib from 'gi://GLib';
 import Gio from 'gi://Gio';
 import GObject from 'gi://GObject';
+import Graphene from 'gi://Graphene';
+import Gsk from 'gi://Gsk';
 import Gtk from 'gi://Gtk';
 
 import * as Utils from './utils.js';
@@ -36,8 +38,8 @@ export class ExportViewDialog extends Gtk.Dialog {
     };
 
     constructor(params) {
-        let surface = params.surface;
-        delete params.surface;
+        let paintable = params.paintable;
+        delete params.paintable;
 
         let latitude = params.latitude;
         delete params.latitude;
@@ -48,17 +50,23 @@ export class ExportViewDialog extends Gtk.Dialog {
         let mapView = params.mapView;
         delete params.mapView;
 
+        let width = params.width;
+        delete params.width;
+
+        let height = params.height;
+        delete params.height;
+
         params.use_header_bar = true;
         super(params);
 
-        this._surface = surface;
+        this._paintable = paintable;
         this._mapView = mapView;
+        this._width = width;
+        this._height = height;
         this._cancelButton.connect('clicked', () => this.response(ExportViewDialog.Response.CANCEL));
         this._exportButton.connect('clicked', () => this._exportView());
         this._filenameEntry.connect('changed', () => this._onFileNameChanged());
-        this._fileChooserButton.connect('file-set', () => this._onFolderChanged());
-        this._layersCheckButton.connect('toggled', () => this._includeLayersChanged());
-
+        this._fileChooserButton.connect('clicked', () => this._onFileChooserClicked());
 
         this._folder = GLib.get_user_special_dir(GLib.UserDirectory.DIRECTORY_PICTURES);
         if (!this._folder)
@@ -66,7 +74,6 @@ export class ExportViewDialog extends Gtk.Dialog {
 
         this._filenameEntry.text = this._fileName =
             this._getName(latitude, longitude);
-        this._fileChooserButton.set_current_folder(this._folder);
         this._setupPreviewArea();
     }
 
@@ -80,25 +87,26 @@ export class ExportViewDialog extends Gtk.Dialog {
     }
 
     _setupPreviewArea() {
-        let [surfaceWidth, surfaceHeight] = this._mapView.view.get_size();
-
-        let width = _PREVIEW_WIDTH;
-        this._scaleFactor = width / surfaceWidth;
-        let height = surfaceHeight * this._scaleFactor;
+        this._scaleFactor = _PREVIEW_WIDTH / this._width;
+        let previewHeight = this._height * this._scaleFactor;
 
-        this._previewArea.set_size_request(width, height);
-        this._previewArea.connect('draw',
-                                  (w, cr) => this._drawPreview(w, cr));
+        this._previewArea.width_request = _PREVIEW_WIDTH;
+        this._previewArea.height_request = previewHeight;
+        this._previewArea.paintable = this._paintable;
     }
 
-    _drawPreview(widget, cr) {
-        cr.setOperator(Cairo.Operator.CLEAR);
-        cr.paint();
-        cr.setOperator(Cairo.Operator.OVER);
+    _onFileChooserClicked() {
+        let folderChooser = new Gtk.FileChooserNative();
 
-        cr.scale(this._scaleFactor, this._scaleFactor);
-        cr.setSourceSurface(this._surface, 0, 0);
-        cr.paint();
+        folderChooser.set_current_folder(Gio.File.new_for_path(this._folder));
+        folderChooser.connect('response',
+                              (widget, response) => {
+            if (response === Gtk.ResponseType.ACCEPT)
+                this._onFolderChanged(folderChooser.get_current_folder().get_path());
+
+            folderChooser.destroy();
+        });
+        folderChooser.show();
     }
 
     _onFileNameChanged() {
@@ -118,9 +126,7 @@ export class ExportViewDialog extends Gtk.Dialog {
         }
     }
 
-    _onFolderChanged() {
-        let folder = this._fileChooserButton.get_filename();
-
+    _onFolderChanged(folder) {
         if (!GLib.file_test(folder, GLib.FileTest.IS_DIR)) {
             this._exportButton.sensitive= false;
             return;
@@ -135,12 +141,23 @@ export class ExportViewDialog extends Gtk.Dialog {
     }
 
     _exportView() {
-        let [width, height] = this._mapView.view.get_size();
-        let pixbuf = Gdk.pixbuf_get_from_surface(this._surface, 0, 0, width, height);
+        let rect = new Graphene.Rect();
+
+        rect.init(0, 0, this._width, this._height);
+
+        let snapshot = Gtk.Snapshot.new();
+
+        this._paintable.snapshot(snapshot,
+                                 this._paintable.get_intrinsic_width(),
+                                 this._paintable.get_intrinsic_height());
+
+        let node = snapshot.to_node();
+        let renderer = this._mapView.get_native().get_renderer();
+        let texture = renderer.render_texture(node, rect);
         let path = GLib.build_filenamev([this._folder, this._fileName]);
 
         try {
-            pixbuf.savev(path, "png", [], []);
+            texture.save_to_png(path);
             this.response(ExportViewDialog.Response.SUCCESS);
         } catch(e) {
             Utils.debug('failed to export view: ' + e.message);
@@ -169,13 +186,6 @@ export class ExportViewDialog extends Gtk.Dialog {
             dialog.show_all();
         }
     }
-
-    _includeLayersChanged() {
-        let includeLayers = this._layersCheckButton.get_active();
-
-        this._surface = this._mapView.view.to_surface(includeLayers);
-        this._previewArea.queue_draw();
-    }
 }
 
 GObject.registerClass({
@@ -184,6 +194,5 @@ GObject.registerClass({
                         'cancelButton',
                         'filenameEntry',
                         'fileChooserButton',
-                        'previewArea',
-                        'layersCheckButton' ],
+                        'previewArea'],
 }, ExportViewDialog);
diff --git a/src/favoritesPopover.js b/src/favoritesPopover.js
index 45d8fe15..9f3cc98e 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,16 @@ export class FavoritesPopover extends Gtk.Popover {
     }
 
     _updateList() {
-        this._list.forall((row) => row.destroy());
+        let listRows = [];
+
+        for (let row of this._list) {
+            if (row instanceof Gtk.ListBoxRow)
+                listRows.push(row);
+        }
+
+        for (let row of listRows) {
+            this._list.remove(row);
+        }
 
         let rows = 0;
         this._model.foreach((model, path, iter) => {
diff --git a/src/geoJSONShapeLayer.js b/src/geoJSONShapeLayer.js
index 68ab7fbd..b5309c2e 100644
--- a/src/geoJSONShapeLayer.js
+++ b/src/geoJSONShapeLayer.js
@@ -18,6 +18,7 @@
  */
 
 import GObject from 'gi://GObject';
+import Shumate from 'gi://Shumate';
 
 import {GeoJSONSource} from './geoJSONSource.js';
 import {ShapeLayer} from './shapeLayer.js';
@@ -39,6 +40,9 @@ export class GeoJSONShapeLayer extends ShapeLayer {
 
         this._mapSource = new GeoJSONSource({ mapView: this._mapView,
                                               markerLayer: this._markerLayer });
+        this._overlayLayer =
+            new Shumate.MapLayer({ map_source: this._mapSource,
+                                   viewport:   this._mapView.map.viewport });
     }
 
     getName() {
diff --git a/src/geoJSONSource.js b/src/geoJSONSource.js
index f0b6e5ef..29ea88d0 100644
--- a/src/geoJSONSource.js
+++ b/src/geoJSONSource.js
@@ -19,10 +19,12 @@
  */
 
 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 Shumate from 'gi://Shumate';
+
+import GnomeMaps from 'gi://GnomeMaps';
 
 import {BoundingBox} from './boundingBox.js';
 import * as Geojsonvt from './geojsonvt/geojsonvt.js';
@@ -38,54 +40,33 @@ const TileFeature = { POINT: 1,
                       LINESTRING: 2,
                       POLYGON: 3 };
 
-export class GeoJSONSource extends Champlain.TileSource {
+export class GeoJSONSource extends GnomeMaps.SyncMapSource {
 
     constructor(params) {
-        super();
+        let mapView = params.mapView;
+        delete params.mapView;
+        let markerLayer = params.markerLayer;
+        delete params.markerLayer;
+
+        super(params);
 
-        this._mapView = params.mapView;
-        this._markerLayer = params.markerLayer;
+        this._mapView = mapView;
+        this._markerLayer = 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));
+        this._renderTile(tile);
     }
 
     _validate([lon, lat]) {
@@ -206,72 +187,70 @@ 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();
     }
 
     _renderTile(tile) {
         let tileJSON = this._tileIndex.getTile(tile.zoom_level, tile.x, tile.y);
-        let content = new Clutter.Canvas({ width: this._tileSize,
-                                           height: this._tileSize });
-        tile.content = new Clutter.Actor({ width: this._tileSize,
-                                           height: this._tileSize,
-                                           content: content });
-
-        content.connect('draw', (canvas, cr) => {
-            tile.set_surface(cr.getTarget());
-            cr.setOperator(Cairo.Operator.CLEAR);
-            cr.paint();
-            cr.setOperator(Cairo.Operator.OVER);
-            cr.setFillRule(Cairo.FillRule.EVEN_ODD);
-
-            if (!tileJSON) {
-                tile.emit('render-complete', null, 0, false);
+        let surface = new Cairo.ImageSurface(Cairo.Format.ARGB32,
+                                             this.tile_size, this.tile_size);
+        let cr = new Cairo.Context(surface);
+
+        cr.setOperator(Cairo.Operator.CLEAR);
+        cr.paint();
+        cr.setOperator(Cairo.Operator.OVER);
+        cr.setFillRule(Cairo.FillRule.EVEN_ODD);
+
+        if (!tileJSON) {
+            return;
+        }
+
+        tileJSON.features.forEach((feature) => {
+            if (feature.type === TileFeature.POINT)
                 return;
-            }
 
-            tileJSON.features.forEach((feature) => {
-                if (feature.type === TileFeature.POINT)
-                    return;
-
-                let geoJSONStyleObj = GeoJSONStyle.parseSimpleStyle(feature.tags);
-
-                feature.geometry.forEach((geometry) => {
-                    let first = true;
-                    cr.moveTo(0, 0);
-                    cr.setLineWidth(geoJSONStyleObj.lineWidth);
-                    cr.setSourceRGBA(geoJSONStyleObj.color.red,
-                                     geoJSONStyleObj.color.green,
-                                     geoJSONStyleObj.color.blue,
-                                     geoJSONStyleObj.alpha);
-
-                    geometry.forEach(function(coord) {
-                        if (first) {
-                            cr.moveTo(coord[0], coord[1]);
-                            first = false;
-                        } else {
-                            cr.lineTo(coord[0], coord[1]);
-                        }
-                    });
+            let geoJSONStyleObj = GeoJSONStyle.parseSimpleStyle(feature.tags);
+
+            feature.geometry.forEach((geometry) => {
+                let first = true;
+                cr.moveTo(0, 0);
+                cr.setLineWidth(geoJSONStyleObj.lineWidth);
+                cr.setSourceRGBA(geoJSONStyleObj.color.red,
+                                 geoJSONStyleObj.color.green,
+                                 geoJSONStyleObj.color.blue,
+                                 geoJSONStyleObj.alpha);
+
+                geometry.forEach(function(coord) {
+                    if (first) {
+                        cr.moveTo(coord[0], coord[1]);
+                        first = false;
+                    } else {
+                        cr.lineTo(coord[0], coord[1]);
+                    }
                 });
-                if (feature.type === TileFeature.POLYGON) {
-                    cr.closePath();
-                    cr.strokePreserve();
-                    cr.setSourceRGBA(geoJSONStyleObj.fillColor.red,
-                                     geoJSONStyleObj.fillColor.green,
-                                     geoJSONStyleObj.fillColor.blue,
-                                     geoJSONStyleObj.fillAlpha);
-                    cr.fill();
-                } else {
-                    cr.stroke();
-                }
             });
-
-            tile.emit('render-complete', null, 0, false);
+            if (feature.type === TileFeature.POLYGON) {
+                cr.closePath();
+                cr.strokePreserve();
+                cr.setSourceRGBA(geoJSONStyleObj.fillColor.red,
+                                 geoJSONStyleObj.fillColor.green,
+                                 geoJSONStyleObj.fillColor.blue,
+                                 geoJSONStyleObj.fillAlpha);
+                cr.fill();
+            } else {
+                cr.stroke();
+            }
         });
 
-        content.invalidate();
+        let paintable =
+            Gdk.Texture.new_for_pixbuf(Gdk.pixbuf_get_from_surface(surface, 0, 0,
+                                                                   this.tile_size,
+                                                                   this.tile_size));
+
+        tile.set_paintable(paintable);
+        tile.state = Shumate.State.DONE;
     }
 }
 
diff --git a/src/gpxShapeLayer.js b/src/gpxShapeLayer.js
index d684e2ce..afcd2781 100644
--- a/src/gpxShapeLayer.js
+++ b/src/gpxShapeLayer.js
@@ -18,6 +18,7 @@
  */
 
 import GObject from 'gi://GObject';
+import Shumate from 'gi://Shumate';
 
 import {GeoJSONSource} from './geoJSONSource.js';
 import {ShapeLayer} from './shapeLayer.js';
@@ -39,6 +40,9 @@ export class GpxShapeLayer extends ShapeLayer {
 
         this._mapSource = new GeoJSONSource({ mapView: this._mapView,
                                               markerLayer: this._markerLayer });
+        this._overlayLayer =
+            new Shumate.MapLayer({ map_source: this._mapSource,
+                                   viewport:   this._mapView.map.viewport });
     }
 
     _parseContent() {
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/kmlShapeLayer.js b/src/kmlShapeLayer.js
index cee3997f..985cfba3 100644
--- a/src/kmlShapeLayer.js
+++ b/src/kmlShapeLayer.js
@@ -18,6 +18,7 @@
  */
 
 import GObject from 'gi://GObject';
+import Shumate from 'gi://Shumate';
 
 import {GeoJSONSource} from './geoJSONSource.js';
 import {ShapeLayer} from './shapeLayer.js';
@@ -39,6 +40,9 @@ export class KmlShapeLayer extends ShapeLayer {
 
         this._mapSource = new GeoJSONSource({ mapView: this._mapView,
                                               markerLayer: this._markerLayer });
+        this._overlayLayer =
+            new Shumate.MapLayer({ map_source: this._mapSource,
+                                   viewport:   this._mapView.map.viewport });
     }
 
     _parseContent() {
diff --git a/src/layersPopover.js b/src/layersPopover.js
index 9297b152..d14b0005 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,10 @@ export class LayersPopover extends Gtk.Popover {
             row.set_header(header);
         });
 
+        this._loadLayerButton.connect('clicked', () => this.popdown());
+
+        // for now let's disable the map type swithery, as we only have street
+        /*
         this._layerPreviews = {
             street: {
                 source: MapSource.createStreetSource(),
@@ -133,8 +135,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,10 +197,16 @@ export class LayersPopover extends Gtk.Popover {
             this._aerialLayerButton.active = true;
         }
     }
+    */
 
     _onRemoveClicked(row) {
         this._mapView.removeShapeLayer(row.shapeLayer);
-        if (this._layersListBox.get_children().length <= 0)
+        let numLayers = 0;
+
+        for (let layer of this._layersListBox) {
+            numLayers++;
+        }
+        if (numLayers <= 0)
             this._layersListBox.hide();
     }
 
@@ -211,10 +221,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 ff7d0326..73374f6b 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';
@@ -90,6 +90,10 @@ export class MainWindow extends Gtk.ApplicationWindow {
         return this._placeEntry;
     }
 
+    get sidebar() {
+        return this._sidebar;
+    }
+
     constructor(params) {
         super(params);
 
@@ -98,7 +102,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,9 +112,6 @@ export class MainWindow extends Gtk.ApplicationWindow {
 
         this._sidebar = this._createSidebar();
 
-        this._contextMenu = new ContextMenu({ mapView: this._mapView,
-                                              mainWindow: this });
-
         if (pkg.name.endsWith('.Devel'))
             this.get_style_context().add_class('devel');
 
@@ -119,9 +122,10 @@ export class MainWindow extends Gtk.ApplicationWindow {
         this._initDND();
         this._initPlaceBar();
 
-        this._grid.attach(this._sidebar, 1, 0, 1, 2);
+        this._contextMenu = new ContextMenu({ mapView: this._mapView,
+                                              mainWindow: this });
 
-        this._grid.show_all();
+        this._grid.attach(this._sidebar, 1, 0, 1, 2);
 
         /* 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 +142,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 +151,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;
     }
 
@@ -161,8 +167,8 @@ export class MainWindow extends Gtk.ApplicationWindow {
     }
 
     _initPlaceBar() {
-        this._placeBar = new PlaceBar({ mapView: this._mapView, visible: true });
-        this._placeBarContainer.add(this._placeBar);
+        this._placeBar = new PlaceBar({ mapView: this._mapView, mainWindow: this });
+        this._placeBarContainer.append(this._placeBar);
 
         this.application.bind_property('selected-place',
                                        this._placeBar, 'place',
@@ -170,20 +176,11 @@ export class MainWindow extends Gtk.ApplicationWindow {
     }
 
     _initDND() {
-        this.drag_dest_set(Gtk.DestDefaults.DROP, null, 0);
-        this.drag_dest_add_uri_targets();
+        this._dropTarget = Gtk.DropTarget.new(Gio.File, Gdk.DragAction.COPY);
+        this.add_controller(this._dropTarget);
 
-        this.connect('drag-motion', (widget, ctx, x, y, time) => {
-            Gdk.drag_status(ctx, Gdk.DragAction.COPY, time);
-            return true;
-        });
-
-        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]);
         });
     }
 
@@ -211,11 +208,11 @@ export class MainWindow extends Gtk.ApplicationWindow {
             },
             'zoom-in': {
                 accels: ['plus', '<Primary>plus', 'KP_Add', '<Primary>KP_Add', 'equal', '<Primary>equal'],
-                onActivate: () => this._mapView.view.zoom_in()
+                onActivate: () => this._mapView.zoomIn()
             },
             'zoom-out': {
                 accels: ['minus', '<Primary>minus', 'KP_Subtract', '<Primary>KP_Subtract'],
-                onActivate:  () => this._mapView.view.zoom_out()
+                onActivate:  () => this._mapView.zoomOut()
             },
             'show-scale': {
                 accels: ['<Primary>S'],
@@ -234,9 +231,19 @@ export class MainWindow extends Gtk.ApplicationWindow {
                 accels: ['<Primary>O'],
                 onActivate: () => this._onOpenShapeLayer()
             },
+            'show-main-menu': {
+                accels: ['F10'],
+                onActivate: () => this._showMainMenu()
+            },
             'export-as-image': {
                 onActivate: () => this._onExportActivated()
-            }
+            },
+            'route-from-here': {},
+            'add-intermediate-destination': {},
+            'route-to-here': {},
+            'whats-here': {},
+            'copy-location': {},
+            'add-osm-location': {}
         };
 
         // when aerial tiles are available, add shortcuts to switch
@@ -256,67 +263,38 @@ 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::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
-         * hijack the key-press to the main window and make sure that
-         * they reach the entry before they can be swallowed as accelerator.
-         */
-        /* 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;
-            }
+        //this._placeEntry.set_key_capture_widget(this)
 
-            if (focusWidget instanceof Gtk.Entry)
-                return focusWidget.event(event);
+        let viewport = this._mapView.map.viewport;
 
-            return false;
-        });
+        viewport.connect('notify::zoom-level',
+                         this._updateZoomButtonsSensitivity.bind(this));
+        viewport.connect('notify::max-zoom-level',
+                         this._updateZoomButtonsSensitivity.bind(this));
+        viewport.connect('notify::min-zoom-level',
+                         this._updateZoomButtonsSensitivity.bind(this));
 
-        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));
+        this._updateZoomButtonsSensitivity();
     }
 
     _updateZoomButtonsSensitivity() {
-        let zoomLevel = this._mapView.view.zoom_level;
-        let maxZoomLevel = this._mapView.view.max_zoom_level;
-        let minZoomLevel = this._mapView.view.min_zoom_level;
+        let zoomLevel = this._mapView.map.viewport.zoom_level;
+        let maxZoomLevel = this._mapView.map.viewport.max_zoom_level;
+        let minZoomLevel = this._mapView.map.viewport.min_zoom_level;
         let zoomInAction = this.lookup_action("zoom-in");
         let zoomOutAction = this.lookup_action("zoom-out");
 
@@ -345,7 +323,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 +335,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 +356,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 +371,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 +388,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() {
@@ -479,36 +437,27 @@ export class MainWindow extends Gtk.ApplicationWindow {
         });
     }
 
-    _activateExport() {
-        let view = this._mapView.view;
-        let surface = view.to_surface(true);
-        let bbox = view.get_bounding_box();
-        let [latitude, longitude] = bbox.get_center();
+    _onExportActivated() {
+        let {x, y, width, height} = this._mapView.get_allocation();
+        let paintable = new Gtk.WidgetPaintable({ widget: this._mapView });
+        let [latitude, longitude] =
+            this._mapView.map.viewport.widget_coords_to_location(this._mapView.map,
+                                                                 width / 2,
+                                                                 height / 2);
 
         let dialog = new ExportViewDialog({
             transient_for: this,
             modal: true,
-            surface: surface,
+            paintable: paintable,
             latitude: latitude,
             longitude: longitude,
+            width: width,
+            height: height,
             mapView: this._mapView
         });
 
         dialog.connect('response', () => dialog.destroy());
-        dialog.show_all();
-    }
-
-    _onExportActivated() {
-        if (this._mapView.view.state === Champlain.State.DONE) {
-            this._activateExport();
-        } else {
-            let notifyId = this._mapView.view.connect('notify::state', () => {
-                if (this._mapView.view.state === Champlain.State.DONE) {
-                    this._mapView.view.disconnect(notifyId);
-                    this._activateExport();
-                }
-            });
-        }
+        dialog.show();
     }
 
     _printRouteActivate() {
@@ -581,7 +530,6 @@ export class MainWindow extends Gtk.ApplicationWindow {
         copyrightLabel.show();
 
         aboutDialog.show();
-        aboutDialog.connect('response', () => aboutDialog.destroy());
     }
 
     _getAttribution() {
@@ -653,11 +601,16 @@ export class MainWindow extends Gtk.ApplicationWindow {
         });
         this._fileChooser.show();
     }
+
+    _showMainMenu() {
+        this._mainMenuButton.activate();
+    }
 }
 
 GObject.registerClass({
     Template: 'resource:///org/gnome/Maps/ui/main-window.ui',
     InternalChildren: [ 'headerBar',
+                        'mainMenuButton',
                         'grid',
                         'actionBar',
                         'actionBarRevealer',
diff --git a/src/mapBubble.js b/src/mapBubble.js
index 88f29775..cc74dd04 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';
@@ -48,10 +49,6 @@ export class MapBubble extends Gtk.Popover {
         let mapView = params.mapView;
         delete params.mapView;
 
-        params.relative_to = mapView;
-        params.transitions_enabled = false;
-        params.modal = false;
-
         super(params);
 
         let content = new PlaceView({ place, mapView, visible: true });
@@ -59,9 +56,9 @@ export class MapBubble extends Gtk.Popover {
         let scrolledWindow = new MapBubbleScrolledWindow({ visible: true,
                                                            propagateNaturalWidth: true,
                                                            propagateNaturalHeight: true,
-                                                           hscrollbarPolicy: Gtk.PolicyType.NEVER });
-        scrolledWindow.add(content);
-        this.add(scrolledWindow);
+                                                           hscrollbarPolicy: Gtk.PolicyType.NEVER,
+                                                           child: content });
+        this.child = scrolledWindow;
 
         this.get_style_context().add_class("map-bubble");
     }
@@ -70,6 +67,39 @@ 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) {
+        log('measure: ' + orientation + ', ' + forSize);
+
+        let [cm, cn, cbx, cby] = this.child.measure(orientation, forSize);
+
+        log('child measure: ' + cm + ', ' + cn);
+
+        // TODO: fix
+        return [500, 500, null, null];
+
+        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,17 +114,29 @@ 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();
 
+            log('allocation: ' + [x, y]);
+
+            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())
                                 * this.scale_factor;
 
+            // TODO: how to do this?
+            let radius = 0;
+
             // bottom left
             cr.moveTo(0, height);
             cr.lineTo(0, radius);
@@ -103,10 +145,12 @@ export class MapBubbleScrolledWindow extends Gtk.ScrolledWindow {
             cr.lineTo(width, height);
 
             cr.clip();
+            cr.$dispose();
         }
 
-        return super.vfunc_draw(cr);
+        super.vfunc_snapshot(snapshot);
     }
+    */
 }
 
 GObject.registerClass(MapBubbleScrolledWindow);
diff --git a/src/mapMarker.js b/src/mapMarker.js
index 8e3842cc..ecc7644d 100644
--- a/src/mapMarker.js
+++ b/src/mapMarker.js
@@ -20,19 +20,19 @@
  */
 
 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 Graphene from 'gi://Graphene';
 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;
@@ -50,12 +50,16 @@ export class MapMarker extends Champlain.Marker {
         this._place = place;
         this._mapView = mapView;
 
-        this.connect('notify::size', this._translateMarkerPosition.bind(this));
+        this._image = new Gtk.Image({ icon_size: Gtk.IconSize.NORMAL });
+        this.child = this._image;
+
         if (this._mapView) {
-            this._view = this._mapView.view;
-            this.connect('notify::selected', this._onMarkerSelected.bind(this));
-            this.connect('button-press', this._onButtonPress.bind(this));
-            this.connect('touch-event', this._onTouchEvent.bind(this));
+            this._viewport = this._mapView.map.viewport;
+
+            this._buttonPressGesture = new Gtk.GestureSingle();
+            this.add_controller(this._buttonPressGesture);
+            this._buttonPressGesture.connect('begin',
+                                             () => this._onMarkerSelected());
 
             // Some markers are draggable, we want to sync the marker location and
             // the location saved in the GeocodePlace
@@ -70,12 +74,12 @@ export class MapMarker extends Champlain.Marker {
 
             this.place.connect('notify::location', this._onLocationChanged.bind(this));
 
-            this._view.bind_property('latitude', this, 'view-latitude',
-                                     GObject.BindingFlags.DEFAULT);
-            this._view.bind_property('longitude', this, 'view-longitude',
-                                     GObject.BindingFlags.DEFAULT);
-            this._view.bind_property('zoom-level', this, 'view-zoom-level',
-                                     GObject.BindingFlags.DEFAULT);
+            this._viewport.bind_property('latitude', this, 'view-latitude',
+                                         GObject.BindingFlags.DEFAULT);
+            this._viewport.bind_property('longitude', this, 'view-longitude',
+                                         GObject.BindingFlags.DEFAULT);
+            this._viewport.bind_property('zoom-level', this, 'view-zoom-level',
+                                         GObject.BindingFlags.DEFAULT);
             this.connect('notify::view-latitude', this._onViewUpdated.bind(this));
             this.connect('notify::view-longitude', this._onViewUpdated.bind(this));
             this.connect('notify::view-zoom-level', this._onViewUpdated.bind(this));
@@ -84,60 +88,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();
-            let pixbuf;
-
-            if (color) {
-                let info = theme.lookup_icon(name, size, 0);
-                pixbuf = info.load_symbolic(color, null, null, null)[0];
-            } else {
-                pixbuf = theme.load_icon(name, size, 0);
-            }
-
-            let canvas = new Clutter.Canvas({ width: pixbuf.get_width(),
-                                              height: pixbuf.get_height() });
-
-            canvas.connect('draw', (canvas, cr) => {
-                cr.setOperator(Cairo.Operator.CLEAR);
-                cr.paint();
-                cr.setOperator(Cairo.Operator.OVER);
-
-                Gdk.cairo_set_source_pixbuf(cr, pixbuf, 0, 0);
-                cr.paint();
-
-                this._surface = cr.getTarget();
-            });
-
-            let actor = new Clutter.Actor();
-            actor.set_content(canvas);
-            actor.set_size(pixbuf.get_width(), pixbuf.get_height());
-            canvas.invalidate();
-
-            return actor;
-        } catch (e) {
-            Utils.debug('Failed to load image: %s'.format(e.message));
-            return null;
-        }
-    }
-
     _onButtonPress(marker, event) {
         // Zoom in on marker on double-click
         if (event.get_click_count() > 1) {
@@ -148,17 +98,6 @@ export class MapMarker extends Champlain.Marker {
         }
     }
 
-    _onTouchEvent(marker, event) {
-        if (event.type() == Clutter.EventType.TOUCH_BEGIN)
-            this.selected = true;
-
-        return Clutter.EVENT_STOP;
-    }
-
-    _translateMarkerPosition() {
-        this.set_translation(-this.anchor.x, -this.anchor.y, 0);
-    }
-
     _onLocationChanged() {
         this.set_location(this.place.location.latitude, this.place.location.longitude);
 
@@ -191,6 +130,7 @@ export class MapMarker extends Champlain.Marker {
             if (this._place.name) {
                 this._bubble = new MapBubble({ place: this._place,
                                                mapView: this._mapView });
+                this._bubble.set_parent(this._mapView);
             }
         }
 
@@ -203,13 +143,14 @@ export class MapMarker extends Champlain.Marker {
     }
 
     _positionBubble(bubble) {
-        let [tx, ty, tz] = this.get_translation();
-        let x = this._view.longitude_to_x(this.longitude);
-        let y = this._view.latitude_to_y(this.latitude);
-        let mapSize = this._mapView.get_allocation();
-
-        let pos = new Gdk.Rectangle({ x: x + tx - this.bubbleSpacing,
-                                      y: y + ty - this.bubbleSpacing,
+        let [x, y] =
+            this._viewport.location_to_widget_coords(this._mapView.map,
+                                                     this.latitude,
+                                                     this.longitude);
+        let mapSize = this._mapView.map.get_allocation();
+
+        let pos = new Gdk.Rectangle({ x: x - this.bubbleSpacing,
+                                      y: y - this.bubbleSpacing,
                                       width: this.width + this.bubbleSpacing * 2,
                                       height: this.height + this.bubbleSpacing * 2 });
         bubble.pointing_to = pos;
@@ -232,7 +173,7 @@ export class MapMarker extends Champlain.Marker {
 
     _hideBubbleOn(signal, duration) {
         let sourceId = null;
-        let signalId = this._view.connect(signal, () => {
+        let signalId = this._viewport.connect(signal, () => {
             if (sourceId)
                 GLib.source_remove(sourceId);
             else
@@ -253,16 +194,7 @@ export class MapMarker extends Champlain.Marker {
             // We still listening for the signal to refresh
             // the existent timeout
             if (!sourceId)
-                this._view.disconnect(signalId);
-        });
-
-        Utils.once(this, 'notify::selected', () => {
-            // When the marker gets deselected, we need to ensure
-            // that the timeout callback is not called anymore.
-            if (sourceId) {
-                GLib.source_remove(sourceId);
-                this._view.disconnect(signalId);
-            }
+                this._viewport.disconnect(signalId);
         });
     }
 
@@ -282,12 +214,10 @@ export class MapMarker extends Champlain.Marker {
                 this.selected = false;
         });
 
-        let viewTouchEventSignalId =
-            this._view.connect('touch-event', () => this.set_selected(false));
-
         let goingToSignalId = this._mapView.connect('going-to', () => {
             this.set_selected(false);
         });
+        /*
         let buttonPressSignalId =
             this._view.connect('button-press-event', () => {
                 this.set_selected(false);
@@ -304,28 +234,32 @@ export class MapMarker extends Champlain.Marker {
                 this.set_selected(false);
             }
         });
+        */
 
         Utils.once(this.bubble, 'closed', () => {
             this._mapView.disconnect(markerSelectedSignalId);
             this._mapView.disconnect(goingToSignalId);
-            this._view.disconnect(buttonPressSignalId);
-            this._view.disconnect(viewTouchEventSignalId);
-            this.disconnect(parentSetSignalId);
-            this.disconnect(dragMotionSignalId);
+            //this._view.disconnect(buttonPressSignalId);
+            //this._view.disconnect(viewTouchEventSignalId);
+            //this.disconnect(parentSetSignalId);
+            //this.disconnect(dragMotionSignalId);
 
-            this._bubble.destroy();
+            //this._bubble.destroy();
             delete this._bubble;
         });
     }
 
     _isInsideView() {
-        let [tx, ty, tz] = this.get_translation();
-        let x = this._view.longitude_to_x(this.longitude);
-        let y = this._view.latitude_to_y(this.latitude);
-        let mapSize = this._mapView.get_allocation();
-
-        return x + tx + this.width > 0 && x + tx < mapSize.width &&
-               y + ty + this.height > 0 && y + ty < mapSize.height;
+        let [x, y] = this._viewport.location_to_widget_coords(this._mapView.map,
+                                                              this.latitude,
+                                                              this.longitude);
+        let markerSize = this.get_allocation();
+        let mapSize = this._mapView.map.get_allocation();
+        let tx = markerSize.width / 2;
+        let ty = markerSize.height / 2;
+
+        return x + tx/2 > 0 && x - tx/2 < mapSize.width &&
+               y + ty/2 > 0 && y - ty/2 < mapSize.height;
     }
 
     _onViewUpdated() {
@@ -340,14 +274,14 @@ export class MapMarker extends Champlain.Marker {
     showBubble() {
         if (this.bubble && !this.bubble.visible && this._isInsideView() && 
!Application.application.adaptive_mode) {
             this._initBubbleSignals();
-            this.bubble.show();
+            this.bubble.popup();
             this._positionBubble(this.bubble);
         }
     }
 
     hideBubble() {
         if (this._bubble)
-            this._bubble.hide();
+            this._bubble.popdown();
     }
 
     get walker() {
@@ -367,43 +301,69 @@ export class MapMarker extends Champlain.Marker {
     }
 
     goToAndSelect(animate) {
-        Utils.once(this, 'gone-to', () => this.selected = true);
+        Utils.once(this, 'gone-to', () => {
+            if (this.bubble)
+                this.showBubble();
+        });
 
         this.goTo(animate);
     }
 
     _onMarkerSelected() {
-        if (this.selected) {
-            if (this.bubble) {
+        if (this.bubble) {
+            if (!this._bubble.visible) {
                 this.showBubble();
                 Application.application.selected_place = this._place;
+            } else {
+                this.hideBubble();
+                Application.application.selected_place = null;
             }
         } else {
-            this.hideBubble();
-            Application.application.selected_place = null;
+            if (!Application.application.selected_place)
+                Application.application.selected_place = this._place;
+            else
+                Application.application.selected_place = null;
         }
     }
 
     _onAdaptiveModeChanged() {
-        if (this.selected) {
-            if (!Application.application.adaptive_mode) {
-                this.showBubble();
-            } else {
-                this.hideBubble();
-            }
+        if (!Application.application.adaptive_mode) {
+            this.showBubble();
+        } else {
+            this.hideBubble();
+        }
+    }
+
+    _paintableFromIconName(name, size, color) {
+        let display = Gdk.Display.get_default();
+        let theme = Gtk.IconTheme.get_for_display(display);
+        let iconPaintable = theme.lookup_icon(name, null, size,
+                                              this.scale_factor,
+                                              Gtk.TextDirection.NONE, 0);
+
+        if (color) {
+            let snapshot = Gtk.Snapshot.new();
+            let rect = new Graphene.Rect();
+
+            iconPaintable.snapshot_symbolic(snapshot, size, size, [color]);
+            rect.init(0, 0, size, size);
+
+            let node = snapshot.to_node();
+            let renderer = this._mapView.get_native().get_renderer();
+
+            return renderer.render_texture(node, rect);
+        } else {
+            return iconPaintable;
         }
     }
 }
 
 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..ac653c3f 100644
--- a/src/mapSource.js
+++ b/src/mapSource.js
@@ -17,69 +17,50 @@
  * Author: Jonas Danielsson <jonas threetimestwo org>
  */
 
-import Champlain from 'gi://Champlain';
+import GLib from 'gi://GLib';
+import Shumate from 'gi://Shumate';
 
 import * as Service from './service.js';
 import * as Utils from './utils.js';
 
-const _FILE_CACHE_SIZE_LIMIT = (10 * 1024 * 1024); /* 10Mb */
-const _MEMORY_CACHE_SIZE_LIMIT = 100; /* number of tiles */
-
-function _createTileSource(source) {
-    let tileSource = new Champlain.NetworkTileSource({
-        id: source.id,
-        name: source.name,
-        license: source.license,
-        license_uri: source.license_uri,
-        min_zoom_level: source.min_zoom_level,
-        max_zoom_level: source.max_zoom_level,
-        tile_size: source.tile_size,
-        renderer: new Champlain.ImageRenderer(),
-        uri_format: source.uri_format
-    });
-    tileSource.max_conns = source.max_connections;
-    return tileSource;
+/* Converts a tile URI format from Champlain style to Shumate style.
+ * e.g. from
+ * https://tile.openstreetmap.org/#Z#/#X#/#Y#.png
+ * to
+ * https://tile.openstreetmap.org/{Z}/{X}/{Y}.png
+ */
+function convertUriFormatFromChamplain(uriFormat) {
+     return uriFormat.replace('#Z#', '{z}').replace('#X#', '{x}').replace('#Y#', '{y}');
 }
 
-function _createCachedSource(source) {
-    let tileSource = _createTileSource(source);
-
-    let fileCache = new Champlain.FileCache({
-        size_limit: _FILE_CACHE_SIZE_LIMIT,
-        renderer: new Champlain.ImageRenderer()
-    });
+function createTileDownloader(source) {
+    let template = convertUriFormatFromChamplain(source.uri_format);
 
-    let memoryCache = new Champlain.MemoryCache({
-        size_limit: _MEMORY_CACHE_SIZE_LIMIT,
-        renderer: new Champlain.ImageRenderer()
-    });
+    return new Shumate.TileDownloader({ url_template: template });
+}
 
-    let errorSource = new Champlain.NullTileSource({
-        renderer: new Champlain.ImageRenderer()
+function createRasterRenderer(source) {
+    return new Shumate.RasterRenderer({
+        id:             source.id,
+        name:           source.name,
+        license:        source.license,
+        license_uri:    source.license_uri,
+        min_zoom_level: source.min_zoom_level,
+        max_zoom_level: source.max_zoom_level,
+        tile_size:      source.tile_size,
+        projection:     Shumate.MapProjection.MERCATOR,
+        data_source:    createTileDownloader(source)
     });
-
-    /*
-     * When the a source in the chain fails to load a given tile
-     * the next one in the chain tries instead. Until we get to the error
-     * source.
-     */
-    let sourceChain = new Champlain.MapSourceChain();
-    sourceChain.push(errorSource);
-    sourceChain.push(tileSource);
-    sourceChain.push(fileCache);
-    sourceChain.push(memoryCache);
-
-    return sourceChain;
 }
 
 export function createAerialSource() {
-    return _createCachedSource(Service.getService().tiles.aerial);
+    return createRasterRenderer(Service.getService().tiles.aerial);
 }
 
 export function createStreetSource() {
-    return _createCachedSource(Service.getService().tiles.street);
+    return createRasterRenderer(Service.getService().tiles.street);
 }
 
 export function createPrintSource() {
-    return _createCachedSource(Service.getService().tiles.print);
+    return createRasterRenderer(Service.getService().tiles.print);
 }
diff --git a/src/mapView.js b/src/mapView.js
index 5aa43155..81993de3 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';
 
@@ -55,6 +53,7 @@ import * as Utils from './utils.js';
 
 const _LOCATION_STORE_TIMEOUT = 500;
 const MapMinZoom = 2;
+const MapMaxZoom = 19;
 
 /* threashhold for route color luminance when we consider it more or less
  * as white, and draw an outline on the path */
@@ -77,7 +76,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 +101,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');
@@ -118,20 +118,30 @@ export class MapView extends GtkChamplain.Embed {
         this.notify('routeShowing');
     }
 
-    constructor(params) {
-        super();
+    get mapSource() {
+        return this._mapSource;
+    }
 
-        let mapType = params.mapType || this._getStoredMapType();
+    constructor(params) {
+        let mapTypeParam = params.mapType;
         delete params.mapType;
 
-        this._mainWindow = params.mainWindow;
+        let mainWindow = params.mainWindow;
         delete params.mainWindow;
 
+        super(params);
+
+        this._mainWindow = mainWindow;
         this._storeId = 0;
-        this.view = this._initView();
-        this._initLayers();
+        this.map = this._initMap();
+
+        this.child = this.map;
 
-        this.setMapType(mapType);
+        this._initLicense();
+        this.setMapType(mapTypeParam ?? this._getStoredMapType());
+
+        this._initScale();
+        this._initLayers();
 
         if (Application.normalStartup)
             this._goToStoredLocation();
@@ -145,66 +155,108 @@ export class MapView extends GtkChamplain.Embed {
         this._connectRouteSignals();
     }
 
-    _initScale(view) {
+    zoomIn() {
+        let zoom = this.map.viewport.zoom_level;
+        let maxZoom = this.map.viewport.max_zoom_level;
+        let fraction = zoom - Math.floor(zoom);
+
+        /* if we're zoomed to a fraction close to the next higher even zoom level
+         * zoom to the next higher after that to avoid just going a tiny bit
+         */
+        this.map.go_to_full_with_duration(this.map.viewport.latitude,
+                                          this.map.viewport.longitude,
+                                          Math.min(fraction < 0.7 ?
+                                                   Math.floor(zoom + 1) :
+                                                   Math.floor(zoom + 2),
+                                                   maxZoom),
+                                          200);
+    }
+
+    zoomOut() {
+        let zoom = this.map.viewport.zoom_level;
+        let minZoom = this.map.viewport.min_zoom_level;
+        let fraction = zoom - Math.floor(zoom);
+
+        /* if we're zoomed to a fraction close to the next lower even zoom level
+         * zoom to the next lower after that to avoid just going a tiny bit
+         */
+        this.map.go_to_full_with_duration(this.map.viewport.latitude,
+                                          this.map.viewport.longitude,
+                                          Math.max(fraction > 0.3 ?
+                                                   Math.floor(zoom) :
+                                                   Math.floor(zoom - 1),
+                                                   minZoom),
+                                          200);
+    }
+
+    _initScale() {
         let showScale = Application.settings.get('show-scale');
 
-        this._scale = new Champlain.Scale({ visible: showScale });
-        this._scale.connect_view(view);
+        this._scale = new Shumate.Scale({ visible:  showScale,
+                                          viewport: this.map.viewport,
+                                          halign:   Gtk.Align.START,
+                                          valign:   Gtk.Align.END });
 
         if (Utils.getMeasurementSystem() === Utils.METRIC_SYSTEM)
-            this._scale.unit = Champlain.Unit.KM;
+            this._scale.unit = Shumate.Unit.METRIC;
         else
-            this._scale.unit = Champlain.Unit.MILES;
+            this._scale.unit = Shumate.Unit.IMPERIAL;
+
+        this.add_overlay(this._scale);
+    }
 
-        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);
+    _initLicense() {
+        this._license = new Shumate.License({ halign: Gtk.Align.END,
+                                              valign: Gtk.Align.END });
+        this.add_overlay(this._license);
     }
 
-    _initView() {
-        let view = this.get_view();
+    _initMap() {
+        let map = new Shumate.Map();
 
-        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;
+        map.viewport.max_zoom_level = MapMaxZoom;
+        map.viewport.min_zoom_level = MapMinZoom;
 
-        view.connect('notify::latitude', this._onViewMoved.bind(this));
+        map.viewport.connect('notify::latitude', this._onViewMoved.bind(this));
         // switching map type will set view min-zoom-level from map source
-        view.connect('notify::min-zoom-level', () => {
-            if (view.min_zoom_level < MapMinZoom) {
-                view.min_zoom_level = MapMinZoom;
+        map.viewport.connect('notify::min-zoom-level', () => {
+            if (map.viewport.min_zoom_level < MapMinZoom) {
+                map.viewport.min_zoom_level = MapMinZoom;
             }
         });
 
         Application.settings.connect('changed::show-scale',
                                      this._onShowScaleChanged.bind(this));
 
-        this._initScale(view);
-        return view;
+        return map;
     }
 
     /* 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),
+                                         green:  Color.parseColor(lineColor, 1),
+                                         blue:   Color.parseColor(lineColor, 2),
+                                         alpha:  1.0 });
+        let routeLayer = new Shumate.PathLayer({ viewport: this.map.viewport,
+                                                 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);
+        this.map.insert_layer_behind(routeLayer, this._instructionMarkerLayer);
 
         return routeLayer;
     }
@@ -213,23 +265,30 @@ export class MapView extends GtkChamplain.Embed {
         this._routeLayers.forEach((routeLayer) => {
             routeLayer.remove_all();
             routeLayer.visible = false;
-            this.view.remove_layer(routeLayer);
+            this.map.remove_layer(routeLayer);
         });
 
         this._routeLayers = [];
     }
 
     _initLayers() {
-        let mode = Champlain.SelectionMode.SINGLE;
+        let mode = Gtk.SelectionMode.MULTIPLE;
 
-        this._userLocationLayer = new Champlain.MarkerLayer({ selection_mode: mode });
-        this.view.add_layer(this._userLocationLayer);
+        this._userLocationLayer =
+            new Shumate.MarkerLayer({ selection_mode: mode,
+                                      viewport: this.map.viewport });
+        this.map.add_layer(this._userLocationLayer);
 
-        this._placeLayer = new Champlain.MarkerLayer({ selection_mode: mode });
-        this.view.add_layer(this._placeLayer);
+        this._placeLayer =
+            new Shumate.MarkerLayer({ selection_mode: mode,
+                                      viewport: this.map.viewport });
+        this.map.insert_layer_above(this._placeLayer, this._userLocationLayer);
 
-        this._instructionMarkerLayer = new Champlain.MarkerLayer({ selection_mode: mode });
-        this.view.add_layer(this._instructionMarkerLayer);
+        this._instructionMarkerLayer =
+            new Shumate.MarkerLayer({ selection_mode: mode,
+                                      viewport: this.map.viewport });
+        this.map.insert_layer_above(this._instructionMarkerLayer,
+                                     this._placeLayer);
 
         ShapeLayer.SUPPORTED_TYPES.push(GeoJSONShapeLayer);
         ShapeLayer.SUPPORTED_TYPES.push(KmlShapeLayer);
@@ -238,11 +297,6 @@ export class MapView extends GtkChamplain.Embed {
         this._routeLayers = [];
     }
 
-    _ensureInstructionLayerAboveRouteLayers() {
-        this.view.remove_layer(this._instructionMarkerLayer);
-        this.view.add_layer(this._instructionMarkerLayer);
-    }
-
     _connectRouteSignals() {
         let route = Application.routingDelegator.graphHopper.route;
         let transitPlan = Application.routingDelegator.transitRouter.plan;
@@ -276,7 +330,12 @@ export class MapView extends GtkChamplain.Embed {
             this.routeShowing = false;
         });
 
-        query.connect('notify', () => this.routingOpen = query.isValid());
+        query.connect('notify', () => {
+            this.routingOpen = query.isValid();
+            this._clearRouteLayers();
+            this._instructionMarkerLayer.remove_all();
+            this.routeShowing = false;
+        });
     }
 
     _getStoredMapType() {
@@ -300,22 +359,29 @@ export class MapView extends GtkChamplain.Embed {
         if (this._mapType && this._mapType === mapType)
             return;
 
-        let overlay_sources = this.view.get_overlay_sources();
+        //let overlay_sources = this.view.get_overlay_sources();
 
         this._mapType = mapType;
 
+        let mapSource;
+
         if (mapType !== MapView.MapType.LOCAL) {
             let tiles = Service.getService().tiles;
 
             if (mapType === MapView.MapType.AERIAL && tiles.aerial)
-                this.view.map_source = MapSource.createAerialSource();
+                mapSource = MapSource.createAerialSource();
             else
-                this.view.map_source = MapSource.createStreetSource();
+                mapSource = MapSource.createStreetSource();
+
+            // update license
+            if (this._mapSource)
+                this._license.remove_map_source(this._mapSource);
+
+            this._license.append_map_source(mapSource);
 
             Application.settings.set('map-type', mapType);
         } else {
-            let renderer = new Champlain.ImageRenderer();
-            let source = new GnomeMaps.FileTileSource({
+            let source = new GnomeMaps.FileDataSource({
                 path: Utils.getBufferText(Application.application.local_tile_path),
                 renderer: renderer,
                 tile_size: Application.application.local_tile_size || 512
@@ -323,10 +389,14 @@ export class MapView extends GtkChamplain.Embed {
             try {
                 source.prepare();
 
-                this.view.map_source = source;
-                this.view.world = source.world;
-                let [lat, lon] = this.view.world.get_center();
-                this.view.center_on(lat, lon);
+                mapSource =
+                    new Shumate.RasterRenderer({ id: 'local',
+                                                 name: 'local',
+                                                 min_zoom_level: source.min_zoom_level,
+                                                 max_zoom_level: source.max_zoom_level,
+                                                 tile_size:      Application.application.local_tile_size ?? 
512,
+                                                 projection:     Shumate.MapProjection.MERCATOR,
+                                                 data_source:    source });
             } catch(e) {
                 this.setMapType(MapView.MapType.STREET);
                 Application.application.local_tile_path = false;
@@ -334,8 +404,12 @@ export class MapView extends GtkChamplain.Embed {
             }
         }
 
-        overlay_sources.forEach((source) => this.view.add_overlay_source(source, 255));
+        let mapLayer = new Shumate.MapLayer({ map_source: mapSource,
+                                                  viewport:   this.map.viewport });
 
+        this.map.add_layer(mapLayer);
+        this._mapSource = mapSource;
+        this.map.viewport.set_reference_map_source(mapSource);
         this.emit("map-type-changed", mapType);
     }
 
@@ -346,10 +420,16 @@ export class MapView extends GtkChamplain.Embed {
     _checkIfFileSizeNeedsConfirmation(files) {
         let confirmLoad = false;
         let totalFileSizeMB = 0;
-        files.forEach((file) => {
-            totalFileSizeMB += file.query_info(Gio.FILE_ATTRIBUTE_STANDARD_SIZE, 
+        let file;
+        let i = 0;
+
+        do {
+            let file = files.get_item(i);
+
+            totalFileSizeMB += file.query_info(Gio.FILE_ATTRIBUTE_STANDARD_SIZE,
                                                0, null).get_size();
-        });
+            i++;
+        } while (file);
         totalFileSizeMB = totalFileSizeMB / (1024 * 1024);
         if (totalFileSizeMB > FILE_SIZE_LIMIT_MB) {
             confirmLoad = true;
@@ -403,9 +483,11 @@ export class MapView extends GtkChamplain.Embed {
 
     _loadShapeLayers(files) {
         let bbox = new BoundingBox();
-        this._remainingFilesToLoad = files.length;
+        this._remainingFilesToLoad = files.get_n_items();
+
+        for (let i = 0; i < files.get_n_items(); i++) {
+            let file = files.get_item(i);
 
-        files.forEach((file) => {
             try {
                 let i = this._findShapeLayerIndex(file);
                 let layer = (i > -1) ? this.shapeLayerStore.get_item(i) : null;
@@ -421,7 +503,7 @@ export class MapView extends GtkChamplain.Embed {
                 let msg = _("Failed to open layer");
                 Utils.showDialog(msg, Gtk.MessageType.ERROR, this._mainWindow);
             }
-        });
+        }
     }
 
     removeShapeLayer(shapeLayer) {
@@ -481,19 +563,32 @@ export class MapView extends GtkChamplain.Embed {
     }
 
     gotoAntipode() {
-        let lat = -this.view.latitude;
-        let lon = this.view.longitude > 0 ?
-                  this.view.longitude - 180 : this.view.longitude + 180;
+        let lat = -this.map.viewport.latitude;
+        let lon = this.map.viewport.longitude > 0 ?
+                  this.map.viewport.longitude - 180 :
+                  this.map.viewport.longitude + 180;
         let place =
             new Place({ location: new Location({ latitude: lat,
                                                  longitude: lon }),
-                        initialZoom: this.view.zoom_level });
+                        initialZoom: this.map.viewport.zoom_level });
 
         new MapWalker(place, this).goTo(true);
     }
 
+    _getViewBBox() {
+        let {x, y, width, height} = this.get_allocation();
+        let [top, left] = this.map.viewport.widget_coords_to_location(0, 0);
+        let [bottom, right] =
+            this.map.viewport.widget_coords_to_location(width - 1, height - 1);
+
+        return new BoundingBox({ left:   left,
+                                 top:    top,
+                                 right:  right,
+                                 bottom: bottom });
+    }
+
     userLocationVisible() {
-        let box = this.view.get_bounding_box();
+        let box = this._getViewBBox();
 
         return box.covers(this._userLocation.latitude, this._userLocation.longitude);
     }
@@ -512,7 +607,6 @@ export class MapView extends GtkChamplain.Embed {
             let place = Application.geoclue.place;
             this._userLocation = new UserLocationMarker({ place: place,
                                                           mapView: this });
-            this._userLocationLayer.remove_all();
             this._userLocation.addToLayer(this._userLocationLayer);
         }
 
@@ -522,10 +616,11 @@ export class MapView extends GtkChamplain.Embed {
     }
 
     _storeLocation() {
-        let zoom = this.view.zoom_level;
-        let location = [this.view.latitude, this.view.longitude];
+        let viewport = this.map.viewport;
+        let zoom = viewport.zoom_level;
+        let location = [viewport.latitude, viewport.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
          */
@@ -544,25 +639,27 @@ export class MapView extends GtkChamplain.Embed {
             let [lat, lon] = location;
             let zoom = Application.settings.get('zoom-level');
 
-            if (zoom >= this.view.min_zoom_level &&
-                zoom <= this.view.max_zoom_level)
-                this.view.zoom_level = Application.settings.get('zoom-level');
-            else
-                Utils.debug('Invalid initial zoom level: ' + zoom);
-
             if (lat >= MapView.MIN_LATITUDE && lat <= MapView.MAX_LATITUDE &&
-                lon >= MapView.MIN_LONGITUDE && lon <= MapView.MAX_LONGITUDE)
-                this.view.center_on(location[0], location[1]);
-            else
+                lon >= MapView.MIN_LONGITUDE && lon <= MapView.MAX_LONGITUDE) {
+                this.map.viewport.latitude = lat;
+                this.map.viewport.longitude = lon;
+                if (zoom >= this.map.viewport.min_zoom_level &&
+                    zoom <= this.map.viewport.max_zoom_level) {
+                    this.map.viewport.zoom_level = zoom;
+                } else {
+                    Utils.debug('Invalid initial zoom level: ' + zoom);
+                }
+            } else {
                 Utils.debug('Invalid initial coordinates: ' + lat + ', ' + lon);
+            }
         } else {
             /* bounding box. for backwards compatibility, not used anymore */
             let bbox = new BoundingBox({ top: location[0],
                                          bottom: location[1],
                                          left: location[2],
                                          right: location[3] });
-            this.view.connect("notify::realized", () => {
-                if (this.view.realized)
+            this.map.connect("notify::realized", () => {
+                if (this.map.realized)
                     this.gotoBBox(bbox, true);
             });
         }
@@ -583,13 +680,13 @@ export class MapView extends GtkChamplain.Embed {
                                                         left   : bbox.left,
                                                         right  : bbox.right })
         });
-        new MapWalker(place, this).goTo(true, linear);
+        new MapWalker(place, this).zoomToFit();
     }
 
     getZoomLevelFittingBBox(bbox) {
-        let mapSource = this.view.get_map_source();
+        let mapSource = this._mapSource;
         let goodSize = false;
-        let zoomLevel = this.view.max_zoom_level;
+        let zoomLevel = this.map.viewport.max_zoom_level;
 
         do {
 
@@ -597,15 +694,15 @@ export class MapView extends GtkChamplain.Embed {
             let minY = mapSource.get_y(zoomLevel, bbox.bottom);
             let maxX = mapSource.get_x(zoomLevel, bbox.right);
             let maxY = mapSource.get_y(zoomLevel, bbox.top);
+            let {x, y, width, height} = this.get_allocation();
 
-            if (minY - maxY <= this.view.height &&
-                maxX - minX <= this.view.width)
+            if (minY - maxY <= height && maxX - minX <= width)
                 goodSize = true;
             else
                 zoomLevel--;
 
-            if (zoomLevel <= this.view.min_zoom_level) {
-                zoomLevel = this.view.min_zoom_level;
+            if (zoomLevel <= this.map.viewport.min_zoom_level) {
+                zoomLevel = this.map.viewport.min_zoom_level;
                 goodSize = true;
             }
         } while (!goodSize);
@@ -677,6 +774,7 @@ export class MapView extends GtkChamplain.Embed {
 
         this._placeLayer.add_marker(placeMarker);
         placeMarker.goToAndSelect(animation);
+        Application.application.selected_place = place;
     }
 
     showRoute(route) {
@@ -686,12 +784,10 @@ 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;
 
-        this._ensureInstructionLayerAboveRouteLayers();
-
         this._showDestinationTurnpoints();
         this.gotoBBox(route.bbox);
     }
@@ -729,15 +825,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 +841,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);
             }));
@@ -766,9 +853,7 @@ export class MapView extends GtkChamplain.Embed {
 
                 routeLayer.add_node(firstPoint);
             }
-        });
-
-        this._ensureInstructionLayerAboveRouteLayers();
+        })
 
         itinerary.legs.forEach((leg, index) => {
             let previousLeg = index === 0 ? null : itinerary.legs[index - 1];
@@ -839,7 +924,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..b4532f67 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';
@@ -39,7 +38,7 @@ export class MapWalker extends GObject.Object {
         super();
         this.place = place;
         this._mapView = mapView;
-        this._view = mapView.view;
+        this._viewport = mapView.map.viewport;
         this._boundingBox = this._createBoundingBox(this.place);
     }
 
@@ -54,15 +53,14 @@ export class MapWalker extends GObject.Object {
         }
     }
 
-    // Zoom to the maximal zoom-level that fits the place type
-    zoomToFit() {
+    _getZoomLevel() {
         let zoom;
 
         if (this.place.initialZoom) {
             zoom = this.place.initialZoom;
         } else {
             zoom = PlaceZoom.getZoomLevelForPlace(this.place) ??
-                   this._view.max_zoom_level;
+                   this._viewport.max_zoom_level;
 
             /* If the place has a bounding box, use the lower of the default
              * zoom level based on the place's type and the zoom level needed
@@ -78,56 +76,37 @@ export class MapWalker extends GObject.Object {
             }
         }
 
-        this._view.zoom_level = zoom;
-        this._view.center_on(this.place.location.latitude,
-                             this.place.location.longitude);
+        return zoom;
+    }
+
+    // Zoom to the maximal zoom-level that fits the place type
+    zoomToFit() {
+        let zoom = this._getZoomLevel();
+
+        this._mapView.map.go_to_full(this.place.location.latitude,
+                                     this.place.location.longitude,
+                                     zoom);
     }
 
     goTo(animate, linear) {
+        let zoom = this._getZoomLevel();
         Utils.debug('Going to ' + [this.place.name,
                     this.place.location.latitude,
                     this.place.location.longitude].join(' '));
         this._mapView.emit('going-to');
 
         if (!animate) {
-            this._view.center_on(this.place.location.latitude,
-                                 this.place.location.longitude);
+            this._mapView.map.center_on(this.place.location.latitude,
+                                        this.place.location.longitude);
+            this._mapView.map.viewport.zoom_level = zoom;
             this.emit('gone-to');
             return;
-        }
-
-        let fromLocation = new Location({ latitude: this._view.get_center_latitude(),
-                                          longitude: this._view.get_center_longitude() });
-        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,
-                             this.place.location.longitude);
         } else {
-            /* Lets first ensure that both current and destination location are visible
-             * before we start the animated journey towards destination itself. We do this
-             * to create the zoom-out-then-zoom-in effect that many map implementations
-             * do. This not only makes the go-to animation look a lot better visually but
-             * 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');
-                });
-            });
+            this._mapView.map.go_to_full(this.place.location.latitude,
+                                         this.place.location.longitude,
+                                         zoom);
+            Utils.once(this._mapView.map, 'animation-completed::go-to',
+                       () => this.emit('gone-to'));
         }
     }
 
@@ -176,21 +155,6 @@ export class MapWalker extends GObject.Object {
 
         return true;
     }
-
-    _updateGoToDuration(fromLocation) {
-        let toLocation = this.place.location;
-
-        let distance = fromLocation.get_distance_from(toLocation);
-        let duration = (distance / _MAX_DISTANCE) * _MAX_ANIMATION_DURATION;
-
-        // Clamp duration
-        duration = Math.max(_MIN_ANIMATION_DURATION,
-                            Math.min(duration, _MAX_ANIMATION_DURATION));
-
-        // We divide by two because Champlain treats both go_to and
-        // ensure_visible as 'goto' journeys with its own duration.
-        this._view.goto_animation_duration = duration / 2;
-    }
 }
 
 GObject.registerClass({
diff --git a/src/osmEditDialog.js b/src/osmEditDialog.js
index 2a7e05b8..4c0faf49 100644
--- a/src/osmEditDialog.js
+++ b/src/osmEditDialog.js
@@ -335,7 +335,8 @@ export class OSMEditDialog extends Gtk.Dialog {
         this._cancellable = new Gio.Cancellable();
         this._cancellable.connect(() => this.response(OSMEditDialog.Response.CANCELLED));
 
-        this.connect('delete-event', () => this._cancellable.cancel());
+        this.connect('response', () => this._cancellable.cancel());
+        this.connect('close-request', () => this._cancellable.cancel());
 
         this._isEditing = false;
         this._nextButton.connect('clicked', () => this._onNextClicked());
@@ -475,10 +476,15 @@ export class OSMEditDialog extends Gtk.Dialog {
         let recentTypes = OSMTypes.recentTypesStore.recentTypes;
 
         if (recentTypes.length > 0) {
-            let children = this._recentTypesListBox.get_children();
+            let children = [];
 
-            for (let i = 0; i < children.length; i++) {
-                this._recentTypesListBox.remove(children[i]);
+            for (let child of this._recentTypesListBox) {
+                if (child instanceof Gtk.ListBoxRow)
+                    children.push(child);
+            }
+
+            for (let child of children) {
+                this._recentTypesListBox.remove(child);
             }
 
             this._recentTypesLabel.visible = true;
@@ -563,7 +569,7 @@ export class OSMEditDialog extends Gtk.Dialog {
         let statusMessage =
             error ? error.message : OSMConnection.getStatusMessage(status);
         let messageDialog =
-            new Gtk.MessageDialog({ transient_for: this.get_toplevel(),
+            new Gtk.MessageDialog({ transient_for: this.get_root(),
                                     destroy_with_parent: true,
                                     message_type: Gtk.MessageType.ERROR,
                                     buttons: Gtk.ButtonsType.OK,
@@ -571,8 +577,8 @@ export class OSMEditDialog extends Gtk.Dialog {
                                     text: _("An error has occurred"),
                                     secondary_text: statusMessage });
 
-        messageDialog.run();
-        messageDialog.destroy();
+        messageDialog.connect('response', () => messageDialog.destroy());
+        messageDialog.show();
         this.response(OSMEditDialog.Response.ERROR);
     }
 
@@ -590,8 +596,7 @@ export class OSMEditDialog extends Gtk.Dialog {
     }
 
     _addOSMEditDeleteButton(fieldSpec) {
-        let deleteButton = Gtk.Button.new_from_icon_name('user-trash-symbolic',
-                                                         Gtk.IconSize.BUTTON);
+        let deleteButton = Gtk.Button.new_from_icon_name('user-trash-symbolic');
         let styleContext = deleteButton.get_style_context();
         let rows = fieldSpec.rows || 1;
 
@@ -632,11 +637,13 @@ export class OSMEditDialog extends Gtk.Dialog {
     }
 
     _showHintPopover(entry, hint) {
-        if (this._hintPopover.visible) {
+        if (this._hintPopover && this._hintPopover.visible) {
             this._hintPopover.popdown();
+            this._hintPopover = null;
         } else {
-            this._hintPopover.relative_to = entry;
-            this._hintLabel.label = hint;
+            let label = new Gtk.Label({ label: hint });
+            this._hintPopover = new Gtk.Popover({ child: label });
+            this._hintPopover.set_parent(entry);
             this._hintPopover.popup();
         }
     }
@@ -665,8 +672,13 @@ export class OSMEditDialog extends Gtk.Dialog {
             entry.placeholder_text = fieldSpec.placeHolder;
 
         entry.connect('changed', () => {
-            if (fieldSpec.rewriteFunc)
-                entry.text = fieldSpec.rewriteFunc(entry.text);
+            if (fieldSpec.rewriteFunc) {
+                let rewrittenText = fieldSpec.rewriteFunc(entry.text);
+
+                if (rewrittenText !== entry.text)
+                    entry.text = rewrittenText;
+            }
+
             this._osmObject.set_tag(fieldSpec.tag, entry.text);
 
             this._validateTextEntry(fieldSpec, entry);
@@ -710,12 +722,16 @@ export class OSMEditDialog extends Gtk.Dialog {
             this._nextButton.sensitive = true;
         });
 
+        // TODO: this doesn't work in GTK4, since SpinButton doesn't extend
+        // Entry, maybe put the hint button beside the spin button?
+        /*
         if (fieldSpec.hint) {
             spinbutton.secondary_icon_name = 'dialog-information-symbolic';
             spinbutton.connect('icon-press', (iconPos, event) => {
                 this._showHintPopover(spinbutton, fieldSpec.hint);
             });
         }
+        */
 
         this._editorGrid.attach(spinbutton, 1, this._currentRow, 1, 1);
         spinbutton.show();
@@ -775,12 +791,15 @@ export class OSMEditDialog extends Gtk.Dialog {
     /* update visible items in the "Add Field" popover */
     _updateAddFieldMenu() {
         /* clear old items */
-        let children = this._addFieldPopoverGrid.get_children();
         let hasAllFields = true;
+        let children = [];
+
+        for (let child of this._addFieldPopoverGrid) {
+            children.push(child);
+        }
 
-        for (let i = 0; i < children.length; i++) {
-            let button = children[i];
-            button.destroy();
+        for (let child of children) {
+            this._addFieldPopoverGrid.remove(child);
         }
 
         /* add selectable items */
@@ -810,6 +829,7 @@ export class OSMEditDialog extends Gtk.Dialog {
 
                 button.connect('clicked', () => {
                     this._addFieldButton.active = false;
+                    this._addFieldPopover.popdown();
                     this._addOSMField(fieldSpec);
                     /* add a "placeholder" empty OSM tag to keep the add field
                      * menu updated, these tags will be filtered out if nothing
@@ -898,6 +918,7 @@ GObject.registerClass({
                         'stack',
                         'editorGrid',
                         'commentTextView',
+                        'addFieldPopover',
                         'addFieldPopoverGrid',
                         'addFieldButton',
                         'typeSearchGrid',
@@ -906,7 +927,5 @@ GObject.registerClass({
                         'typeValueLabel',
                         'recentTypesLabel',
                         'recentTypesListBox',
-                        'hintPopover',
-                        'hintLabel',
                         'headerBar'],
 }, OSMEditDialog);
diff --git a/src/osmTypeListRow.js b/src/osmTypeListRow.js
index 84bb0c48..81940e0e 100644
--- a/src/osmTypeListRow.js
+++ b/src/osmTypeListRow.js
@@ -25,11 +25,12 @@ import Gtk from 'gi://Gtk';
 export class OSMTypeListRow extends Gtk.ListBoxRow {
 
     constructor(props) {
-        this._type = props.type;
+        let type = props.type;
         delete props.type;
 
         super(props);
 
+        this._type = type;
         this._name.label = this._type.title;
     }
 
diff --git a/src/osmTypePopover.js b/src/osmTypePopover.js
index 5a3d30e2..b4026f98 100644
--- a/src/osmTypePopover.js
+++ b/src/osmTypePopover.js
@@ -37,10 +37,23 @@ export class OSMTypePopover extends SearchPopover {
     }
 
     showMatches(matches) {
-        this._list.foreach((row) => this._list.remove(row));
+        let rows = [];
+
+        for (let row of this._list) {
+            rows.push(row);
+        }
+        for (let row of rows) {
+            this._list.remove(row);
+        }
 
         matches.forEach((type) => this._addRow(type));
-        this.show();
+
+        let {x, y, width, height} = this.get_parent().get_allocation();
+
+        // Magic number to make the alignment pixel perfect.
+        this.width_request = width + 20;
+
+        this.popup();
     }
 
     _addRow(type) {
diff --git a/src/osmTypeSearchEntry.js b/src/osmTypeSearchEntry.js
index 74ee6544..fc14ec21 100644
--- a/src/osmTypeSearchEntry.js
+++ b/src/osmTypeSearchEntry.js
@@ -33,13 +33,9 @@ export class OSMTypeSearchEntry extends Gtk.SearchEntry {
     constructor(props) {
         super(props);
 
-        this._popover = new OSMTypePopover({relative_to: this});
-
-        this.connect('size-allocate', (widget, allocation) => {
-            /* Magic number to make the alignment pixel perfect. */
-            let width_request = allocation.width + 20;
-            this._popover.width_request = width_request;
-        });
+        this._popover = new OSMTypePopover({ entry: this });
+        this._popover.set_parent(this);
+        this.set_key_capture_widget(this._popover);
 
         this.connect('search-changed', this._onSearchChanged.bind(this));
         this.connect('activate', this._onSearchChanged.bind(this));
diff --git a/src/placeBar.js b/src/placeBar.js
index 6db74564..e119c7da 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';
@@ -37,21 +36,29 @@ export class PlaceBar extends Gtk.Revealer {
         let mapView = params.mapView;
         delete params.mapView;
 
+        let mainWindow = params.mainWindow;
+        delete params.mainWindow;
+
         super(params);
 
         this._mapView = mapView;
+        this._mainWindow = mainWindow;
         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 +90,7 @@ export class PlaceBar extends Gtk.Revealer {
         }
     }
 
-    _onEventBoxClicked() {
+    _onBoxClicked() {
         if (this.place.isCurrentLocation) {
             if (this._currentLocationView) {
                 this._box.remove(this._currentLocationView);
@@ -93,10 +100,10 @@ export class PlaceBar extends Gtk.Revealer {
                                                             mapView: this._mapView,
                                                             inlineMode: true,
                                                             visible: true });
-                this._box.add(this._currentLocationView);
+                this._box.append(this._currentLocationView);
             }
         } else {
-            this._dialog = new PlaceDialog ({ transient_for: this.get_toplevel(),
+            this._dialog = new PlaceDialog ({ transient_for: this._mainWindow,
                                               modal: true,
                                               mapView: this._mapView,
                                               place: this.place });
@@ -113,21 +120,8 @@ export class PlaceBar extends Gtk.Revealer {
     }
 
     _onMapClickEvent(view, event) {
-        switch (event.type()) {
-            case Clutter.EventType.TOUCH_BEGIN:
-                this._tapped = true;
-                break;
-            case Clutter.EventType.TOUCH_UPDATE:
-                this._tapped = false;
-            case Clutter.EventType.TOUCH_END:
-            case Clutter.EventType.TOUCH_CANCEL:
-                if (this._tapped) {
-                    Application.application.selected_place = null;
-                }
-                break;
-        }
-
-        return Clutter.EVENT_PROPAGATE;
+       Application.application.selected_place = null;
+       this.reveal_child = false;
     }
 }
 
@@ -136,7 +130,6 @@ GObject.registerClass({
     InternalChildren: [ 'actionbar',
                         'altSendToButton',
                         'box',
-                        'eventbox',
                         'title' ],
     Properties: {
         'place': GObject.ParamSpec.object('place',
@@ -147,3 +140,4 @@ GObject.registerClass({
                                           GeocodeGlib.Place)
     },
 }, PlaceBar);
+
diff --git a/src/placeButtons.js b/src/placeButtons.js
index 05aac8bf..c5e1f031 100644
--- a/src/placeButtons.js
+++ b/src/placeButtons.js
@@ -71,11 +71,18 @@ export class PlaceButtons extends Gtk.Box {
 
     initSendToButton(button) {
         button.connect('clicked', () => {
-            let dialog = new SendToDialog({ transient_for: this.get_toplevel(),
+            let dialog = new SendToDialog({ transient_for: this.get_root(),
                                             modal: true,
                                             mapView: this._mapView,
                                             place: this._place });
-            dialog.connect('response', () => dialog.destroy());
+            dialog.connect('response', () => {
+                dialog.destroy();
+                this._popup();
+            });
+            /* on GTK 4 the popover gets overlayead over the dialog,
+             * so close the popover when not in adaptive mode
+             */
+            this._popdown();
             dialog.show();
         });
     }
@@ -135,13 +142,19 @@ export class PlaceButtons extends Gtk.Box {
         let osmEdit = Application.osmEdit;
         /* if the user is not already signed in, show the account dialog */
         if (!osmEdit.isSignedIn) {
-            let dialog = osmEdit.createAccountDialog(this.get_toplevel(), true);
+            let dialog = osmEdit.createAccountDialog(this.get_root(), true);
 
+            /* on GTK 4 the popover gets overlayead over the dialog,
+             * so close the popover when not in adaptive mode
+             */
+            this._popdown();
             dialog.show();
             dialog.connect('response', (dialog, response) => {
                 dialog.destroy();
                 if (response === OSMAccountDialog.Response.SIGNED_IN)
                     this._edit();
+                else
+                    this._popup();
             });
 
             return;
@@ -150,10 +163,25 @@ export class PlaceButtons extends Gtk.Box {
         this._edit();
     }
 
+    // popdown the parent popove(when not in adaptive mode)
+    _popdown() {
+        if (!Application.application.adaptive_mode)
+            this.get_ancestor(Gtk.Popover).popdown();
+    }
+
+    _popup() {
+        if (!Application.application.adaptive_mode)
+            this.get_ancestor(Gtk.Popover).popup();
+    }
+
     _edit() {
         let osmEdit = Application.osmEdit;
-        let dialog = osmEdit.createEditDialog(this.get_toplevel(), this._place);
+        let dialog = osmEdit.createEditDialog(this.get_root(), this._place);
 
+        /* on GTK 4 the popover gets overlayead over the dialog,
+         * so close the popover when not in adaptive mode
+         */
+        this._popdown();
         dialog.show();
         dialog.connect('response', (dialog, response) => {
             dialog.destroy();
@@ -169,6 +197,8 @@ export class PlaceButtons extends Gtk.Box {
             default:
                 break;
             }
+
+            this._popup();
         });
     }
 }
diff --git a/src/placeDialog.js b/src/placeDialog.js
index ce641f92..7adabad5 100644
--- a/src/placeDialog.js
+++ b/src/placeDialog.js
@@ -36,10 +36,10 @@ export class PlaceDialog extends Gtk.Dialog {
         params.use_header_bar = true;
         super(params);
 
-        if (this.transient_for.is_maximized) {
+        if (this.transient_for.maximized) {
             this.maximize();
         } else {
-            let [width, height] = this.transient_for.get_size();
+            let {x, y, width, height} = this.transient_for.get_allocation();
 
             // Don't let the dialog get too wide
             if (width > 400) {
@@ -54,7 +54,7 @@ export class PlaceDialog extends Gtk.Dialog {
                                           mapView,
                                           valign: Gtk.Align.START,
                                           visible: true });
-        this._scroll.add(this._placeView);
+        this._scroll.child = this._placeView;
 
         let formatter = new PlaceFormatter(place);
         this.title = formatter.title;
diff --git a/src/placeEntry.js b/src/placeEntry.js
index 74f9df97..bf89e302 100644
--- a/src/placeEntry.js
+++ b/src/placeEntry.js
@@ -68,9 +68,11 @@ export class PlaceEntry extends Gtk.SearchEntry {
             this._placeText = '';
         }
 
-        this.text = this._placeText;
+        if (this.text !== this._placeText)
+            this._setTextWithoutTriggerSearch(this._placeText);
 
         this._place = p;
+
         this.notify('place');
     }
 
@@ -88,10 +90,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 +110,14 @@ 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 = {});
+        this._mapView.map.viewport.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.map.viewport.connect('notify::zoom-level', () => this._cache =  {});
+    }
+
+    _setTextWithoutTriggerSearch(text) {
+        this._setText = text;
+        this.text = text;
     }
 
     _onSearchChanged() {
@@ -125,6 +128,10 @@ export class PlaceEntry extends Gtk.SearchEntry {
         if (this._cancellable)
             return;
 
+        // don't trigger a search when setting explicit text (such as reordering points)
+        if (this.text === this._setText)
+            return;
+
         /* start search if more than the threshold number of characters have
          * been entered, or if the first character is in the ideographic CJK
          * block, as for these, shorter strings could be meaningful
@@ -147,7 +154,8 @@ export class PlaceEntry extends Gtk.SearchEntry {
                 this._doSearch();
             }
         } else {
-            this._popover.hide();
+            this._popover.popdown();
+            this.grab_focus();
             if (this.text.length === 0)
                 this.place = null;
             this._previousSearch = null;
@@ -164,24 +172,24 @@ export class PlaceEntry extends Gtk.SearchEntry {
 
     _createPopover(numVisible, maxChars) {
         let popover = new PlacePopover({ num_visible:   numVisible,
-                                         relative_to:   this,
+                                         entry:         this,
                                          maxChars:      maxChars });
 
-        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.set_parent(this);
+        this.set_key_capture_widget(popover);
 
         popover.connect('selected', (widget, place) => {
             this.place = place;
-            popover.hide();
+            popover.popdown();
         });
 
         return popover;
     }
 
+    _onKeyPressed(controller, keyval, keycode, state) {
+        return true;
+    }
+
     _completionVisibleFunc(model, iter) {
         let place = model.get_value(iter, PlaceStore.Columns.PLACE);
         let type = model.get_value(iter, PlaceStore.Columns.TYPE);
@@ -280,8 +288,8 @@ export class PlaceEntry extends Gtk.SearchEntry {
         this._previousSearch = this.text;
 
         GeocodeFactory.getGeocoder().search(this.text,
-                                            this._mapView.view.latitude,
-                                            this._mapView.view.longitude,
+                                            this._mapView.map.viewport.latitude,
+                                            this._mapView.map.viewport.longitude,
                                             this._cancellable,
                                             (places, error) => {
             this._cancellable = null;
diff --git a/src/placeMarker.js b/src/placeMarker.js
index 25a0fe9e..b80b02f0 100644
--- a/src/placeMarker.js
+++ b/src/placeMarker.js
@@ -20,6 +20,7 @@
  */
 
 import GObject from 'gi://GObject';
+import Gtk from 'gi://Gtk';
 
 import {MapMarker} from './mapMarker.js';
 
@@ -28,7 +29,8 @@ export class PlaceMarker extends MapMarker {
     constructor(params) {
         super(params);
 
-        this.add_actor(this._actorFromIconName('mark-location', 32));
+        this._image.icon_name = 'mark-location';
+        this._image.icon_size = Gtk.IconSize.LARGE;
     }
 
     get anchor() {
diff --git a/src/placePopover.js b/src/placePopover.js
index c2af1c33..c1abce6a 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)
@@ -63,16 +61,26 @@ export class PlacePopover extends SearchPopover {
         this.connect('unmap', (popover) => popover.hide());
     }
 
+    _showPopover() {
+        let {x, y, width, height} = this._entry.get_allocation();
+
+        // Magic number to make the alignment pixel perfect.
+        this.width_request = width + 20;
+        this.popup();
+    }
+
     showSpinner() {
         this._spinner.start();
         this._stack.visible_child = this._spinner;
 
         if (!this.visible)
-            this.show();
+            this._showPopover();
+
+        this._numResults = 0;
     }
 
     showResult() {
-        if (this._spinner.active)
+        if (this._spinner.spinning)
             this._spinner.stop();
 
         this._stack.visible_child = this._scrolledWindow;
@@ -82,7 +90,7 @@ export class PlacePopover extends SearchPopover {
             this._list.select_row(row);
 
         if (!this.visible)
-            this.show();
+            this._showPopover();
     }
 
     showNoResult() {
@@ -90,6 +98,7 @@ export class PlacePopover extends SearchPopover {
             this._spinner.stop();
 
         this._stack.visible_child = this._noResultsLabel;
+        this._numResults = 0;
     }
 
     showError() {
@@ -97,6 +106,7 @@ export class PlacePopover extends SearchPopover {
             this._spinner.stop();
 
         this._stack.visible_child = this._errorLabel;
+        this._numResults = 0;
     }
 
     updateResult(places, searchString) {
@@ -114,6 +124,8 @@ export class PlacePopover extends SearchPopover {
             i++;
         });
 
+        this._numResults = i;
+
         // remove remaining rows
         let row = this._list.get_row_at_index(i);
 
diff --git a/src/placeView.js b/src/placeView.js
index 6d0e4e58..ce76dcee 100644
--- a/src/placeView.js
+++ b/src/placeView.js
@@ -86,12 +86,12 @@ export class PlaceView extends Gtk.Box {
         this._mainBox = ui.bubbleMainBox;
         this._addressLabel = ui.addressLabel;
 
-        this.add(this._mainStack);
+        this.append(this._mainStack);
 
         let placeButtons = new PlaceButtons({ place: this._place,
                                               mapView: mapView });
         placeButtons.connect('place-edited', this._onPlaceEdited.bind(this));
-        ui.placeButtons.add(placeButtons);
+        ui.placeButtons.append(placeButtons);
 
         if (this.place.isCurrentLocation) {
             /* Current Location bubbles have a slightly different layout, to
@@ -175,11 +175,11 @@ export class PlaceView extends Gtk.Box {
     }
 
     get loading() {
-        return this._spinner.active;
+        return this._spinner.spinning;
     }
     set loading(val) {
         this._mainStack.set_visible_child(val ? this._spinner : this._mainBox);
-        this._spinner.active = val;
+        this._spinner.spinning = val;
     }
 
     updatePlaceDetails() {
@@ -484,7 +484,7 @@ export class PlaceView extends Gtk.Box {
         content.forEach(({ type, label, icon, linkUrl, info, grid }) => {
             let separator = new Gtk.Separator({ visible: true });
             separator.get_style_context().add_class('no-margin-separator');
-            this._placeDetails.add(separator);
+            this._placeDetails.append(separator);
 
             let box = new Gtk.Box({ orientation: Gtk.Orientation.HORIZONTAL,
                                     visible: true,
@@ -501,7 +501,6 @@ export class PlaceView extends Gtk.Box {
             if (icon) {
                 let widget = new Gtk.Image({ icon_name: icon,
                                              visible: true,
-                                             xalign: 1,
                                              valign: Gtk.Align.START,
                                              halign: Gtk.Align.END });
 
@@ -509,14 +508,14 @@ export class PlaceView extends Gtk.Box {
                     widget.tooltip_markup = label;
                 }
 
-                box.add(widget);
+                box.append(widget);
             } else if (label) {
                 let widget = new Gtk.Label({ label: label.italics(),
                                              visible: true,
                                              use_markup: true,
                                              yalign: 0,
                                              halign: Gtk.Align.END });
-                box.add(widget);
+                box.append(widget);
             }
 
             if (linkUrl) {
@@ -574,8 +573,8 @@ export class PlaceView extends Gtk.Box {
                 }
             }
 
-            box.add(widget);
-            this._placeDetails.add(box);
+            box.append(widget);
+            this._placeDetails.append(box);
         });
     }
 
@@ -628,7 +627,15 @@ export class PlaceView extends Gtk.Box {
 
     // clear the view widgets to be able to re-populate an updated place
     _clearView() {
-        this._placeDetails.get_children().forEach((child) => this._placeDetails.remove(child));
+        let details = [];
+
+        for (let detail of this._placeDetails) {
+            details.push(detail);
+        }
+
+        for (let detail of details) {
+            this._placeDetails.remove(detail);
+        }
     }
 
     // called when the place's location changes (e.g. for the current location)
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..c977b6c9 100644
--- a/src/printLayout.js
+++ b/src/printLayout.js
@@ -18,27 +18,31 @@
  */
 
 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 Graphene from 'gi://Graphene';
 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 {BoundingBox} from './boundingBox.js';
 import * as Color from './color.js';
 import {MapView} from './mapView.js';
 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,
+                                     blue: 255,
+                                     green: 0,
+                                     alpha: 1.0 });
 const _STROKE_WIDTH = 5.0;
 
+const _ICON_COLOR = new Gdk.RGBA({ red: 0, green: 0, blue: 0, alpha: 1.0 });
+const _ICON_SIZE = 24;
+
 /* All following constants are ratios of surface size to page size */
 const _Header = {
     SCALE_X: 0.9,
@@ -48,8 +52,7 @@ const _Header = {
 const _MapView = {
     SCALE_X: 1.0,
     SCALE_Y: 0.4,
-    SCALE_MARGIN: 0.04,
-    ZOOM_LEVEL: 18
+    SCALE_MARGIN: 0.04
 };
 
 export class PrintLayout extends GObject.Object {
@@ -64,11 +67,15 @@ export class PrintLayout extends GObject.Object {
         let totalSurfaces = params.totalSurfaces;
         delete params.totalSurfaces;
 
+        let mainWindow = params.mainWindow;
+        delete params.mainWindow;
+
         super(params);
 
         this._pageWidth = pageWidth;
         this._pageHeight = pageHeight;
         this._totalSurfaces = totalSurfaces;
+        this._mainWindow = mainWindow;
         this.numPages = 0;
         this.surfaceObjects = [];
         this._surfacesRendered = 0;
@@ -88,8 +95,7 @@ export class PrintLayout extends GObject.Object {
 
         let mapViewWidth = _MapView.SCALE_X * this._pageWidth;
         let mapViewHeight = _MapView.SCALE_Y * this._pageHeight;
-        let mapViewMargin = _MapView.SCALE_MARGIN * this._pageHeight;
-        let mapViewZoomLevel = _MapView.ZOOM_LEVEL;
+        let mapViewMargin = _MapView.SCALE_MARGIN * this._pageHeight
 
         this._createNewPage();
         let dy = 0;
@@ -103,13 +109,15 @@ export class PrintLayout extends GObject.Object {
         this._drawHeader(headerWidth, headerHeight);
         this._cursorY += dy;
 
+        // TODO: for now for now skip drawing the mini map
+        /*
         dy = mapViewHeight + mapViewMargin;
         this._adjustPage(dy);
         let turnPointsLength = this._route.turnPoints.length;
         let allTurnPoints = this._createTurnPointArray(0, turnPointsLength);
-        this._drawMapView(mapViewWidth, mapViewHeight,
-                          mapViewZoomLevel, allTurnPoints);
+        this._drawMapView(mapViewWidth, mapViewHeight, allTurnPoints);
         this._cursorY += dy;
+        */
     }
 
     _initSignals() {
@@ -120,20 +128,38 @@ export class PrintLayout extends GObject.Object {
         return new TurnPointMarker({ turnPoint: turnPoint, queryPoint: {} });
     }
 
-    _drawMapView(width, height, zoomLevel, turnPoints) {
+    _getZoomLevelFittingBBox(bbox, mapSource, width, height) {
+        let goodSize = false;
+        let zoomLevel = mapSource.max_zoom_level;
+
+        do {
+
+            let minX = mapSource.get_x(zoomLevel, bbox.left);
+            let minY = mapSource.get_y(zoomLevel, bbox.bottom);
+            let maxX = mapSource.get_x(zoomLevel, bbox.right);
+            let maxY = mapSource.get_y(zoomLevel, bbox.top);
+
+            if (minY - maxY <= height && maxX - minX <= width)
+                goodSize = true;
+            else
+                zoomLevel--;
+
+            if (zoomLevel <= mapSource.min_zoom_level) {
+                zoomLevel = mapSource.min_zoom_level;
+                goodSize = true;
+            }
+        } while (!goodSize);
+
+        return zoomLevel;
+    }
+
+    _drawMapView(width, height, turnPoints) {
         let pageNum = this.numPages - 1;
         let x = this._cursorX;
         let y = this._cursorY;
         let mapSource = MapSource.createPrintSource();
         let locations = [];
-        let markerLayer = new Champlain.MarkerLayer();
-        let view = new Champlain.View({ width: width,
-                                        height: height,
-                                        zoom_level: zoomLevel });
-        view.set_map_source(mapSource);
-        view.add_layer(markerLayer);
-
-        this._addRouteLayer(view);
+        let markerLayer = new Shumate.MarkerLayer();
 
         turnPoints.forEach((turnPoint) => {
             locations.push(turnPoint.coordinate);
@@ -142,30 +168,60 @@ export class PrintLayout extends GObject.Object {
             }
         });
 
-        view.ensure_visible(this._createBBox(locations), false);
-        if (view.state !== Champlain.State.DONE) {
-            let notifyId = view.connect('notify::state', () => {
-                if (view.state === Champlain.State.DONE) {
-                    view.disconnect(notifyId);
-                    let surface = view.to_surface(true);
-                    if (surface)
-                        this._addSurface(surface, x, y, pageNum);
-                }
-            });
-        } else {
-            let surface = view.to_surface(true);
-            if (surface)
-                this._addSurface(surface, x, y, pageNum);
-        }
+        let bbox = this._createBBox(locations);
+        let zoomLevel =
+            this._getZoomLevelFittingBBox(bbox, mapSource, width, height);
+
+        let map = new Shumate.Map();
+        let mapLayer = new Shumate.MapLayer({ map_source: mapSource,
+                                              viewport:   map.viewport });
+
+        map.viewport.zoom_level = zoomLevel;
+        map.add_layer(mapLayer);
+
+        let routeLayer = this._addRouteLayer(map, mapLayer);
+
+        map.insert_layer_above(markerLayer, routeLayer);
+        map.viewport.set_reference_map_source(mapSource);
+        map.set_size_request(width, height);
+
+        // TODO: how do we know when it's loaded?
+        let surface = this._mapToSurface(map, width, height);
+        if (surface)
+            this._addSurface(surface, x, y, pageNum);
+    }
+
+    // TODO: this does not quite work...
+    _mapToSurface(map, width, height) {
+        let surface = new Cairo.ImageSurface(Cairo.Format.ARGB32, width, height);
+        let cr = new Cairo.Context(surface);
+        let paintable = new Gtk.WidgetPaintable({ widget: map });
+        let rect = new Graphene.Rect();
+
+        rect.init(0, 0, width, height);
+
+        let snapshot = Gtk.Snapshot.new();
+
+        paintable.snapshot(snapshot, width, height);
+
+        let node = snapshot.to_node();
+        let renderer = this._mainWindow.get_native().get_renderer();
+        let texture = renderer.render_texture(node, rect);
+        let pixbuf = Gdk.pixbuf_get_from_texture(texture);
+
+        Gdk.cairo_set_source_pixbuf(cr, pixbuf, 0, 0);
+        cr.paint();
+
+        return surface;
     }
 
     _createBBox(locations) {
         let bbox = this._route.createBBox(locations);
 
-        return new Champlain.BoundingBox({ top:    bbox.top,
-                                           left:   bbox.left,
-                                           bottom: bbox.bottom,
-                                           right:  bbox.right });
+        return new BoundingBox({ top:    bbox.top,
+                                 left:   bbox.left,
+                                 bottom: bbox.bottom,
+                                 right:  bbox.right });
     }
 
     _createTurnPointArray(startIndex, endIndex) {
@@ -176,24 +232,37 @@ export class PrintLayout extends GObject.Object {
         return turnPointArray;
     }
 
-    _addRouteLayer(view) {
-        let routeLayer = new Champlain.PathLayer({ stroke_width: _STROKE_WIDTH,
-                                                   stroke_color: _STROKE_COLOR });
-        view.add_layer(routeLayer);
+    _addRouteLayer(map, mapLayer) {
+        let routeLayer = new Shumate.PathLayer({ stroke_width: _STROKE_WIDTH,
+                                                 stroke_color: _STROKE_COLOR });
+        map.insert_layer_above(routeLayer, mapLayer);
         this._route.path.forEach((node) => routeLayer.add_node(node));
+
+        return routeLayer;
     }
 
-    _drawIcon(cr, iconName, width, height) {
-        let theme = Gtk.IconTheme.get_default();
-        let pixbuf = theme.load_icon(iconName, height, 0);
-        let iconWidth = pixbuf.width;
-        let iconHeight = pixbuf.height;
+    _drawIcon(cr, iconName, width, height, size) {
+        let display = Gdk.Display.get_default();
+        let theme = Gtk.IconTheme.get_for_display(display);
+        let iconPaintable = theme.lookup_icon(iconName, null, size, 1,
+                                          Gtk.TextDirection.NONE, 0);
+        let snapshot = Gtk.Snapshot.new();
+        let rect = new Graphene.Rect();
+
+        iconPaintable.snapshot_symbolic(snapshot, size, size, [_ICON_COLOR]);
+        rect.init(0, 0, size, size);
+
+        let node = snapshot.to_node();
+        let renderer = this._mainWindow.get_native().get_renderer();
+
+        let paintable = renderer.render_texture(node, rect);
+        let pixbuf = Gdk.pixbuf_get_from_texture(paintable)
 
         Gdk.cairo_set_source_pixbuf(cr, pixbuf,
                                     this._rtl ?
-                                    width - height + (height - iconWidth) / 2 :
-                                    (height - iconWidth) / 2,
-                                    (height - iconWidth) / 2);
+                                    width - height + (height - size) / 2 :
+                                    (height - size) / 2,
+                                    (height - size) / 2);
         cr.paint();
     }
 
@@ -259,7 +328,7 @@ export class PrintLayout extends GObject.Object {
         let iconName = turnPoint.iconName;
 
         if (iconName) {
-            this._drawIcon(cr, iconName, width, height);
+            this._drawIcon(cr, iconName, width, height, _ICON_SIZE);
         }
 
         // draw the instruction text
diff --git a/src/printOperation.js b/src/printOperation.js
index 47dbb2ba..0979bbaf 100644
--- a/src/printOperation.js
+++ b/src/printOperation.js
@@ -69,17 +69,27 @@ export class PrintOperation {
             this._layout =
                 new TransitPrintLayout({ itinerary: selectedTransitItinerary,
                                          pageWidth: width,
-                                         pageHeight: height });
+                                         pageHeight: height,
+                                         mainWindow: this._mainWindow });
         } else {
+            // TODO: for now just use short layout, as we don't have minimaps
+            this._layout = new ShortPrintLayout({ route: route,
+                                                  pageWidth: width,
+                                                  pageHeight: height,
+                                                  mainWindow: this._mainWindow });
+            /*
             if (route.distance > _SHORT_LAYOUT_MAX_DISTANCE) {
                 this._layout = new LongPrintLayout({ route: route,
                                                      pageWidth: width,
-                                                     pageHeight: height });
+                                                     pageHeight: height,
+                                                     mainWindow: this._mainWindow });
             } else {
                 this._layout = new ShortPrintLayout({ route: route,
                                                       pageWidth: width,
-                                                      pageHeight: height });
+                                                      pageHeight: height,
+                                                      mainWindow: this._mainWindow });
             }
+            */
         }
 
         GLib.timeout_add(null, _MIN_TIME_TO_ABORT, () => {
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..423d8ff7 100644
--- a/src/searchPopover.js
+++ b/src/searchPopover.js
@@ -29,53 +29,54 @@ 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.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());
+
+        this._numResults = 0;
     }
 
     _propagateKeys(controller, keyval, keycode, state) {
         if (keyval === Gdk.KEY_Escape) {
             this.hide();
             this._list.unselect_all();
-            return true;
-        }
-
-        if (keyval === Gdk.KEY_Return ||
-            keyval === Gdk.KEY_KP_ENTER ||
-            keyval === Gdk.KEY_ISO_Enter) {
+        } else if (keyval === Gdk.KEY_Return ||
+                   keyval === Gdk.KEY_KP_ENTER ||
+                   keyval === Gdk.KEY_ISO_Enter) {
 
             // If we get an 'enter' keypress and we have a selected
             // row, we do not want to propagate the event.
             let row = this._list.get_selected_row();
+
             if (this.visible && row) {
                 row.activate();
-                return true;
             } else {
-                return false;
+                controller.forward(this._entry);
             }
-        }
-
-        if (keyval === Gdk.KEY_KP_Up ||
-            keyval === Gdk.KEY_Up ||
-            keyval === Gdk.KEY_KP_Down ||
-            keyval === Gdk.KEY_Down) {
+        } else if (keyval === Gdk.KEY_KP_Up ||
+                   keyval === Gdk.KEY_Up ||
+                   keyval === Gdk.KEY_KP_Down ||
+                   keyval === Gdk.KEY_Down) {
 
-            let length = this._list.get_children().length;
+            let length = this._numResults;
             if (length === 0) {
-                return false;
+                controller.forward(this._entry);
             }
 
             let direction = (keyval === Gdk.KEY_KP_Up || keyval === Gdk.KEY_Up) ? -1 : 1;
@@ -93,10 +94,17 @@ export class SearchPopover extends Gtk.Popover {
             } else {
                 this._list.unselect_all();
             }
-            return true;
+        } else {
+            if (keyval === Gdk.KEY_space) {
+                /* forwarding space seems to not work for some reason,
+                 * work around by manually injecting a space into the entry string
+                 */
+                this._entry.set_text(this._entry.text + ' ');
+                this._entry.set_position(this._entry.text.length);
+            } else {
+                controller.forward(this._entry);
+            }
         }
-
-        return false;
     }
 
     /* Selects given row and ensures that it is visible. */
diff --git a/src/sendToDialog.js b/src/sendToDialog.js
index 435b7b37..caa4ae96 100644
--- a/src/sendToDialog.js
+++ b/src/sendToDialog.js
@@ -124,7 +124,7 @@ export class SendToDialog extends Gtk.Dialog {
         });
 
         /* Hide the list box if it is empty */
-        if (this._list.get_children().length == 0) {
+        if (!this._list.get_first_child()) {
             this._scrolledWindow.hide();
         }
     }
@@ -153,7 +153,7 @@ export class SendToDialog extends Gtk.Dialog {
     }
 
     _getOSMURI() {
-        let view = this._mapView.view;
+        let viewport = this._mapView.map.viewport;
         let place = this._place;
 
         let base = 'https://openstreetmap.org';
@@ -165,16 +165,15 @@ export class SendToDialog extends Gtk.Dialog {
             return '%s?mlat=%f&mlon=%f&zoom=%d'.format(base,
                                                        this._location.latitude,
                                                        this._location.longitude,
-                                                       view.zoom_level);
+                                                       viewport.zoom_level);
         }
     }
 
     _copySummary() {
         let summary = '%s\n%s'.format(this._getSummary(), this._getOSMURI());
+        let clipboard = this.get_clipboard();
 
-        let display = Gdk.Display.get_default();
-        let clipboard = Gtk.Clipboard.get_default(display);
-        clipboard.set_text(summary, -1);
+        clipboard.set(summary);
         this.response(SendToDialog.Response.SUCCESS);
     }
 
@@ -188,7 +187,7 @@ export class SendToDialog extends Gtk.Dialog {
           Gio.app_info_launch_default_for_uri(uri, this._getAppLaunchContext());
         } catch(e) {
           Utils.showDialog(_("Failed to open URI"), Gtk.MessageType.ERROR,
-                           this.get_toplevel());
+                           this);
           Utils.debug('failed to open URI: %s'.format(e.message));
         }
 
@@ -196,20 +195,17 @@ export class SendToDialog extends Gtk.Dialog {
     }
 
     _getAppLaunchContext() {
-        let timestamp = Gtk.get_current_event_time();
-        let display = Gdk.Display.get_default();
         let ctx = Gdk.Display.get_default().get_app_launch_context();
-        let screen = display.get_default_screen();
 
-        ctx.set_timestamp(timestamp);
-        ctx.set_screen(screen);
+        // GdkAppLaunchContext uses second-precision timestamps
+        ctx.set_timestamp(GLib.get_real_time() / 1000000);
 
         return ctx;
     }
 
     _activateRow(row) {
         if (row === this._weatherRow || row === this._clocksRow) {
-            let timestamp = Gtk.get_current_event_time();
+            let timestamp = GLib.get_real_time() / 1000000;
 
             let action;
             let appId;
diff --git a/src/shapeLayer.js b/src/shapeLayer.js
index 18ac5170..3967fca1 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,9 @@ 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,
+            viewport:       this._mapView.map.viewport
         });
         this._mapSource = null;
     }
@@ -69,10 +71,10 @@ export class ShapeLayer extends GObject.Object {
 
     set visible(v) {
         if (v && !this._visible) {
-            this._mapView.view.add_overlay_source(this._mapSource, 255);
+            this._overlayLayer.visible = true;
             this._markerLayer.show_all_markers();
         } else if (!v && this._visible) {
-            this._mapView.view.remove_overlay_source(this._mapSource);
+            this._overlayLayer.visible = false;
             this._markerLayer.hide_all_markers();
         }
         this._visible = v;
@@ -95,8 +97,8 @@ export class ShapeLayer extends GObject.Object {
                 if (!status)
                     throw new Error(_("failed to load file"));
                 this._parseContent();
-                this._mapView.view.add_layer(this._markerLayer);
-                this._mapView.view.add_overlay_source(this._mapSource, 255);
+                this._mapView.map.add_layer(this._markerLayer);
+                this._mapView.map.add_layer(this._overlayLayer);
             } catch (e) {
                 Utils.debug(e);
                 error = true;
@@ -110,8 +112,8 @@ export class ShapeLayer extends GObject.Object {
     }
 
     unload() {
-        this._mapView.view.remove_layer(this._markerLayer);
-        this._mapView.view.remove_overlay_source(this._mapSource);
+        this._mapView.map.remove_layer(this._markerLayer);
+        this._mapView.map.remove_layer(this._overlayLayer);
     }
 }
 
diff --git a/src/shortPrintLayout.js b/src/shortPrintLayout.js
index 2875c51c..5d09be12 100644
--- a/src/shortPrintLayout.js
+++ b/src/shortPrintLayout.js
@@ -34,7 +34,9 @@ export class ShortPrintLayout extends PrintLayout {
         delete params.route;
 
         /* (Header +  map) + instructions */
-        let totalSurfaces = 2 + route.turnPoints.length;
+        //let totalSurfaces = 2 + route.turnPoints.length;
+        // for now don't count the map surface, as we don't have that yet
+        let totalSurfaces = 1 + route.turnPoints.length;
         params.totalSurfaces = totalSurfaces;
 
         super(params);
diff --git a/src/sidebar.js b/src/sidebar.js
index 372ca737..5a44c2a8 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', () => {
@@ -216,12 +220,12 @@ export class Sidebar extends Gtk.Revealer {
     }
 
     _onRouteEntrySelectedPlace(entry) {
-        let index = this._getIndexForRouteEntry(entry);
+        let [index, numEntries] = this._getIndexForRouteEntryAndNumEntries(entry);
 
         /* if a new place is selected and it's not the last entry, focus next
          * entry
          */
-        if (entry.place && index < this._entryList.get_children().length - 1) {
+        if (entry.place && index < numEntries - 1) {
             let nextPlaceEntry =
                 this._entryList.get_row_at_index(index + 1).get_child().entry;
 
@@ -230,15 +234,27 @@ export class Sidebar extends Gtk.Revealer {
         }
     }
 
-    _getIndexForRouteEntry(entry) {
-        for (let i = 0; i < this._entryList.get_children().length; i++) {
-            let routeEntry = this._entryList.get_row_at_index(i).get_child();
+    _getIndexForRouteEntryAndNumEntries(entry) {
+        let index = 0;
+        let foundIndex = -1;
+
+        for (let item of this._entryList) {
+            let routeEntry = item.get_child();
 
             if (routeEntry.entry === entry)
-                return i;
+                foundIndex = index;
+
+            index++;
         }
 
-        return -1;
+        return [foundIndex, index];
+    }
+
+    // this is needed to be called on shutdown to avoid a GTK warning
+    unparentSearchPopovers() {
+        for (let item of this._entryList) {
+            item.get_child().entry.popover.unparent();
+        }
     }
 
     _initInstructionList() {
@@ -266,8 +282,13 @@ export class Sidebar extends Gtk.Revealer {
 
         transitPlan.connect('no-more-results', () => {
             // set the "load more" row to indicate no more results
-            let numRows = this._transitOverviewListBox.get_children().length;
-            let loadMoreRow = this._transitOverviewListBox.get_row_at_index(numRows - 2);
+            let loadMoreRow;
+
+            for (let row of this._transitOverviewListBox) {
+                if (row instanceof TransitMoreRow)
+                    loadMoreRow = row;
+            }
+
             loadMoreRow.showNoMore();
         });
 
@@ -361,10 +382,23 @@ export class Sidebar extends Gtk.Revealer {
         this._errorLabel.label = msg;
     }
 
+    _clearListBox(listBox) {
+        let rows = [];
+
+        for (let row of listBox) {
+            if (row instanceof Gtk.ListBoxRow)
+                rows.push(row);
+        }
+
+        for (let row of rows) {
+            listBox.remove(row);
+        }
+    }
+
     _clearTransitOverview() {
         let listBox = this._transitOverviewListBox;
-        listBox.forall(listBox.remove.bind(listBox));
 
+        this._clearListBox(listBox);
         this._instructionStack.visible_child = this._transitWindow;
         this._timeInfo.label = '';
         this._distanceInfo.label = '';
@@ -372,7 +406,8 @@ export class Sidebar extends Gtk.Revealer {
 
     _clearTransitItinerary() {
         let listBox = this._transitItineraryListBox;
-        listBox.forall(listBox.remove.bind(listBox));
+
+        this._clearListBox(listBox);
     }
 
     _updateTransitAttribution() {
@@ -469,8 +504,8 @@ export class Sidebar extends Gtk.Revealer {
 
     _clearInstructions() {
         let listBox = this._instructionList;
-        listBox.forall(listBox.remove.bind(listBox));
 
+        this._clearListBox(listBox);
         this._instructionStack.visible_child = this._instructionWindow;
         this._timeInfo.label = '';
         this._distanceInfo.label = '';
@@ -515,12 +550,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 +581,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 +590,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 +601,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/transitArrivalMarker.js b/src/transitArrivalMarker.js
index 506b3905..8a0c1560 100644
--- a/src/transitArrivalMarker.js
+++ b/src/transitArrivalMarker.js
@@ -49,15 +49,8 @@ export class TransitArrivalMarker extends MapMarker {
                                    blue: bgBlue,
                                    alpha: 1.0
                                  });
-        let actor =
-            this._actorFromIconName('maps-point-end-symbolic', 0, color);
-
-        this.add_actor(actor);
-    }
-
-    get anchor() {
-        return { x: Math.floor(this.width / 2) - 1,
-                 y: Math.floor(this.height / 2) - 1 };
+        this._image.paintable =
+            this._paintableFromIconName('maps-point-end-symbolic', 16, color);
     }
 }
 
diff --git a/src/transitArrivalRow.js b/src/transitArrivalRow.js
index 023134ed..05e00e13 100644
--- a/src/transitArrivalRow.js
+++ b/src/transitArrivalRow.js
@@ -41,20 +41,14 @@ export class TransitArrivalRow extends Gtk.ListBoxRow {
         this._arrivalLabel.label = Transit.getArrivalLabel(lastLeg);
         this._timeLabel.label = lastLeg.prettyPrintArrivalTime();
 
-        this._eventBox.connect('event', (widget, event) => {
-            this._onEvent(event, lastLeg.toCoordinate);
-            return true;
-        });
+        this._buttonPressGesture = new Gtk.GestureSingle();
+        this.add_controller(this._buttonPressGesture);
+        this._buttonPressGesture.connect('begin',
+                                         () => this._onPress(lastLeg.toCoordinate));
     }
 
-    _onEvent(event, coord) {
-        let [isButton, button] = event.get_button();
-        let type = event.get_event_type();
-
-        if (isButton && button === 1 && type === Gdk.EventType.BUTTON_PRESS) {
-            this._mapView.view.zoom_level = 16;
-            this._mapView.view.center_on(coord[0], coord[1]);
-        }
+    _onPress(coord) {
+        this._mapView.map.go_to_full(coord[0], coord[1], 16);
     }
 }
 
@@ -62,6 +56,5 @@ GObject.registerClass({
     Template: 'resource:///org/gnome/Maps/ui/transit-arrival-row.ui',
     InternalChildren: ['arrivalLabel',
                        'timeLabel',
-                       'eventBox',
                        'separator']
 }, TransitArrivalRow);
diff --git a/src/transitBoardMarker.js b/src/transitBoardMarker.js
index 462727c4..2bc73182 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';
@@ -33,7 +32,7 @@ import * as TransitPlan from './transitPlan.js';
 import * as Utils from './utils.js';
 
 const ICON_SIZE = 12;
-const ACTOR_SIZE = 20;
+const MARKER_SIZE = 20;
 
 /* threashhold for route color luminance when we consider it more or less
  * as white, and draw an outline around the label
@@ -52,10 +51,11 @@ export class TransitBoardMarker extends MapMarker {
         params.place = new Place({ location: location });
         super(params);
 
-        this.add_actor(this._createActor(leg));
+        this._image.pixel_size = MARKER_SIZE;
+        this._image.paintable = this._createPaintable(leg);
     }
 
-    /* Creates a Clutter actor for the given transit leg, showing the
+    /* Creates a Gdk.Paintable for the given transit leg, showing the
      * corresponding transit type icon and rendered inside a circle using the
      * foreground color of the icon taken from the transit legs text color
      * attribute and background color taken from the transit legs color
@@ -64,7 +64,7 @@ export class TransitBoardMarker extends MapMarker {
      * background color above a threshold to improve readability against the
      * map background.
      */
-    _createActor(leg) {
+    _createPaintable(leg) {
         try {
             let bgColor = leg.color ?? TransitPlan.DEFAULT_ROUTE_COLOR;
             let fgColor =
@@ -84,45 +84,37 @@ export class TransitBoardMarker extends MapMarker {
                                         blue: fgBlue,
                                         alpha: 1.0
                                       });
-            let theme = Gtk.IconTheme.get_default();
-            let info = theme.lookup_icon(leg.iconName, ICON_SIZE,
-                                         Gtk.IconLookupFlags.FORCE_SIZE);
-            let pixbuf = info.load_symbolic(fgRGBA, null, null, null)[0];
-            let canvas = new Clutter.Canvas({ width: ACTOR_SIZE,
-                                              height: ACTOR_SIZE });
-
-
-            canvas.connect('draw', (canvas, cr) => {
-                cr.setOperator(Cairo.Operator.CLEAR);
-                cr.paint();
-                cr.setOperator(Cairo.Operator.OVER);
-
-                cr.setSourceRGB(bgRed, bgGreen, bgBlue);
-                cr.arc(ACTOR_SIZE / 2, ACTOR_SIZE / 2, ACTOR_SIZE / 2,
-                       0, Math.PI * 2);
-                cr.fillPreserve();
-
-                Gdk.cairo_set_source_pixbuf(cr, pixbuf,
-                                            (ACTOR_SIZE - pixbuf.get_width()) / 2,
-                                            (ACTOR_SIZE - pixbuf.get_height()) / 2);
-                cr.paint();
-
-                if (hasOutline) {
-                    cr.setSourceRGB(fgRed, fgGreen, fgBlue);
-                    cr.setLineWidth(1);
-                    cr.stroke();
-                }
-
-                this._surface = cr.getTarget();
-            });
-
-            let actor = new Clutter.Actor();
-
-            actor.set_content(canvas);
-            actor.set_size(ACTOR_SIZE, ACTOR_SIZE);
-            canvas.invalidate();
-
-            return actor;
+            let paintable = this._paintableFromIconName(leg.iconName,
+                                                        ICON_SIZE, fgRGBA);
+
+            let surface = new Cairo.ImageSurface(Cairo.Format.ARGB32,
+                                                 MARKER_SIZE, MARKER_SIZE);
+            let cr = new Cairo.Context(surface);
+            let pixbuf = Gdk.pixbuf_get_from_texture(paintable);
+
+            cr.setOperator(Cairo.Operator.CLEAR);
+            cr.paint();
+            cr.setOperator(Cairo.Operator.OVER);
+
+            cr.setSourceRGB(bgRed, bgGreen, bgBlue);
+            cr.arc(MARKER_SIZE / 2, MARKER_SIZE / 2, MARKER_SIZE / 2,
+                   0, Math.PI * 2);
+            cr.fillPreserve();
+
+            Gdk.cairo_set_source_pixbuf(cr, pixbuf,
+                                        (MARKER_SIZE - pixbuf.get_width()) / 2,
+                                        (MARKER_SIZE - pixbuf.get_height()) / 2);
+            cr.paint();
+
+            if (hasOutline) {
+                cr.setSourceRGB(fgRed, fgGreen, fgBlue);
+                cr.setLineWidth(1);
+                cr.stroke();
+            }
+
+            return Gdk.Texture.new_for_pixbuf(
+                Gdk.pixbuf_get_from_surface(surface, 0, 0,
+                                            MARKER_SIZE, MARKER_SIZE));
         } catch (e) {
             Utils.debug('Failed to load image: %s'.format(e.message));
             return null;
diff --git a/src/transitLegRow.js b/src/transitLegRow.js
index 371dacab..cde05ee4 100644
--- a/src/transitLegRow.js
+++ b/src/transitLegRow.js
@@ -119,29 +119,22 @@ export class TransitLegRow extends Gtk.ListBoxRow {
             }
         });
 
-        this._eventBox.connect('event', (widget, event) => {
-            this._handleEventBox(event);
-            return true;
-        });
+        this._buttonPressGesture = new Gtk.GestureSingle();
+        this.add_controller(this._buttonPressGesture);
+        this._buttonPressGesture.connect('begin', () => this._onPress());
 
         this._isExpanded = false;
     }
 
-    // Handle events received on the EventBox for expanding when clicking
-    _handleEventBox(event) {
-        let [isButton, button] = event.get_button();
-        let type = event.get_event_type();
-
-        if (isButton && button === 1 && type === Gdk.EventType.BUTTON_PRESS) {
-            if (this._isExpanded) {
-                this._collaps();
-            } else {
-                this._mapView.view.zoom_level = 16;
-                this._mapView.view.center_on(this._leg.fromCoordinate[0],
-                                             this._leg.fromCoordinate[1]);
-                if (this._hasIntructions())
-                    this._expand();
-            }
+    _onPress() {
+        if (this._isExpanded) {
+            this._collaps();
+        } else {
+            this._mapView.map.go_to_full(this._leg.fromCoordinate[0],
+                                         this._leg.fromCoordinate[1],
+                                         16);
+            if (this._hasIntructions())
+                this._expand();
         }
     }
 
@@ -207,6 +200,5 @@ GObject.registerClass({
                        'detailsRevealer',
                        'agencyLabel',
                        'collapsButton',
-                       'instructionList',
-                       'eventBox']
+                       'instructionList']
 }, TransitLegRow);
diff --git a/src/transitOptionsPanel.js b/src/transitOptionsPanel.js
index ee2abaa2..28c953f5 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() {
@@ -125,8 +127,7 @@ export class TransitOptionsPanel extends Gtk.Grid {
     _updateTransitDateButton(date) {
         let calendar = this._transitDateButton.popover.get_child();
 
-        calendar.select_month(date.get_month() - 1, date.get_year());
-        calendar.select_day(date.get_day_of_month());
+        calendar.select_day(date);
         this._transitDateButton.label =
             /*
              * Translators: this is a format string giving the equivalent to
@@ -153,9 +154,8 @@ export class TransitOptionsPanel extends Gtk.Grid {
         }
     }
 
-    _onTransitDateButtonToogled() {
-        if (!this._transitDateButton.active)
-            this._onTransitDateCalenderDaySelected();
+    _onTransitDateClosed() {
+        this._onTransitDateCalenderDaySelected();
     }
 
     _createTransitOptions() {
@@ -188,14 +188,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 4693edde..07dffd72 100644
--- a/src/transitPrintLayout.js
+++ b/src/transitPrintLayout.js
@@ -20,8 +20,6 @@
  */
 
 import Cairo from 'cairo';
-import Champlain from 'gi://Champlain';
-import Clutter from 'gi://Clutter';
 import GObject from 'gi://GObject';
 import Pango from 'gi://Pango';
 
@@ -34,13 +32,6 @@ import {TransitArrivalMarker} from './transitArrivalMarker.js';
 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_WIDTH = 5.0;
-
 // All following constants are ratios of surface size to page size
 const _Header = {
     SCALE_X: 0.9,
@@ -59,6 +50,8 @@ const _Instruction = {
     SCALE_MARGIN: 0.01
 };
 
+const _ICON_SIZE = 24;
+
 // luminance threashhold for drawing outline around route label badges
 const OUTLINE_LUMINANCE_THREASHHOLD = 0.9;
 
@@ -83,8 +76,11 @@ export class TransitPrintLayout extends PrintLayout {
         for (let leg of itinerary.legs) {
             numSurfaces++;
             // add a surface when a leg of the itinerary should have a map view
+            // TODO: skip this now, as we don't have minimaps now..
+            /*
             if (TransitPrintLayout._legHasMiniMap(leg))
                 numSurfaces++;
+            */
         }
 
         // always include the arrival row
@@ -93,108 +89,6 @@ export class TransitPrintLayout extends PrintLayout {
         return numSurfaces;
     }
 
-    _drawMapView(width, height, zoomLevel, index) {
-        let pageNum = this.numPages - 1;
-        let x = this._cursorX;
-        let y = this._cursorY;
-        let mapSource = MapSource.createPrintSource();
-        let markerLayer = new Champlain.MarkerLayer();
-        let view = new Champlain.View({ width: width,
-                                        height: height,
-                                        zoom_level: zoomLevel });
-        let leg = this._itinerary.legs[index];
-        let nextLeg = this._itinerary.legs[index + 1];
-        let previousLeg = index === 0 ? null : this._itinerary.legs[index - 1];
-
-        view.set_map_source(mapSource);
-        /* we want to add the path layer before the marker layer, so that
-         * boarding marker are drawn about the walk dash lines
-         */
-        this._addRouteLayer(view, index);
-        view.add_layer(markerLayer);
-
-        markerLayer.add_marker(this._createStartMarker(leg, previousLeg));
-        if (nextLeg)
-            markerLayer.add_marker(this._createBoardMarker(nextLeg));
-        else
-            markerLayer.add_marker(this._createArrivalMarker(leg));
-
-        /* in some cases, we seem to get get zero distance walking instructions
-         * within station complexes, don't try to show a bounding box for low
-         * distances, instead center on the spot
-         */
-        if (leg.distance < 10)
-            view.center_on(leg.fromCoordinate[0], leg.fromCoordinate[1]);
-        else
-            view.ensure_visible(this._createBBox(leg), false);
-        if (view.state !== Champlain.State.DONE) {
-            let notifyId = view.connect('notify::state', () => {
-                if (view.state === Champlain.State.DONE) {
-                    view.disconnect(notifyId);
-                    let surface = view.to_surface(true);
-                    if (surface)
-                        this._addSurface(surface, x, y, pageNum);
-                }
-            });
-        } else {
-            let surface = view.to_surface(true);
-            if (surface)
-                this._addSurface(surface, x, y, pageNum);
-        }
-    }
-
-    _createBBox(leg) {
-        return new Champlain.BoundingBox({ top:    leg.bbox.top,
-                                           left:   leg.bbox.left,
-                                           bottom: leg.bbox.bottom,
-                                           right:  leg.bbox.right });
-    }
-
-    _createStartMarker(leg, previousLeg) {
-        return new TransitWalkMarker({ leg: leg, previousLeg: previousLeg });
-    }
-
-    _createBoardMarker(leg) {
-        return new TransitBoardMarker({ leg: leg });
-    }
-
-    _createArrivalMarker(leg) {
-        return new TransitArrivalMarker({ leg: leg });
-    }
-
-    _addRouteLayer(view, index) {
-        let routeLayer = new Champlain.PathLayer({ stroke_width: _STROKE_WIDTH,
-                                                   stroke_color: _STROKE_COLOR });
-        let leg = this._itinerary.legs[index];
-
-        routeLayer.set_dash([5, 5]);
-        view.add_layer(routeLayer);
-
-        /* 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
-         * route might not reach all the way
-         */
-        if (index > 0 && !leg.transit) {
-            let previousLeg = this._itinerary.legs[index - 1];
-            let lastPoint =
-                previousLeg.polyline[previousLeg.polyline.length - 1];
-
-            routeLayer.add_node(lastPoint);
-        }
-
-        leg.polyline.forEach((node) => routeLayer.add_node(node));
-
-        /* like above, "stitch" the route segment with the next one if it's
-         * a walking leg, and not the last one
-         */
-        if (index < this._itinerary.legs.length - 1 && !leg.transit) {
-            let nextLeg = this._itinerary.legs[index + 1];
-            let firstPoint = nextLeg.polyline[0];
-
-            routeLayer.add_node(firstPoint);
-        }
-    }
-
     _drawInstruction(width, height, leg, start) {
         let pageNum = this.numPages - 1;
         let x = this._cursorX;
@@ -205,7 +99,7 @@ export class TransitPrintLayout extends PrintLayout {
         let fromText = Transit.getFromLabel(leg, start);
         let routeWidth = 0;
 
-        this._drawIcon(cr, leg.iconName, width, height);
+        this._drawIcon(cr, leg.iconName, width, height, _ICON_SIZE);
         this._drawText(cr, fromText, this._rtl ? timeWidth : height, 0,
                        width - height - timeWidth, height / 2, Pango.Alignment.LEFT);
 
@@ -273,7 +167,7 @@ export class TransitPrintLayout extends PrintLayout {
         let cr = new Cairo.Context(surface);
         let lastLeg = this._itinerary.legs[this._itinerary.legs.length - 1];
 
-        this._drawIcon(cr, 'maps-point-end-symbolic', width, height);
+        this._drawIcon(cr, 'maps-point-end-symbolic', width, height, _ICON_SIZE);
         // draw the arrival text
         this._drawTextVerticallyCentered(cr, Transit.getArrivalLabel(lastLeg),
                                          width - height * 3,
@@ -297,11 +191,6 @@ export class TransitPrintLayout extends PrintLayout {
         let headerHeight = _Header.SCALE_Y * this._pageHeight;
         let headerMargin = _Header.SCALE_MARGIN * this._pageHeight;
 
-        let mapViewWidth = _MapView.SCALE_X * this._pageWidth;
-        let mapViewHeight = _MapView.SCALE_Y * this._pageHeight;
-        let mapViewMargin = _MapView.SCALE_MARGIN * this._pageHeight;
-        let mapViewZoomLevel = _MapView.ZOOM_LEVEL;
-
         let instructionWidth = _Instruction.SCALE_X * this._pageWidth;
         let instructionHeight = _Instruction.SCALE_Y * this._pageHeight;
         let instructionMargin = _Instruction.SCALE_MARGIN * this._pageHeight;
@@ -315,22 +204,13 @@ export class TransitPrintLayout extends PrintLayout {
 
         for (let i = 0; i < this._itinerary.legs.length; i++) {
             let leg = this._itinerary.legs[i];
-            let hasMap = TransitPrintLayout._legHasMiniMap(leg);
             let instructionDy = instructionHeight + instructionMargin;
-            let mapDy = hasMap ? mapViewHeight + mapViewMargin : 0;
 
-            dy = instructionDy + mapDy;
+            dy = instructionDy;
             this._adjustPage(dy);
             this._drawInstruction(instructionWidth, instructionHeight, leg,
                                   i === 0);
             this._cursorY += instructionDy;
-
-            if (hasMap) {
-                let nextLeg = i < this._itinerary.legs.length - 1 ?
-                              this._itinerary.legs[i + 1] : null;
-                this._drawMapView(mapViewWidth, mapViewHeight, mapViewZoomLevel, i);
-                this._cursorY += mapDy;
-            }
         }
 
         this._drawArrival(instructionWidth, instructionHeight);
diff --git a/src/transitRouteLabel.js b/src/transitRouteLabel.js
index b8eebe2d..8de17047 100644
--- a/src/transitRouteLabel.js
+++ b/src/transitRouteLabel.js
@@ -22,6 +22,7 @@
 import Cairo from 'cairo';
 import GLib from 'gi://GLib';
 import GObject from 'gi://GObject';
+import Graphene from 'gi://Graphene';
 import Gtk from 'gi://Gtk';
 
 import * as Color from './color.js';
@@ -52,7 +53,6 @@ export class TransitRouteLabel extends Gtk.Label {
         super(params);
 
         this._setLabel(leg, compact);
-        this.connect('draw', this._onDraw.bind(this));
     }
 
     _setLabel(leg, compact) {
@@ -103,21 +103,27 @@ export class TransitRouteLabel extends Gtk.Label {
         this.label = '<span foreground="#%s">%s</span>'.format(
                                         textColor,
                                         GLib.markup_escape_text(label, -1));
+
+        this.queue_draw();
     }
 
     /* I didn't find any easy/obvious way to override widget background color
      * and getting rounded corner just using CSS styles, so doing a custom
      * Cairo drawing of a "roundrect"
      */
-    _onDraw(widget, cr) {
-        let width = widget.get_allocated_width();
-        let height = widget.get_allocated_height();
+    vfunc_snapshot(snapshot) {
+        let {x, y, width, height} = this.get_allocation();
+        let rect = new Graphene.Rect();
+
+        rect.init(0, 0, width, height);
 
+        let cr = snapshot.append_cairo(rect);
+
+        // clip off the badge, this seems to avoid some extra space at right/bottom with the CSS
         Gfx.drawColoredBagde(cr, this._color,
                              this._hasOutline ? this._textColor : null,
-                             0, 0, width, height);
-
-        return false;
+                             0, 0, width - 3, height - 3);
+        super.vfunc_snapshot(snapshot);
     }
 }
 
diff --git a/src/transitWalkMarker.js b/src/transitWalkMarker.js
index c1221391..b9da6ee2 100644
--- a/src/transitWalkMarker.js
+++ b/src/transitWalkMarker.js
@@ -62,15 +62,8 @@ export class TransitWalkMarker extends MapMarker {
                                    blue: bgBlue,
                                    alpha: 1.0
                                  });
-        let actor =
-            this._actorFromIconName('maps-point-start-symbolic', 0, color);
-
-        this.add_actor(actor);
-    }
-
-    get anchor() {
-        return { x: Math.floor(this.width / 2) - 1,
-                 y: Math.floor(this.height / 2) - 1 };
+        this._image.paintable =
+            this._paintableFromIconName('maps-point-start-symbolic', 16, color);
     }
 }
 
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..87868b30 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';
 
@@ -64,18 +63,16 @@ export class TurnPointMarker extends MapMarker {
 
         this._queryPoint = queryPoint;
 
-        let actor;
         if (this._queryPoint) {
-            this.draggable = true;
-            this.connect('drag-finish', () => this._onMarkerDrag());
-            actor = this._actorFromIconName(turnPoint.iconName, 0);
+            this._image.paintable =
+                this._paintableFromIconName(turnPoint.iconName, 16);
         } else {
             let color = this._getColor(transitLeg);
-            actor = this._actorFromIconName('maps-point-end-symbolic',
-                                            0,
+            this._image.paintable =
+                this._paintableFromIconName('maps-point-end-symbolic',
+                                            16,
                                             color);
         }
-        this.add_actor(actor);
     }
 
     _getColor(transitLeg) {
@@ -102,7 +99,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', () => {
@@ -112,15 +108,6 @@ export class TurnPointMarker extends MapMarker {
 
         view.go_to(this.latitude, this.longitude);
     }
-
-    _onMarkerDrag() {
-        let query = Application.routeQuery;
-        let place =
-            new Place({ location: new Location({ latitude: this.latitude.toFixed(5),
-                                                 longitude: this.longitude.toFixed(5) }) });
-
-        this._queryPoint.place = place;
-    }
 }
 
 GObject.registerClass(TurnPointMarker);
diff --git a/src/userLocationMarker.js b/src/userLocationMarker.js
index 55d9d85f..d4dbc192 100644
--- a/src/userLocationMarker.js
+++ b/src/userLocationMarker.js
@@ -19,49 +19,64 @@
  * Author: Damián Nohales <damiannohales gmail com>
  */
 
-import Champlain from 'gi://Champlain';
-import Clutter from 'gi://Clutter';
+import Cairo from 'cairo';
 import GObject from 'gi://GObject';
+import Graphene from 'gi://Graphene';
+import Shumate from 'gi://Shumate';
 
 import {MapMarker} from './mapMarker.js';
 
-export class AccuracyCircleMarker extends Champlain.Point {
+export class AccuracyCircleMarker extends Shumate.Marker {
 
     constructor(params) {
         let place = params.place;
         delete params.place;
 
-        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;
 
         super(params);
 
         this._place = place;
     }
 
-    refreshGeometry(view) {
+    refreshGeometry(mapView) {
         this.latitude = this._place.location.latitude;
         this.longitude = this._place.location.longitude;
 
-        let zoom = view.zoom_level;
-        let source = view.map_source;
+        let zoom = mapView.map.viewport.zoom_level;
+        let source = mapView.mapSource;
         let metersPerPixel = source.get_meters_per_pixel(zoom,
                                                          this.latitude,
                                                          this.longitude);
         let size = this._place.location.accuracy * 2 / metersPerPixel;
+        let {x, y, width, height} = mapView.get_allocation();
 
-        if (size > view.width || size > view.height)
-            this.hide();
+        if (size > width || size > height)
+            this.visible = false;
         else {
-            this.size = size;
-            this.show();
+            this.set_size_request(size, size);
+            this.visible = true;
+            this.queue_draw();
         }
     }
+
+    vfunc_snapshot(snapshot) {
+        let {x, y, width, height} = this.get_allocation();
+        let rect = new Graphene.Rect();
+
+        rect.init(0, 0, width, height);
+
+        let cr = snapshot.append_cairo(rect);
+
+        cr.setOperator(Cairo.Operator.OVER);
+
+        cr.setSourceRGBA(0, 0, 255, 0.1);
+        cr.arc(width / 2, width / 2, width / 2, 0, Math.PI * 2);
+        cr.fillPreserve();
+
+        super.vfunc_snapshot(snapshot);
+    }
 }
 
 GObject.registerClass(AccuracyCircleMarker);
@@ -73,24 +88,20 @@ export class UserLocationMarker extends MapMarker {
 
         this._accuracyMarker = new AccuracyCircleMarker({ place: this.place });
         this.connect('notify::view-zoom-level',
-                     () => this._accuracyMarker.refreshGeometry(this._view));
-        this._view.connect('notify::width',
-                     () => this._accuracyMarker.refreshGeometry(this._view));
-        this._view.connect('notify::height',
-                     () => this._accuracyMarker.refreshGeometry(this._view));
-        this._accuracyMarker.refreshGeometry(this._view);
+                     () => this._accuracyMarker.refreshGeometry(this._mapView));
+        this._mapView.connect('notify::default-width',
+                     () => this._accuracyMarker.refreshGeometry(this._mapView));
+        this._mapView.connect('notify::default-height',
+                     () => this._accuracyMarker.refreshGeometry(this._mapView));
+        this._accuracyMarker.refreshGeometry(this._mapView);
 
         this.place.connect('notify::location', () => this._updateLocation());
+        this._image.pixel_size = 24;
         this._updateLocation();
 
         this.connect('notify::visible', this._updateAccuracyCircle.bind(this));
     }
 
-    get anchor() {
-        return { x: Math.floor(this.width / 2),
-                 y: Math.floor(this.height / 2) };
-    }
-
     _hasBubble() {
         return true;
     }
@@ -101,30 +112,32 @@ 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);
-            this._actor.set_rotation_angle(Clutter.RotateAxis.Z_AXIS, this.place.location.heading);
+            this._image.icon_name = 'user-location-compass'
+            this.queue_draw();
         } else {
-            this._actor = this._actorFromIconName('user-location', 0);
+            this._image.icon_name = 'user-location';
         }
-        this.add_actor(this._actor);
 
         this._updateAccuracyCircle();
     }
 
     _updateAccuracyCircle() {
         if (this.visible && this.place.location.accuracy > 0) {
-            this._accuracyMarker.refreshGeometry(this._view);
+            this._accuracyMarker.refreshGeometry(this._mapView);
         } else {
             this._accuracyMarker.visible = false;
         }
     }
+
+    vfunc_snapshot(snapshot) {
+        if (this.place.location.heading > -1) {
+            snapshot.rotate(this.place.location.heading);
+        }
+
+        this.snapshot_child(this._image, snapshot);
+        super.vfunc_snapshot(snapshot);
+    }
 }
 
 GObject.registerClass(UserLocationMarker);
diff --git a/src/utils.js b/src/utils.js
index 2cf6920b..38994b8f 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);
@@ -460,7 +464,7 @@ export function showDialog(msg, type, transientFor) {
                                 text: msg });
 
     messageDialog.connect('response', () => messageDialog.destroy());
-    messageDialog.show_all();
+    messageDialog.show();
 }
 
 let decoder = new TextDecoder('utf-8');
diff --git a/src/zoomInDialog.js b/src/zoomInDialog.js
index c7fd7b6b..89eb07d1 100644
--- a/src/zoomInDialog.js
+++ b/src/zoomInDialog.js
@@ -32,8 +32,8 @@ export class ZoomInDialog extends Gtk.Dialog {
         delete params.latitude;
         let longitude = params.longitude;
         delete params.longitude;
-        let view = params.view;
-        delete params.view;
+        let map = params.map;
+        delete params.map;
 
         /* This is a construct-only property and cannot be set by GtkBuilder */
         params.use_header_bar = true;
@@ -42,16 +42,15 @@ export class ZoomInDialog extends Gtk.Dialog {
 
         this._latitude = latitude;
         this._longitude = longitude;
-        this._view = view;
+        this._map = map;
         this._zoomInButton.connect('clicked', () => this._onZoomIn());
         this._cancelButton.connect('clicked', () => this._onCancel());
     }
 
     _onZoomIn() {
-        this._view.zoom_level = OSMEdit.MIN_ADD_LOCATION_ZOOM_LEVEL;
-
         /* center on the position first selected */
-        this._view.center_on(this._latitude, this._longitude);
+        this._map.go_to_full(this._latitude, this._longitude,
+                             OSMEdit.MIN_ADD_LOCATION_ZOOM_LEVEL);
         this.response(Gtk.ResponseType.OK);
     }
 


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