[niepce/gtk4: 1/10] gtk4: initial port




commit bcb322106d81f8c1beb732cc6e6e4a67e5c01c5b
Author: Hubert Figuière <hub figuiere net>
Date:   Sun Mar 20 11:28:24 2022 -0400

    gtk4: initial port
    
    - ThumbListView and ImageGridView no longer gtk::Widget
    - Stubber osm map widget (not gtk4 compatible)
    - Require C++17

 Cargo.lock                                         | 177 ++++++++-------
 configure.ac                                       |   6 +-
 crates/npc-engine/src/db/filebundle.rs             |   1 +
 crates/npc-engine/src/db/libfile.rs                |   2 +
 crates/npc-engine/src/db/libmetadata.rs            |   1 +
 crates/npc-engine/src/db/library.rs                |   3 +-
 crates/npc-engine/src/lib.rs                       |   7 -
 crates/npc-engine/src/library/commands.rs          |   1 +
 crates/npc-engine/src/library/notification.rs      |   3 +-
 crates/npc-engine/src/library/thumbnail_cache.rs   |   1 +
 crates/npc-fwk/Cargo.toml                          |   6 +-
 crates/npc-fwk/src/base/rgbcolour.rs               |  12 +-
 crates/npc-fwk/src/lib.rs                          |  17 --
 crates/npc-fwk/src/toolkit/gdk_utils.rs            |   2 +-
 crates/npc-fwk/src/toolkit/widgets/rating_label.rs | 125 ++++++-----
 crates/npc-fwk/src/utils/exiv2.rs                  |   2 +-
 crates/npc-fwk/src/utils/files.rs                  |   8 +-
 niepce-main/Cargo.toml                             |   7 +-
 niepce-main/src/lib.rs                             |  21 +-
 niepce-main/src/libraryclient.rs                   |   8 +-
 niepce-main/src/libraryclient/clientimpl.rs        |   1 +
 niepce-main/src/niepce/ui/dialogs/confirm.rs       |  28 +--
 .../src/niepce/ui/dialogs/requestnewfolder.rs      |  45 ++--
 niepce-main/src/niepce/ui/image_grid_view.rs       |  92 ++++----
 niepce-main/src/niepce/ui/image_list_store.rs      |  87 ++++----
 niepce-main/src/niepce/ui/imagetoolbar.rs          |  40 ++--
 niepce-main/src/niepce/ui/library_cell_renderer.rs | 239 ++++++++++++---------
 niepce-main/src/niepce/ui/thumb_nav.rs             | 101 +++++----
 niepce-main/src/niepce/ui/thumb_strip_view.rs      | 235 +++++++-------------
 src/Makefile.am                                    |   2 +-
 src/ext/Makefile.am                                |   3 +-
 src/fwk/toolkit/application.cpp                    |  12 +-
 src/fwk/toolkit/application.hpp                    |  11 +-
 src/fwk/toolkit/configuration.cpp                  |  19 +-
 src/fwk/toolkit/configuration.hpp                  |  10 +-
 src/fwk/toolkit/dialog.cpp                         |  21 +-
 src/fwk/toolkit/dockable.cpp                       |   4 +-
 src/fwk/toolkit/frame.cpp                          |  46 ++--
 src/fwk/toolkit/frame.hpp                          |  11 +-
 src/fwk/toolkit/gdkutils.cpp                       |  20 +-
 src/fwk/toolkit/gphoto.cpp                         |   2 +-
 src/fwk/toolkit/mapcontroller.cpp                  |  28 ++-
 src/fwk/toolkit/metadatawidget.cpp                 |  61 +++---
 src/fwk/toolkit/metadatawidget.hpp                 |  17 +-
 src/fwk/toolkit/mimetype.cpp                       |   8 +-
 src/fwk/toolkit/notificationcenter.cpp             |   7 +-
 src/fwk/toolkit/notificationcenter.hpp             |  12 +-
 src/fwk/toolkit/uiresult.hpp                       |   6 +-
 src/fwk/toolkit/undo.hpp                           |  10 +-
 src/fwk/toolkit/widgets/addinstreemodel.cpp        |  10 +-
 src/fwk/toolkit/widgets/addinstreemodel.hpp        |  22 +-
 src/fwk/toolkit/widgets/dock.cpp                   |   8 +-
 src/fwk/toolkit/widgets/dock.hpp                   |   9 +-
 src/fwk/toolkit/widgets/editablehscale.cpp         |  65 +++---
 src/fwk/toolkit/widgets/editablehscale.hpp         |  28 +--
 src/fwk/toolkit/widgets/tokentextview.cpp          |   4 +-
 src/fwk/utils/init.cpp                             |   3 +-
 src/fwk/utils/modulemanager.cpp                    |   6 +-
 src/fwk/utils/pathutils.cpp                        |   4 +-
 src/ncr/image.cpp                                  |   2 +-
 src/ncr/image.hpp                                  |  13 +-
 src/niepce/modules/darkroom/darkroommodule.cpp     |  23 +-
 src/niepce/modules/darkroom/darkroommodule.hpp     |  10 +-
 src/niepce/modules/darkroom/dritemwidget.cpp       |  14 +-
 src/niepce/modules/darkroom/imagecanvas.cpp        |  89 ++++----
 src/niepce/modules/darkroom/imagecanvas.hpp        |  13 +-
 src/niepce/modules/darkroom/toolboxcontroller.cpp  |   8 +-
 src/niepce/modules/map/mapmodule.cpp               |   4 +-
 src/niepce/modules/map/mapmodule.hpp               |  10 +-
 src/niepce/notificationcenter.hpp                  |   4 +-
 src/niepce/ui/dialogs/editlabels.cpp               |  11 +-
 src/niepce/ui/dialogs/importdialog.cpp             |  45 ++--
 src/niepce/ui/dialogs/importdialog.hpp             |   6 +-
 .../ui/dialogs/importers/cameraimporterui.cpp      |   9 +-
 .../ui/dialogs/importers/directoryimporterui.cpp   |  26 +--
 src/niepce/ui/dialogs/preferencesdialog.cpp        |   9 +-
 src/niepce/ui/filmstripcontroller.cpp              |  15 +-
 src/niepce/ui/filmstripcontroller.hpp              |   1 +
 src/niepce/ui/gridviewmodule.cpp                   |  77 +++----
 src/niepce/ui/gridviewmodule.hpp                   |  14 +-
 src/niepce/ui/imageliststore.cpp                   |  18 +-
 src/niepce/ui/imageliststore.hpp                   |   8 +-
 src/niepce/ui/metadatapanecontroller.cpp           |   4 +-
 src/niepce/ui/metadatapanecontroller.hpp           |  13 +-
 src/niepce/ui/moduleshellwidget.cpp                |  28 ++-
 src/niepce/ui/moduleshellwidget.hpp                |  19 +-
 src/niepce/ui/niepceapplication.cpp                |   6 +-
 src/niepce/ui/niepcewindow.cpp                     |  75 ++++---
 src/niepce/ui/niepcewindow.hpp                     |  12 +-
 src/niepce/ui/selectioncontroller.cpp              |   2 +-
 src/niepce/ui/selectioncontroller.hpp              |  12 +-
 src/niepce/ui/workspacecontroller.cpp              | 121 +++++------
 src/niepce/ui/workspacecontroller.hpp              |  50 ++---
 93 files changed, 1202 insertions(+), 1316 deletions(-)
---
diff --git a/Cargo.lock b/Cargo.lock
index f72fc00..1d9a58e 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -39,30 +39,6 @@ dependencies = [
  "futures-core",
 ]
 
-[[package]]
-name = "atk"
-version = "0.15.1"
-source = "registry+https://github.com/rust-lang/crates.io-index";
-checksum = "2c3d816ce6f0e2909a96830d6911c2aff044370b1ef92d7f267b43bae5addedd"
-dependencies = [
- "atk-sys",
- "bitflags",
- "glib",
- "libc",
-]
-
-[[package]]
-name = "atk-sys"
-version = "0.15.1"
-source = "registry+https://github.com/rust-lang/crates.io-index";
-checksum = "58aeb089fb698e06db8089971c7ee317ab9644bade33383f63631437b03aafb6"
-dependencies = [
- "glib-sys",
- "gobject-sys",
- "libc",
- "system-deps",
-]
-
 [[package]]
 name = "atty"
 version = "0.2.14"
@@ -294,22 +270,6 @@ dependencies = [
  "slab",
 ]
 
-[[package]]
-name = "gdk"
-version = "0.15.4"
-source = "registry+https://github.com/rust-lang/crates.io-index";
-checksum = "a6e05c1f572ab0e1f15be94217f0dc29088c248b14f792a5ff0af0d84bcda9e8"
-dependencies = [
- "bitflags",
- "cairo-rs",
- "gdk-pixbuf",
- "gdk-sys",
- "gio",
- "glib",
- "libc",
- "pango",
-]
-
 [[package]]
 name = "gdk-pixbuf"
 version = "0.15.6"
@@ -337,10 +297,26 @@ dependencies = [
 ]
 
 [[package]]
-name = "gdk-sys"
-version = "0.15.1"
+name = "gdk4"
+version = "0.4.6"
 source = "registry+https://github.com/rust-lang/crates.io-index";
-checksum = "32e7a08c1e8f06f4177fb7e51a777b8c1689f743a7bc11ea91d44d2226073a88"
+checksum = "d9df40006277ff44538fe758400fc671146f6f2665978b6b57d2408db3c2becf"
+dependencies = [
+ "bitflags",
+ "cairo-rs",
+ "gdk-pixbuf",
+ "gdk4-sys",
+ "gio",
+ "glib",
+ "libc",
+ "pango",
+]
+
+[[package]]
+name = "gdk4-sys"
+version = "0.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index";
+checksum = "48a39e34abe35ee2cf54a1e29dd983accecd113ad30bdead5050418fa92f2a1b"
 dependencies = [
  "cairo-sys-rs",
  "gdk-pixbuf-sys",
@@ -467,51 +443,88 @@ dependencies = [
 ]
 
 [[package]]
-name = "gtk"
-version = "0.15.4"
+name = "graphene-rs"
+version = "0.15.1"
 source = "registry+https://github.com/rust-lang/crates.io-index";
-checksum = "5f2d1326b36af927fe46ae2f89a8fec38c6f0d279ebc5ef07ffeeabb70300bfc"
+checksum = "7c54f9fbbeefdb62c99f892dfca35f83991e2cb5b46a8dc2a715e58612f85570"
+dependencies = [
+ "glib",
+ "graphene-sys",
+ "libc",
+]
+
+[[package]]
+name = "graphene-sys"
+version = "0.15.1"
+source = "registry+https://github.com/rust-lang/crates.io-index";
+checksum = "03f311acb023cf7af5537f35de028e03706136eead7f25a31e8fd26f5011e0b3"
+dependencies = [
+ "glib-sys",
+ "libc",
+ "pkg-config",
+ "system-deps",
+]
+
+[[package]]
+name = "gsk4"
+version = "0.4.6"
+source = "registry+https://github.com/rust-lang/crates.io-index";
+checksum = "1bf63d454e2f75abd92ee6de0ac9fc5aaf1018cd9c458aaf9de296c5cbab6bb9"
 dependencies = [
- "atk",
  "bitflags",
  "cairo-rs",
- "field-offset",
- "futures-channel",
- "gdk",
- "gdk-pixbuf",
- "gio",
+ "gdk4",
  "glib",
- "gtk-sys",
- "gtk3-macros",
+ "graphene-rs",
+ "gsk4-sys",
  "libc",
- "once_cell",
  "pango",
- "pkg-config",
 ]
 
 [[package]]
-name = "gtk-sys"
-version = "0.15.3"
+name = "gsk4-sys"
+version = "0.4.2"
 source = "registry+https://github.com/rust-lang/crates.io-index";
-checksum = "d5bc2f0587cba247f60246a0ca11fe25fb733eabc3de12d1965fc07efab87c84"
+checksum = "e31d21d7ce02ba261bb24c50c4ab238a10b41a2c97c32afffae29471b7cca69b"
 dependencies = [
- "atk-sys",
  "cairo-sys-rs",
- "gdk-pixbuf-sys",
- "gdk-sys",
- "gio-sys",
+ "gdk4-sys",
  "glib-sys",
  "gobject-sys",
+ "graphene-sys",
  "libc",
  "pango-sys",
  "system-deps",
 ]
 
 [[package]]
-name = "gtk3-macros"
-version = "0.15.4"
+name = "gtk4"
+version = "0.4.6"
 source = "registry+https://github.com/rust-lang/crates.io-index";
-checksum = "24f518afe90c23fba585b2d7697856f9e6a7bbc62f65588035e66f6afb01a2e9"
+checksum = "9e841556e3fe55d8a43ada76b7b08a5f65570bbdfe3b8f72c333053b8832c626"
+dependencies = [
+ "bitflags",
+ "cairo-rs",
+ "field-offset",
+ "futures-channel",
+ "gdk-pixbuf",
+ "gdk4",
+ "gio",
+ "glib",
+ "graphene-rs",
+ "gsk4",
+ "gtk4-macros",
+ "gtk4-sys",
+ "libc",
+ "once_cell",
+ "pango",
+]
+
+[[package]]
+name = "gtk4-macros"
+version = "0.4.3"
+source = "registry+https://github.com/rust-lang/crates.io-index";
+checksum = "573db42bb64973a4d5f718b73caa7204285a1a665308a23b11723d0ee56ec305"
 dependencies = [
  "anyhow",
  "proc-macro-crate",
@@ -521,6 +534,25 @@ dependencies = [
  "syn",
 ]
 
+[[package]]
+name = "gtk4-sys"
+version = "0.4.5"
+source = "registry+https://github.com/rust-lang/crates.io-index";
+checksum = "c47c075e8f795c38f6e9a47b51a73eab77b325f83c0154979ed4d4245c36490d"
+dependencies = [
+ "cairo-sys-rs",
+ "gdk-pixbuf-sys",
+ "gdk4-sys",
+ "gio-sys",
+ "glib-sys",
+ "gobject-sys",
+ "graphene-sys",
+ "gsk4-sys",
+ "libc",
+ "pango-sys",
+ "system-deps",
+]
+
 [[package]]
 name = "hashbrown"
 version = "0.11.2"
@@ -670,15 +702,16 @@ dependencies = [
  "async-channel",
  "cairo-rs",
  "cbindgen",
- "gdk",
  "gdk-pixbuf",
  "gdk-pixbuf-sys",
+ "gdk4",
  "gettext-rs",
  "gio",
  "gio-sys",
  "glib",
- "gtk",
- "gtk-sys",
+ "graphene-rs",
+ "gtk4",
+ "gtk4-sys",
  "libc",
  "npc-engine",
  "npc-fwk",
@@ -712,15 +745,15 @@ dependencies = [
  "cbindgen",
  "chrono",
  "exempi",
- "gdk",
  "gdk-pixbuf",
  "gdk-pixbuf-sys",
+ "gdk4",
  "gio",
  "gio-sys",
  "glib",
  "glib-sys",
- "gtk",
- "gtk-sys",
+ "gtk4",
+ "gtk4-sys",
  "lazy_static",
  "libc",
  "libopenraw-rs",
diff --git a/configure.ac b/configure.ac
index 072fb0f..b365809 100644
--- a/configure.ac
+++ b/configure.ac
@@ -13,7 +13,7 @@ dnl all the library version.
 dnl if one is harcoded elsewhere, it is a bug
 LIBGIOMM_VERSION=2.66
 LIBGLIBMM_VERSION=2.66
-LIBGTKMM_VERSION=3.24
+LIBGTKMM_VERSION=4.4
 EXEMPI_VERSION=2.4.0
 SQLITE_VERSION=3.0
 GEGL_VERSION=0.3.0
@@ -25,7 +25,7 @@ BOOST_VERSION=1.60
 
 AC_PROG_CXX
 AC_GNU_SOURCE
-AX_CXX_COMPILE_STDCXX_11(noext,mandatory)
+AX_CXX_COMPILE_STDCXX_17(noext,mandatory)
 
 AC_ARG_ENABLE(debug,[  --enable-debug    Turn on debugging],[
         case "${enableval}" in
@@ -58,7 +58,7 @@ AC_LANG_CPLUSPLUS
 AC_LANG_COMPILER_REQUIRE
 
 dnl Framework requirements.
-PKG_CHECK_MODULES(FRAMEWORK, [glibmm-2.4 >= $LIBGLIBMM_VERSION giomm-2.4 >= $LIBGIOMM_VERSION gthread-2.0 
gtkmm-3.0 >= $LIBGTKMM_VERSION sqlite3 >= $SQLITE_VERSION exempi-2.0 >= $EXEMPI_VERSION libxml-2.0 >= 
$LIBXML2_VERSION osmgpsmap-1.0])
+PKG_CHECK_MODULES(FRAMEWORK, [glibmm-2.68 >= $LIBGLIBMM_VERSION giomm-2.68 >= $LIBGIOMM_VERSION gthread-2.0 
gtkmm-4.0 >= $LIBGTKMM_VERSION sqlite3 >= $SQLITE_VERSION exempi-2.0 >= $EXEMPI_VERSION libxml-2.0 >= 
$LIBXML2_VERSION])
 
 dnl optional framework
 PKG_CHECK_MODULES(GPHOTO, [libgphoto2 >= $LIBGPHOTO_VERSION libgphoto2_port])
diff --git a/crates/npc-engine/src/db/filebundle.rs b/crates/npc-engine/src/db/filebundle.rs
index 0add42a..f79a7df 100644
--- a/crates/npc-engine/src/db/filebundle.rs
+++ b/crates/npc-engine/src/db/filebundle.rs
@@ -21,6 +21,7 @@ use std::ffi::OsString;
 use std::path::{Path, PathBuf};
 
 use crate::db::libfile::FileType;
+use npc_fwk::dbg_out;
 use npc_fwk::toolkit::mimetype::{IsRaw, MType};
 use npc_fwk::MimeType;
 
diff --git a/crates/npc-engine/src/db/libfile.rs b/crates/npc-engine/src/db/libfile.rs
index 337f2aa..3b49802 100644
--- a/crates/npc-engine/src/db/libfile.rs
+++ b/crates/npc-engine/src/db/libfile.rs
@@ -22,6 +22,8 @@ use std::ffi::CStr;
 use std::ffi::CString;
 use std::path::{Path, PathBuf};
 
+use npc_fwk::err_out;
+
 use super::fsfile::FsFile;
 use super::FromDb;
 use super::LibraryId;
diff --git a/crates/npc-engine/src/db/libmetadata.rs b/crates/npc-engine/src/db/libmetadata.rs
index f7d305d..cab2a34 100644
--- a/crates/npc-engine/src/db/libmetadata.rs
+++ b/crates/npc-engine/src/db/libmetadata.rs
@@ -25,6 +25,7 @@ use super::NiepceProperties as Np;
 use super::{FromDb, LibraryId};
 use crate::{NiepcePropertyBag, NiepcePropertySet};
 use npc_fwk::utils::exempi::{NS_DC, NS_XAP};
+use npc_fwk::{dbg_out, err_out};
 use npc_fwk::{xmp_date_from, PropertyBag, PropertySet, PropertyValue, XmpMeta};
 
 #[derive(Clone)]
diff --git a/crates/npc-engine/src/db/library.rs b/crates/npc-engine/src/db/library.rs
index cd3f69d..00df239 100644
--- a/crates/npc-engine/src/db/library.rs
+++ b/crates/npc-engine/src/db/library.rs
@@ -1,7 +1,7 @@
 /*
  * niepce - engine/db/library.rs
  *
- * Copyright (C) 2017-2021 Hubert Figuière
+ * Copyright (C) 2017-2022 Hubert Figuière
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -40,6 +40,7 @@ use crate::db::libmetadata::LibMetadata;
 use crate::library::notification::LibNotification;
 use npc_fwk::toolkit;
 use npc_fwk::PropertyValue;
+use npc_fwk::{dbg_assert, dbg_out, err_out};
 
 #[repr(i32)]
 #[derive(PartialEq, Clone, Copy)]
diff --git a/crates/npc-engine/src/lib.rs b/crates/npc-engine/src/lib.rs
index 86c6901..874c614 100644
--- a/crates/npc-engine/src/lib.rs
+++ b/crates/npc-engine/src/lib.rs
@@ -17,13 +17,6 @@
  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
  */
 
-extern crate gdk_pixbuf;
-extern crate gdk_pixbuf_sys;
-extern crate glib;
-
-#[macro_use]
-extern crate npc_fwk;
-
 pub mod db;
 pub mod library;
 
diff --git a/crates/npc-engine/src/library/commands.rs b/crates/npc-engine/src/library/commands.rs
index 28c87fc..362edd5 100644
--- a/crates/npc-engine/src/library/commands.rs
+++ b/crates/npc-engine/src/library/commands.rs
@@ -31,6 +31,7 @@ use crate::db::library::{Library, Managed};
 use crate::db::props::NiepceProperties as Np;
 use crate::db::LibraryId;
 use npc_fwk::PropertyValue;
+use npc_fwk::{dbg_assert, err_out, err_out_line};
 
 pub fn cmd_list_all_keywords(lib: &Library) -> bool {
     match lib.get_all_keywords() {
diff --git a/crates/npc-engine/src/library/notification.rs b/crates/npc-engine/src/library/notification.rs
index 30452c9..40f8ad4 100644
--- a/crates/npc-engine/src/library/notification.rs
+++ b/crates/npc-engine/src/library/notification.rs
@@ -1,7 +1,7 @@
 /*
  * niepce - engine/library/notification.rs
  *
- * Copyright (C) 2017-2021 Hubert Figuière
+ * Copyright (C) 2017-2022 Hubert Figuière
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -21,6 +21,7 @@ use super::queriedcontent::QueriedContent;
 use crate::db::libfile::FileStatus;
 use crate::db::{Keyword, Label, LibFolder, LibMetadata, LibraryId, NiepceProperties};
 use npc_fwk::base::PropertyIndex;
+use npc_fwk::err_out;
 use npc_fwk::toolkit;
 use npc_fwk::toolkit::thumbnail;
 use npc_fwk::toolkit::PortableChannel;
diff --git a/crates/npc-engine/src/library/thumbnail_cache.rs 
b/crates/npc-engine/src/library/thumbnail_cache.rs
index 04775f9..b4bdc1e 100644
--- a/crates/npc-engine/src/library/thumbnail_cache.rs
+++ b/crates/npc-engine/src/library/thumbnail_cache.rs
@@ -35,6 +35,7 @@ use crate::library::notification::{FileStatusChange, LcChannel, LibNotification}
 use crate::library::queriedcontent::QueriedContent;
 use npc_fwk::toolkit;
 use npc_fwk::toolkit::thumbnail::Thumbnail;
+use npc_fwk::{dbg_out, err_out};
 
 /// Thumbnail task
 struct ThumbnailTask {
diff --git a/crates/npc-fwk/Cargo.toml b/crates/npc-fwk/Cargo.toml
index ce15c76..f555ec1 100644
--- a/crates/npc-fwk/Cargo.toml
+++ b/crates/npc-fwk/Cargo.toml
@@ -16,11 +16,11 @@ gio-sys = "*"
 gio = "^0.15.7"
 glib-sys = "*"
 glib = "^0.15.9"
-gtk-sys = "*"
-gdk = "^0.15.4"
+gtk4-sys = "*"
+gdk4 = "^0.4.6"
 gdk-pixbuf-sys = "*"
 gdk-pixbuf = "^0.15.6"
-gtk = "^0.15.3"
+gtk4 = "^0.4.6"
 lazy_static = "^1.2.0"
 libc = "0.2.39"
 libopenraw-rs = { path = "../../../libopenraw-rs/libopenraw-rs" }
diff --git a/crates/npc-fwk/src/base/rgbcolour.rs b/crates/npc-fwk/src/base/rgbcolour.rs
index 341372c..1807fce 100644
--- a/crates/npc-fwk/src/base/rgbcolour.rs
+++ b/crates/npc-fwk/src/base/rgbcolour.rs
@@ -73,12 +73,12 @@ impl ToString for RgbColour {
     }
 }
 
-impl From<RgbColour> for gdk::RGBA {
-    fn from(v: RgbColour) -> gdk::RGBA {
-        gdk::RGBA::new(
-            v.r as f64 / 65535_f64,
-            v.g as f64 / 65535_f64,
-            v.b as f64 / 65535_f64,
+impl From<RgbColour> for gdk4::RGBA {
+    fn from(v: RgbColour) -> gdk4::RGBA {
+        gdk4::RGBA::new(
+            v.r as f32 / 65535_f32,
+            v.g as f32 / 65535_f32,
+            v.b as f32 / 65535_f32,
             1.0,
         )
     }
diff --git a/crates/npc-fwk/src/lib.rs b/crates/npc-fwk/src/lib.rs
index f6674c8..80502c4 100644
--- a/crates/npc-fwk/src/lib.rs
+++ b/crates/npc-fwk/src/lib.rs
@@ -17,23 +17,6 @@
  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
  */
 
-extern crate chrono;
-extern crate exempi;
-extern crate gdk;
-extern crate gdk_pixbuf;
-extern crate gdk_pixbuf_sys;
-extern crate gio;
-extern crate gio_sys;
-extern crate glib;
-extern crate glib_sys;
-extern crate gtk;
-extern crate gtk_sys;
-#[macro_use]
-extern crate lazy_static;
-extern crate libc;
-extern crate multimap;
-extern crate once_cell;
-
 #[macro_use]
 pub mod base;
 pub mod capi;
diff --git a/crates/npc-fwk/src/toolkit/gdk_utils.rs b/crates/npc-fwk/src/toolkit/gdk_utils.rs
index a3859c1..feb05b4 100644
--- a/crates/npc-fwk/src/toolkit/gdk_utils.rs
+++ b/crates/npc-fwk/src/toolkit/gdk_utils.rs
@@ -1,7 +1,7 @@
 /*
  * niepce - npc-fwk/toolkit/gdk_utils.rs
  *
- * Copyright (C) 2020-2021 Hubert Figuière
+ * Copyright (C) 2020-2022 Hubert Figuière
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
diff --git a/crates/npc-fwk/src/toolkit/widgets/rating_label.rs 
b/crates/npc-fwk/src/toolkit/widgets/rating_label.rs
index ba19231..a3a21d4 100644
--- a/crates/npc-fwk/src/toolkit/widgets/rating_label.rs
+++ b/crates/npc-fwk/src/toolkit/widgets/rating_label.rs
@@ -20,13 +20,13 @@
 use libc::c_int;
 use std::cell::Cell;
 
-use gdk::prelude::*;
+use gdk4::prelude::*;
 use gdk_pixbuf::Pixbuf;
 use glib::subclass::prelude::*;
 use glib::subclass::Signal;
 use glib::translate::*;
-use gtk::prelude::*;
-use gtk::subclass::prelude::*;
+use gtk4::prelude::*;
+use gtk4::subclass::prelude::*;
 
 use once_cell::unsync::Lazy;
 
@@ -43,7 +43,7 @@ const PIXBUFS: Lazy<Pixbufs> = Lazy::new(|| Pixbufs {
 glib::wrapper! {
     pub struct RatingLabel(
         ObjectSubclass<RatingLabelPriv>)
-        @extends gtk::DrawingArea, gtk::Widget;
+        @extends gtk4::DrawingArea, gtk4::Widget;
 }
 
 pub struct RatingLabelPriv {
@@ -61,13 +61,22 @@ impl RatingLabelPriv {
         let w = self.instance();
         w.queue_draw();
     }
+
+    fn press_event(&self, _gesture: &gtk4::GestureClick, _: i32, x: f64, _: f64) {
+        let new_rating = RatingLabel::rating_value_from_hit_x(x);
+        if new_rating != self.rating.get() {
+            self.set_rating(new_rating);
+            self.instance()
+                .emit_by_name::<()>("rating-changed", &[&new_rating]);
+        }
+    }
 }
 
 #[glib::object_subclass]
 impl ObjectSubclass for RatingLabelPriv {
     const NAME: &'static str = "RatingLabel";
     type Type = RatingLabel;
-    type ParentType = gtk::DrawingArea;
+    type ParentType = gtk4::DrawingArea;
 
     fn new() -> Self {
         Self {
@@ -81,17 +90,14 @@ impl ObjectImpl for RatingLabelPriv {
     fn constructed(&self, obj: &Self::Type) {
         self.parent_constructed(obj);
 
-        let widget = self.instance();
-        widget.connect_realize(|w| {
-            let priv_ = RatingLabelPriv::from_instance(w);
-            if priv_.editable.get() {
-                if let Some(win) = w.window() {
-                    let mut mask = win.events();
-                    mask |= gdk::EventMask::BUTTON_PRESS_MASK;
-                    win.set_events(mask);
-                }
-            }
-        });
+        let click = gtk4::GestureClick::new();
+        click.connect_pressed(glib::clone!(@weak obj => move |gesture, n, x, y| {
+            let this = Self::from_instance(&obj);
+            this.press_event(&gesture, n, x, y);
+        }));
+        obj.add_controller(&click);
+
+        obj.set_draw_func(&RatingLabel::draw_func);
     }
 
     fn signals() -> &'static [Signal] {
@@ -170,9 +176,10 @@ impl RatingLabel {
         PIXBUFS.unstar.clone()
     }
 
-    pub fn geometry() -> (i32, i32) {
+    /// Return the geometry as (width, height)
+    pub fn geometry() -> (f32, f32) {
         let star = Self::star();
-        (star.width() * 5, star.height())
+        (star.width() as f32 * 5.0, star.height() as f32)
     }
 
     pub fn draw_rating(
@@ -180,27 +187,37 @@ impl RatingLabel {
         rating: i32,
         star: &Pixbuf,
         unstar: &Pixbuf,
-        x: f64,
-        y: f64,
+        x: f32,
+        y: f32,
     ) {
         let rating = if rating == -1 { 0 } else { rating };
 
-        let w = star.width();
-        let h = star.height();
+        let w = star.width() as f32;
+        let h = star.height() as f32;
         let mut y = y;
-        y -= h as f64;
+        y -= h;
         let mut x = x;
         for i in 1..=5 {
             if i <= rating {
-                cr.set_source_pixbuf(star, x, y);
+                cr.set_source_pixbuf(star, x.into(), y.into());
             } else {
-                cr.set_source_pixbuf(unstar, x, y);
+                cr.set_source_pixbuf(unstar, x.into(), y.into());
             }
             on_err_out!(cr.paint());
-            x += w as f64;
+            x += w;
         }
     }
 
+    fn draw_func(widget: &gtk4::DrawingArea, cr: &cairo::Context, _: i32, _: i32) {
+        let star = RatingLabel::star();
+        let x = 0_f32;
+        let y = star.height() as f32;
+        let rating = RatingLabelPriv::from_instance(widget.downcast_ref::<RatingLabel>().unwrap())
+            .rating
+            .get(); // this shouldn't fail.
+        RatingLabel::draw_rating(cr, rating, &star, &RatingLabel::unstar(), x, y);
+    }
+
     pub fn rating_value_from_hit_x(x: f64) -> i32 {
         let width: f64 = Self::star().width().into();
         (x / width).round() as i32
@@ -217,48 +234,28 @@ impl RatingLabel {
 }
 
 impl DrawingAreaImpl for RatingLabelPriv {}
-impl WidgetImpl for RatingLabelPriv {
-    fn button_press_event(&self, _widget: &RatingLabel, event: &gdk::EventButton) -> Inhibit {
-        if event.button() != 1 {
-            Inhibit(false)
-        } else {
-            if let Some((x, _)) = event.coords() {
-                let new_rating = RatingLabel::rating_value_from_hit_x(x);
-                if new_rating != self.rating.get() {
-                    self.set_rating(new_rating);
-                    self
-                        .instance()
-                        .emit_by_name::<()>("rating-changed", &[&new_rating]);
-                }
-            }
-            Inhibit(true)
-        }
-    }
-
-    fn draw(&self, _widget: &RatingLabel, cr: &cairo::Context) -> Inhibit {
-        let star = RatingLabel::star();
-        let x = 0_f64;
-        let y = star.height() as f64;
-        RatingLabel::draw_rating(cr, self.rating.get(), &star, &RatingLabel::unstar(), x, y);
-
-        Inhibit(true)
-    }
-
-    fn preferred_width(&self, _widget: &RatingLabel) -> (i32, i32) {
-        let w = RatingLabel::star().width() * 5;
-        (w, w)
-    }
 
-    fn preferred_height(&self, _widget: &RatingLabel) -> (i32, i32) {
-        let h = RatingLabel::star().height();
-        (h, h)
+impl WidgetImpl for RatingLabelPriv {
+    fn measure(
+        &self,
+        _widget: &Self::Type,
+        orientation: gtk4::Orientation,
+        _for_size: i32,
+    ) -> (i32, i32, i32, i32) {
+        let m = match orientation {
+            gtk4::Orientation::Horizontal => RatingLabel::star().width() * 5,
+            gtk4::Orientation::Vertical => RatingLabel::star().height(),
+            _ => -1,
+        };
+
+        (m, m, -1, -1)
     }
 }
 
 #[no_mangle]
-pub extern "C" fn fwk_rating_label_new(rating: c_int, editable: bool) -> *mut gtk_sys::GtkWidget {
+pub extern "C" fn fwk_rating_label_new(rating: c_int, editable: bool) -> *mut gtk4_sys::GtkWidget {
     RatingLabel::new(rating, editable)
-        .upcast::<gtk::Widget>()
+        .upcast::<gtk4::Widget>()
         .to_glib_full()
 }
 
@@ -268,10 +265,10 @@ pub extern "C" fn fwk_rating_label_new(rating: c_int, editable: bool) -> *mut gt
 /// Dereference the widget pointer.
 #[no_mangle]
 pub unsafe extern "C" fn fwk_rating_label_set_rating(
-    widget: *mut gtk_sys::GtkDrawingArea,
+    widget: *mut gtk4_sys::GtkDrawingArea,
     rating: i32,
 ) {
-    let rating_label = gtk::DrawingArea::from_glib_none(widget)
+    let rating_label = gtk4::DrawingArea::from_glib_none(widget)
         .downcast::<RatingLabel>()
         .expect("Not a RatingLabel widget");
     rating_label.set_rating(rating);
diff --git a/crates/npc-fwk/src/utils/exiv2.rs b/crates/npc-fwk/src/utils/exiv2.rs
index c2c8afa..7a3f3f1 100644
--- a/crates/npc-fwk/src/utils/exiv2.rs
+++ b/crates/npc-fwk/src/utils/exiv2.rs
@@ -53,7 +53,7 @@ enum Converted {
 #[derive(Clone)]
 struct XmpPropDesc(&'static str, &'static str, Conversion);
 
-lazy_static! {
+lazy_static::lazy_static! {
     static ref EXIV2_TO_XMP: MultiMap<&'static str, XmpPropDesc> = {
         [
             (
diff --git a/crates/npc-fwk/src/utils/files.rs b/crates/npc-fwk/src/utils/files.rs
index 5a42702..acd5858 100644
--- a/crates/npc-fwk/src/utils/files.rs
+++ b/crates/npc-fwk/src/utils/files.rs
@@ -44,9 +44,11 @@ impl FileList {
             return l;
         }
         let dir_path = dir_path.unwrap();
-        if let Ok(enumerator) =
-            dir.enumerate_children("*", gio::FileQueryInfoFlags::NONE, Option::<&gio::Cancellable>::None)
-        {
+        if let Ok(enumerator) = dir.enumerate_children(
+            "*",
+            gio::FileQueryInfoFlags::NONE,
+            Option::<&gio::Cancellable>::None,
+        ) {
             for itr in enumerator.into_iter() {
                 if itr.is_err() {
                     err_out!("Enumeration failed: {:?}", itr.err());
diff --git a/niepce-main/Cargo.toml b/niepce-main/Cargo.toml
index 0f69e6c..4ee7992 100644
--- a/niepce-main/Cargo.toml
+++ b/niepce-main/Cargo.toml
@@ -13,11 +13,12 @@ glib = "*"
 gio-sys = "*"
 gio = "*"
 cairo-rs = "*"
-gdk = "*"
+gdk4 = "*"
 gdk-pixbuf = "*"
 gdk-pixbuf-sys = "*"
-gtk-sys = { version = "*", features = ["v3_22"] }
-gtk = "*"
+graphene-rs = "0.15.1"
+gtk4-sys = "*"
+gtk4 = "*"
 libc = "0.2.39"
 #gphoto = "0.1.1"
 
diff --git a/niepce-main/src/lib.rs b/niepce-main/src/lib.rs
index ccc5c10..1b0d43a 100644
--- a/niepce-main/src/lib.rs
+++ b/niepce-main/src/lib.rs
@@ -17,25 +17,6 @@
  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
  */
 
-extern crate cairo;
-extern crate gdk;
-extern crate gdk_pixbuf;
-extern crate gdk_pixbuf_sys;
-extern crate gettextrs;
-extern crate gio;
-extern crate gio_sys;
-#[macro_use]
-extern crate glib;
-extern crate gtk;
-extern crate gtk_sys;
-extern crate libc;
-extern crate once_cell;
-
-// internal crates
-#[macro_use]
-extern crate npc_fwk;
-extern crate npc_engine;
-
 pub mod libraryclient;
 pub mod niepce;
 
@@ -47,7 +28,7 @@ pub extern "C" fn niepce_init() {
     static START: Once = Once::new();
 
     START.call_once(|| {
-        gtk::init().unwrap();
+        gtk4::init().unwrap();
         npc_fwk::init();
     });
 }
diff --git a/niepce-main/src/libraryclient.rs b/niepce-main/src/libraryclient.rs
index b0e28fe..e265e42 100644
--- a/niepce-main/src/libraryclient.rs
+++ b/niepce-main/src/libraryclient.rs
@@ -1,7 +1,7 @@
 /*
  * niepce - libraryclient/mod.rs
  *
- * Copyright (C) 2017-2021 Hubert Figuière
+ * Copyright (C) 2017-2022 Hubert Figuière
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -53,9 +53,15 @@ impl LibraryClientWrapper {
         }
     }
 
+    #[inline]
+    pub fn client(&self) -> Arc<LibraryClient> {
+        self.client.clone()
+    }
+
     /// unwrap the mutable client Arc
     /// XXX we need to unsure this is thread safe.
     /// Don't hold this reference more than you need.
+    #[inline]
     pub fn unwrap_mut(&mut self) -> &mut LibraryClient {
         Arc::get_mut(&mut self.client).unwrap()
     }
diff --git a/niepce-main/src/libraryclient/clientimpl.rs b/niepce-main/src/libraryclient/clientimpl.rs
index c24104c..a8ca9da 100644
--- a/niepce-main/src/libraryclient/clientimpl.rs
+++ b/niepce-main/src/libraryclient/clientimpl.rs
@@ -32,6 +32,7 @@ use npc_engine::library::commands;
 use npc_engine::library::notification::LibNotification;
 use npc_engine::library::op::Op;
 use npc_fwk::base::PropertyValue;
+use npc_fwk::err_out;
 
 pub struct ClientImpl {
     terminate: sync::Arc<atomic::AtomicBool>,
diff --git a/niepce-main/src/niepce/ui/dialogs/confirm.rs b/niepce-main/src/niepce/ui/dialogs/confirm.rs
index 9f981b9..8359b5c 100644
--- a/niepce-main/src/niepce/ui/dialogs/confirm.rs
+++ b/niepce-main/src/niepce/ui/dialogs/confirm.rs
@@ -1,7 +1,7 @@
 /*
  * niepce - niepce/ui/dialogs/confirm.rs
  *
- * Copyright (C) 2017-2021 Hubert Figuière
+ * Copyright (C) 2017-2022 Hubert Figuière
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -21,31 +21,35 @@ use libc::c_char;
 use std::ffi::CStr;
 
 use glib::translate::*;
-use gtk::prelude::*;
-use gtk::MessageDialog;
+use gtk4::prelude::*;
+use gtk4::MessageDialog;
 
 /// # Safety
 /// Use raw pointers.
 #[no_mangle]
 pub unsafe extern "C" fn dialog_confirm(
     message: *const c_char,
-    parent: *mut gtk_sys::GtkWindow,
+    parent: *mut gtk4_sys::GtkWindow,
 ) -> bool {
     let mut result: bool = false;
     let msg = CStr::from_ptr(message).to_string_lossy();
-    let parent = gtk::Window::from_glib_none(parent);
+    let parent = gtk4::Window::from_glib_none(parent);
     let dialog = MessageDialog::new(
         Some(&parent),
-        gtk::DialogFlags::MODAL,
-        gtk::MessageType::Question,
-        gtk::ButtonsType::YesNo,
+        gtk4::DialogFlags::MODAL,
+        gtk4::MessageType::Question,
+        gtk4::ButtonsType::YesNo,
         &*msg,
     );
 
-    if dialog.run() == gtk::ResponseType::Yes {
-        result = true;
-    }
-    dialog.destroy();
+    dialog.set_modal(true);
+    dialog.connect_response(|_, response| {
+        if response == gtk4::ResponseType::Yes {
+            // XXX fix this
+            let result = true;
+        }
+    });
+    dialog.show();
 
     result
 }
diff --git a/niepce-main/src/niepce/ui/dialogs/requestnewfolder.rs 
b/niepce-main/src/niepce/ui/dialogs/requestnewfolder.rs
index b4c4df4..1b9db1d 100644
--- a/niepce-main/src/niepce/ui/dialogs/requestnewfolder.rs
+++ b/niepce-main/src/niepce/ui/dialogs/requestnewfolder.rs
@@ -1,7 +1,7 @@
 /*
  * niepce - niepce/ui/dialogs/requestnewfolder.rs
  *
- * Copyright (C) 2017-2021 Hubert Figuière
+ * Copyright (C) 2017-2022 Hubert Figuière
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -19,8 +19,8 @@
 
 use gettextrs::gettext;
 use glib::translate::*;
-use gtk::prelude::*;
-use gtk::{Dialog, Entry, Label};
+use gtk4::prelude::*;
+use gtk4::{Dialog, Entry, Label};
 
 use crate::libraryclient::{ClientInterface, LibraryClientWrapper};
 
@@ -29,32 +29,39 @@ use crate::libraryclient::{ClientInterface, LibraryClientWrapper};
 #[no_mangle]
 pub unsafe extern "C" fn dialog_request_new_folder(
     client: &mut LibraryClientWrapper,
-    parent: *mut gtk_sys::GtkWindow,
+    parent: *mut gtk4_sys::GtkWindow,
 ) {
-    let parent = gtk::Window::from_glib_none(parent);
+    let parent = gtk4::Window::from_glib_none(parent);
     let dialog = Dialog::with_buttons(
         Some("New folder"),
         Some(&parent),
-        gtk::DialogFlags::MODAL,
+        gtk4::DialogFlags::MODAL,
         &[
-            (&gettext("OK"), gtk::ResponseType::Ok),
-            (&gettext("Cancel"), gtk::ResponseType::Cancel),
+            (&gettext("OK"), gtk4::ResponseType::Ok),
+            (&gettext("Cancel"), gtk4::ResponseType::Cancel),
         ],
     );
     let label = Label::with_mnemonic(gettext("Folder _name:").as_str());
-    dialog.content_area().pack_start(&label, true, false, 4);
+    dialog.content_area().append(&label);
     let entry = Entry::new();
     entry.set_text("foobar");
     entry.add_mnemonic_label(&label);
-    dialog.content_area().pack_end(&entry, true, false, 4);
+    dialog.content_area().append(&entry);
 
-    dialog.content_area().show_all();
-    let cancel = dialog.run() != gtk::ResponseType::Ok;
-    let folder_name = entry.text();
-    dialog.destroy();
-    if !cancel {
-        client
-            .unwrap_mut()
-            .create_folder(folder_name.to_string(), None);
-    }
+    dialog.set_modal(true);
+
+    let client = client.client();
+    dialog.connect_response(
+        glib::clone!(@strong entry, @strong client => move |_, response| {
+            let mut client = client.clone();
+            let folder_name = entry.text();
+            let cancel = response != gtk4::ResponseType::Ok;
+            if !cancel {
+                std::sync::Arc::get_mut(&mut client)
+                    .unwrap()
+                    .create_folder(folder_name.to_string(), None);
+            }
+        }),
+    );
+    dialog.show();
 }
diff --git a/niepce-main/src/niepce/ui/image_grid_view.rs b/niepce-main/src/niepce/ui/image_grid_view.rs
index b7cfb3f..401425c 100644
--- a/niepce-main/src/niepce/ui/image_grid_view.rs
+++ b/niepce-main/src/niepce/ui/image_grid_view.rs
@@ -1,7 +1,7 @@
 /*
  * niepce - niepce/ui/image_grid_view.rs
  *
- * Copyright (C) 2020-2021 Hubert Figuière
+ * Copyright (C) 2020-2022 Hubert Figuière
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -17,73 +17,77 @@
  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
  */
 
-use glib::subclass::prelude::*;
 use glib::translate::*;
-use gtk::prelude::*;
-use gtk::subclass::prelude::*;
-use gtk::subclass::widget::WidgetImplExt;
+use gtk4::prelude::*;
 
 use super::library_cell_renderer::LibraryCellRenderer;
 use npc_fwk::toolkit::clickable_cell_renderer::ClickableCellRenderer;
 
-glib::wrapper! {
-    pub struct ImageGridView(
-        ObjectSubclass<ImageGridViewPriv>)
-        @extends gtk::IconView, gtk::Container, gtk::Widget;
+pub struct ImageGridView {
+    icon_view: gtk4::IconView,
 }
 
 impl ImageGridView {
-    pub fn new(store: &gtk::TreeModel) -> Self {
-        glib::Object::new(&[("model", store)]).expect("Failed to create ImageGridView")
-    }
-}
-
-pub struct ImageGridViewPriv {}
+    pub fn new(store: &gtk4::TreeModel) -> Self {
+        let icon_view = gtk4::IconView::with_model(store);
 
-#[glib::object_subclass]
-impl ObjectSubclass for ImageGridViewPriv {
-    const NAME: &'static str = "ImageGridView";
-    type Type = ImageGridView;
-    type ParentType = gtk::IconView;
+        let click = gtk4::GestureClick::new();
+        //        click.connect_pressed(glib::clone!(@weak obj => move |gesture, n, x, y| {
+        // XXX handle press event
+        // self.press_event(x, y);
+        //        }));
+        icon_view.add_controller(&click);
 
-    fn new() -> Self {
-        Self {}
+        ImageGridView { icon_view }
     }
 }
 
-impl ObjectImpl for ImageGridViewPriv {
-    fn constructed(&self, obj: &ImageGridView) {
-        self.parent_constructed(obj);
+impl std::ops::Deref for ImageGridView {
+    type Target = gtk4::IconView;
+
+    fn deref(&self) -> &gtk4::IconView {
+        &self.icon_view
     }
 }
 
-impl WidgetImpl for ImageGridViewPriv {
-    fn button_press_event(&self, widget: &ImageGridView, event: &gdk::EventButton) -> gtk::Inhibit {
-        let r = self.parent_button_press_event(widget, event);
+impl ImageGridView {
+    fn press_event(&self, x: f64, y: f64) {
+        // let event = gesture.last_event();
 
-        if let Some((x, y)) = event.coords() {
-            if let Some((_, cell)) = widget.item_at_pos(x as i32, y as i32) {
-                if let Ok(mut cell) = cell.downcast::<LibraryCellRenderer>() {
-                    cell.hit(x as i32, y as i32);
-                }
+        // XXX forward to the icon_view or something
+        // self.parent_button_press_event(widget, event);
+
+        if let Some((_, cell)) = self.icon_view.item_at_pos(x as i32, y as i32) {
+            if let Ok(mut cell) = cell.downcast::<LibraryCellRenderer>() {
+                cell.hit(x as i32, y as i32);
             }
         }
-
-        r
     }
 }
 
-impl ContainerImpl for ImageGridViewPriv {}
-
-impl IconViewImpl for ImageGridViewPriv {}
-
 /// # Safety
 /// Use raw pointers.
 #[no_mangle]
 pub unsafe extern "C" fn npc_image_grid_view_new(
-    store: *mut gtk_sys::GtkTreeModel,
-) -> *mut gtk_sys::GtkWidget {
-    ImageGridView::new(&gtk::TreeModel::from_glib_full(store))
-        .upcast::<gtk::Widget>()
-        .to_glib_full()
+    store: *mut gtk4_sys::GtkTreeModel,
+) -> *mut ImageGridView {
+    Box::into_raw(Box::new(ImageGridView::new(
+        &gtk4::TreeModel::from_glib_full(store),
+    )))
+}
+
+/// # Safety
+/// Use raw pointers
+#[no_mangle]
+pub unsafe extern "C" fn npc_image_grid_view_get_icon_view(
+    view: &ImageGridView,
+) -> *mut gtk4_sys::GtkIconView {
+    view.icon_view.to_glib_none().0
+}
+
+/// # Safety
+/// Use raw pointers
+#[no_mangle]
+pub unsafe extern "C" fn npc_image_grid_view_release(view: *mut ImageGridView) {
+    Box::from_raw(view);
 }
diff --git a/niepce-main/src/niepce/ui/image_list_store.rs b/niepce-main/src/niepce/ui/image_list_store.rs
index c5f0376..86c23cb 100644
--- a/niepce-main/src/niepce/ui/image_list_store.rs
+++ b/niepce-main/src/niepce/ui/image_list_store.rs
@@ -21,7 +21,7 @@ use std::collections::BTreeMap;
 use std::ptr;
 
 use glib::translate::*;
-use gtk::prelude::*;
+use gtk4::prelude::*;
 
 use once_cell::unsync::OnceCell;
 
@@ -33,6 +33,7 @@ use npc_engine::library::notification::{LibNotification, MetadataChange};
 use npc_engine::library::thumbnail_cache::ThumbnailCache;
 use npc_fwk::toolkit::gdk_utils;
 use npc_fwk::PropertyValue;
+use npc_fwk::{dbg_out, err_out};
 
 /// Wrap a libfile into something that can be in a glib::Value
 #[derive(Clone, glib::Boxed)]
@@ -50,11 +51,11 @@ pub enum ColIndex {
 /// The Image list store.
 /// It wraps the tree model/store.
 pub struct ImageListStore {
-    store: gtk::ListStore,
+    store: gtk4::ListStore,
     current_folder: LibraryId,
     current_keyword: LibraryId,
-    idmap: BTreeMap<LibraryId, gtk::TreeIter>,
-    image_loading_icon: OnceCell<Option<gdk_pixbuf::Pixbuf>>,
+    idmap: BTreeMap<LibraryId, gtk4::TreeIter>,
+    image_loading_icon: OnceCell<gtk4::IconPaintable>,
 }
 
 impl Default for ImageListStore {
@@ -66,13 +67,13 @@ impl Default for ImageListStore {
 impl ImageListStore {
     pub fn new() -> Self {
         let col_types: [glib::Type; 4] = [
-            gdk_pixbuf::Pixbuf::static_type(),
+            gdk4::Paintable::static_type(),
             StoreLibFile::static_type(),
-            gdk_pixbuf::Pixbuf::static_type(),
+            gdk4::Paintable::static_type(),
             glib::Type::I32,
         ];
 
-        let store = gtk::ListStore::new(&col_types);
+        let store = gtk4::ListStore::new(&col_types);
 
         Self {
             store,
@@ -83,22 +84,17 @@ impl ImageListStore {
         }
     }
 
-    fn get_loading_icon(&self) -> Option<&gdk_pixbuf::Pixbuf> {
-        self.image_loading_icon
-            .get_or_init(|| {
-                if let Some(theme) = gtk::IconTheme::default() {
-                    if let Ok(icon) =
-                        theme.load_icon("image-loading", 32, gtk::IconLookupFlags::USE_BUILTIN)
-                    {
-                        icon
-                    } else {
-                        None
-                    }
-                } else {
-                    None
-                }
-            })
-            .as_ref()
+    fn get_loading_icon(&self) -> &gtk4::IconPaintable {
+        self.image_loading_icon.get_or_init(|| {
+            gtk4::IconTheme::default().lookup_icon(
+                "image-loading",
+                &[],
+                32,
+                1,
+                gtk4::TextDirection::None,
+                gtk4::IconLookupFlags::empty(),
+            )
+        })
     }
 
     fn is_property_interesting(idx: Np) -> bool {
@@ -108,7 +104,7 @@ impl ImageListStore {
             || (idx == Np::Index(NpNiepceFlagProp))
     }
 
-    fn get_iter_from_id(&self, id: LibraryId) -> Option<&gtk::TreeIter> {
+    fn get_iter_from_id(&self, id: LibraryId) -> Option<&gtk4::TreeIter> {
         self.idmap.get(&id)
     }
 
@@ -119,11 +115,12 @@ impl ImageListStore {
     }
 
     fn add_libfile(&mut self, f: &LibFile) {
-        let icon = self.get_loading_icon().cloned();
+        let icon = self.get_loading_icon().clone();
+        let thumb_icon = icon.clone();
         let iter = self.add_row(
-            icon.as_ref(),
+            Some(icon.upcast()),
             f,
-            gdk_utils::gdkpixbuf_scale_to_fit(icon.as_ref(), 100).as_ref(),
+            Some(thumb_icon.upcast()),
             FileStatus::Ok,
         );
         self.idmap.insert(f.id(), iter);
@@ -220,11 +217,11 @@ impl ImageListStore {
         }
     }
 
-    pub fn get_file_id_at_path(&self, path: &gtk::TreePath) -> LibraryId {
+    pub fn get_file_id_at_path(&self, path: &gtk4::TreePath) -> LibraryId {
         if let Some(iter) = self.store.iter(&path) {
             if let Ok(libfile) = self
                 .store
-                .value(&iter, ColIndex::File as i32)
+                .get_value(&iter, ColIndex::File as i32)
                 .get::<&StoreLibFile>()
             {
                 return libfile.0.id();
@@ -236,7 +233,7 @@ impl ImageListStore {
     pub fn get_file(&self, id: LibraryId) -> Option<LibFile> {
         if let Some(iter) = self.idmap.get(&id) {
             self.store
-                .value(&iter, ColIndex::File as i32)
+                .get_value(&iter, ColIndex::File as i32)
                 .get::<&StoreLibFile>()
                 .map(|v| v.0.clone())
                 .ok()
@@ -247,11 +244,11 @@ impl ImageListStore {
 
     pub fn add_row(
         &mut self,
-        thumb: Option<&gdk_pixbuf::Pixbuf>,
+        thumb: Option<gdk4::Paintable>,
         file: &LibFile,
-        strip_thumb: Option<&gdk_pixbuf::Pixbuf>,
+        strip_thumb: Option<gdk4::Paintable>,
         status: FileStatus,
-    ) -> gtk::TreeIter {
+    ) -> gtk4::TreeIter {
         let iter = self.store.append();
         let store_libfile = StoreLibFile(file.clone());
         self.store.set(
@@ -268,7 +265,8 @@ impl ImageListStore {
 
     pub fn set_thumbnail(&mut self, id: LibraryId, thumb: &gdk_pixbuf::Pixbuf) {
         if let Some(iter) = self.idmap.get(&id) {
-            let strip_thumb = gdk_utils::gdkpixbuf_scale_to_fit(Some(thumb), 100);
+            let strip_thumb = gdk_utils::gdkpixbuf_scale_to_fit(Some(thumb), 100)
+                .map(|pix| gdk4::Texture::for_pixbuf(&pix));
             assert!(thumb.ref_count() > 0);
             self.store.set(
                 iter,
@@ -280,10 +278,10 @@ impl ImageListStore {
         }
     }
 
-    pub fn set_property(&self, iter: &gtk::TreeIter, change: &MetadataChange) {
+    pub fn set_property(&self, iter: &gtk4::TreeIter, change: &MetadataChange) {
         if let Ok(libfile) = self
             .store
-            .value(&iter, ColIndex::File as i32)
+            .get_value(&iter, ColIndex::File as i32)
             .get::<&StoreLibFile>()
         {
             assert!(libfile.0.id() == change.id);
@@ -316,7 +314,7 @@ pub unsafe extern "C" fn npc_image_list_store_delete(self_: *mut ImageListStore)
 
 /// Return the gobj for the GtkListStore. You must ref it to hold it.
 #[no_mangle]
-pub extern "C" fn npc_image_list_store_gobj(self_: &ImageListStore) -> *mut gtk_sys::GtkListStore {
+pub extern "C" fn npc_image_list_store_gobj(self_: &ImageListStore) -> *mut gtk4_sys::GtkListStore {
     self_.store.to_glib_none().0
 }
 
@@ -327,7 +325,7 @@ pub extern "C" fn npc_image_list_store_gobj(self_: &ImageListStore) -> *mut gtk_
 #[no_mangle]
 pub unsafe extern "C" fn npc_image_list_store_get_file_id_at_path(
     self_: &ImageListStore,
-    path: *const gtk_sys::GtkTreePath,
+    path: *const gtk4_sys::GtkTreePath,
 ) -> LibraryId {
     assert!(!path.is_null());
     self_.get_file_id_at_path(&from_glib_borrow(path))
@@ -342,11 +340,18 @@ pub unsafe extern "C" fn npc_image_list_store_add_row(
     file: *const LibFile,
     strip_thumb: *mut gdk_pixbuf_sys::GdkPixbuf,
     status: FileStatus,
-) -> gtk_sys::GtkTreeIter {
+) -> gtk4_sys::GtkTreeIter {
     let thumb: Option<gdk_pixbuf::Pixbuf> = from_glib_none(thumb);
     let strip_thumb: Option<gdk_pixbuf::Pixbuf> = from_glib_none(strip_thumb);
+    let thumb = thumb.as_ref().map(gdk4::Texture::for_pixbuf);
+    let strip_thumb = strip_thumb.as_ref().map(gdk4::Texture::for_pixbuf);
     *self_
-        .add_row(thumb.as_ref(), &*file, strip_thumb.as_ref(), status)
+        .add_row(
+            thumb.map(|t| t.upcast()),
+            &*file,
+            strip_thumb.map(|t| t.upcast()),
+            status,
+        )
         .to_glib_none()
         .0
 }
@@ -355,7 +360,7 @@ pub unsafe extern "C" fn npc_image_list_store_add_row(
 pub extern "C" fn npc_image_list_store_get_iter_from_id(
     self_: &mut ImageListStore,
     id: LibraryId,
-) -> *const gtk_sys::GtkTreeIter {
+) -> *const gtk4_sys::GtkTreeIter {
     self_.idmap.get(&id).to_glib_none().0
 }
 
diff --git a/niepce-main/src/niepce/ui/imagetoolbar.rs b/niepce-main/src/niepce/ui/imagetoolbar.rs
index 7bc7669..f4a3c50 100644
--- a/niepce-main/src/niepce/ui/imagetoolbar.rs
+++ b/niepce-main/src/niepce/ui/imagetoolbar.rs
@@ -1,7 +1,7 @@
 /*
  * niepce - ui/imagetoolbar.rs
  *
- * Copyright (C) 2018-2021 Hubert Figuiere
+ * Copyright (C) 2018-2022 Hubert Figuière
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -18,41 +18,31 @@
  */
 
 use glib::translate::*;
-use gtk::prelude::*;
+use gtk4::prelude::*;
 
 #[no_mangle]
-pub extern "C" fn image_toolbar_new() -> *mut gtk_sys::GtkToolbar {
-    let toolbar = gtk::Toolbar::new();
+pub extern "C" fn image_toolbar_new() -> *mut gtk4_sys::GtkBox {
+    let toolbar = gtk4::Box::new(gtk4::Orientation::Horizontal, 6);
+    // XXX set style class "toolbar"
 
-    let icon =
-        gtk::Image::from_icon_name(Some("go-previous-symbolic"), gtk::IconSize::SmallToolbar);
-    let tool_item = gtk::ToolButton::new(Some(&icon), None);
+    let tool_item = gtk4::Button::from_icon_name("go-previous-symbolic");
     tool_item.set_action_name(Some("shell.PrevImage"));
-    toolbar.add(&tool_item);
+    toolbar.append(&tool_item);
 
-    let icon = gtk::Image::from_icon_name(Some("go-next-symbolic"), gtk::IconSize::SmallToolbar);
-    let tool_item = gtk::ToolButton::new(Some(&icon), None);
+    let tool_item = gtk4::Button::from_icon_name("go-next-symbolic");
     tool_item.set_action_name(Some("shell.NextImage"));
-    toolbar.add(&tool_item);
+    toolbar.append(&tool_item);
 
-    let separator = gtk::SeparatorToolItem::new();
-    toolbar.add(&separator);
+    // let separator = gtk4::SeparatorToolItem::new();
+    // toolbar.add(&separator);
 
-    let icon = gtk::Image::from_icon_name(
-        Some("object-rotate-left-symbolic"),
-        gtk::IconSize::SmallToolbar,
-    );
-    let tool_item = gtk::ToolButton::new(Some(&icon), None);
+    let tool_item = gtk4::Button::from_icon_name("object-rotate-left-symbolic");
     tool_item.set_action_name(Some("shell.RotateLeft"));
-    toolbar.add(&tool_item);
+    toolbar.append(&tool_item);
 
-    let icon = gtk::Image::from_icon_name(
-        Some("object-rotate-right-symbolic"),
-        gtk::IconSize::SmallToolbar,
-    );
-    let tool_item = gtk::ToolButton::new(Some(&icon), None);
+    let tool_item = gtk4::Button::from_icon_name("object-rotate-right-symbolic");
     tool_item.set_action_name(Some("shell.RotateRight"));
-    toolbar.add(&tool_item);
+    toolbar.append(&tool_item);
 
     toolbar.to_glib_full()
 }
diff --git a/niepce-main/src/niepce/ui/library_cell_renderer.rs 
b/niepce-main/src/niepce/ui/library_cell_renderer.rs
index e80813c..ff35ce7 100644
--- a/niepce-main/src/niepce/ui/library_cell_renderer.rs
+++ b/niepce-main/src/niepce/ui/library_cell_renderer.rs
@@ -22,21 +22,23 @@ use once_cell::unsync::Lazy;
 use std::cell::{Cell, RefCell};
 use std::ptr;
 
-use gdk::prelude::*;
+use gdk4::prelude::*;
 use gdk_pixbuf::Pixbuf;
 use glib::subclass::prelude::*;
 use glib::subclass::Signal;
 use glib::translate::*;
-use gtk::prelude::*;
-use gtk::subclass::prelude::*;
+use graphene::Rect;
+use gtk4::prelude::*;
+use gtk4::subclass::prelude::*;
 
 use crate::niepce::ui::image_list_store::StoreLibFile;
 use npc_engine::db::libfile::{FileStatus, FileType};
 use npc_fwk::base::rgbcolour::RgbColour;
 use npc_fwk::toolkit::clickable_cell_renderer::ClickableCellRenderer;
 use npc_fwk::toolkit::widgets::rating_label::RatingLabel;
+use npc_fwk::{dbg_out, err_out, on_err_out};
 
-const CELL_PADDING: i32 = 4;
+const CELL_PADDING: f32 = 4.0;
 
 struct Emblems {
     raw: Pixbuf,
@@ -63,7 +65,7 @@ const EMBLEMS: Lazy<Emblems> = Lazy::new(|| Emblems {
 glib::wrapper! {
     pub struct LibraryCellRenderer(
         ObjectSubclass<LibraryCellRendererPriv>)
-        @extends gtk::CellRendererPixbuf, gtk::CellRenderer;
+        @extends gtk4::CellRenderer;
 }
 
 impl LibraryCellRenderer {
@@ -71,7 +73,7 @@ impl LibraryCellRenderer {
     /// callback: an optional callback used to get a colour for labels.
     /// callback_data: raw pointer passed as is to the callback.
     pub fn new(callback: Option<GetColourCallback>, callback_data: *const c_void) -> Self {
-        let obj: Self = glib::Object::new(&[("mode", &gtk::CellRendererMode::Activatable)])
+        let obj: Self = glib::Object::new(&[("mode", &gtk4::CellRendererMode::Activatable)])
             .expect("Failed to create Library Cell Renderer");
 
         if callback.is_some() {
@@ -99,6 +101,11 @@ impl LibraryCellRenderer {
 
         cell_renderer
     }
+
+    pub fn pixbuf(&self) -> Option<gdk4::Paintable> {
+        let priv_ = LibraryCellRendererPriv::from_instance(self);
+        priv_.pixbuf.borrow().clone()
+    }
 }
 
 /// Option to set for the LibraryCellRenderer
@@ -191,6 +198,7 @@ impl ClickableCellRenderer for LibraryCellRenderer {
 type GetColourCallback = unsafe extern "C" fn(i32, *mut RgbColour, *const c_void) -> bool;
 
 pub struct LibraryCellRendererPriv {
+    pixbuf: RefCell<Option<gdk4::Paintable>>,
     libfile: RefCell<Option<StoreLibFile>>,
     status: Cell<FileStatus>,
     size: Cell<i32>,
@@ -215,23 +223,29 @@ impl LibraryCellRendererPriv {
         self.libfile.replace(libfile);
     }
 
-    fn do_draw_thumbnail(&self, cr: &cairo::Context, pixbuf: &Pixbuf, r: &cairo::RectangleInt) {
-        let w = pixbuf.width();
-        let h = pixbuf.height();
-        let offset_x = (self.size.get() - w) / 2;
-        let offset_y = (self.size.get() - h) / 2;
-        let x: f64 = (r.x + self.pad.get() + offset_x).into();
-        let y: f64 = (r.y + self.pad.get() + offset_y).into();
+    fn set_pixbuf(&self, pixbuf: Option<gdk4::Paintable>) {
+        self.pixbuf.replace(pixbuf);
+    }
+
+    fn do_draw_thumbnail_frame(&self, cr: &cairo::Context, w: f32, h: f32, r: &Rect) {
+        let offset_x = (self.size.get() as f32 - w) / 2.0;
+        let offset_y = (self.size.get() as f32 - h) / 2.0;
+        let x = r.x() + self.pad.get() as f32 + offset_x;
+        let y = r.y() + self.pad.get() as f32 + offset_y;
 
         cr.set_source_rgb(1.0, 1.0, 1.0);
-        cr.rectangle(x, y, w.into(), h.into());
+        cr.rectangle(x.into(), y.into(), w.into(), h.into());
         on_err_out!(cr.stroke());
+    }
 
-        cr.set_source_pixbuf(&pixbuf, x, y);
-        on_err_out!(cr.paint());
+    fn do_draw_thumbnail(&self, snapshot: &gtk4::Snapshot, pixbuf: &gdk4::Paintable) {
+        let w = pixbuf.intrinsic_width() as f64;
+        let h = pixbuf.intrinsic_height() as f64;
+
+        pixbuf.snapshot(snapshot.upcast_ref::<gdk4::Snapshot>(), w, h);
     }
 
-    fn do_draw_flag(cr: &cairo::Context, flag: i32, r: &cairo::RectangleInt) {
+    fn do_draw_flag(cr: &cairo::Context, flag: i32, r: &Rect) {
         if flag == 0 {
             return;
         }
@@ -241,45 +255,50 @@ impl LibraryCellRendererPriv {
             _ => return,
         };
 
-        let w = pixbuf.width();
-        let x: f64 = (r.x + r.width - CELL_PADDING - w).into();
-        let y: f64 = (r.y + CELL_PADDING).into();
-        cr.set_source_pixbuf(&pixbuf, x, y);
+        let w = pixbuf.width() as f32;
+        let x = r.x() + r.width() - CELL_PADDING - w;
+        let y = r.y() + CELL_PADDING;
+        cr.set_source_pixbuf(&pixbuf, x.into(), y.into());
         on_err_out!(cr.paint());
     }
 
-    fn do_draw_status(cr: &cairo::Context, status: FileStatus, r: &cairo::RectangleInt) {
+    fn do_draw_status(cr: &cairo::Context, status: FileStatus, r: &Rect) {
         if status == FileStatus::Ok {
             return;
         }
-        let x: f64 = (r.x + CELL_PADDING).into();
-        let y: f64 = (r.y + CELL_PADDING).into();
-        cr.set_source_pixbuf(&EMBLEMS.status_missing, x, y);
+        let x = r.x() + CELL_PADDING;
+        let y = r.y() + CELL_PADDING;
+        cr.set_source_pixbuf(&EMBLEMS.status_missing, x.into(), y.into());
         on_err_out!(cr.paint());
     }
 
-    fn do_draw_format_emblem(cr: &cairo::Context, emblem: &Pixbuf, r: &cairo::RectangleInt) -> i32 {
-        let w = emblem.width();
-        let h = emblem.height();
+    fn do_draw_format_emblem(cr: &cairo::Context, emblem: &Pixbuf, r: &Rect) -> f32 {
+        let w = emblem.width() as f32;
+        let h = emblem.height() as f32;
         let left = CELL_PADDING + w;
-        let x: f64 = (r.x + r.width - left).into();
-        let y: f64 = (r.y + r.height - CELL_PADDING - h).into();
-        cr.set_source_pixbuf(emblem, x, y);
+        let x = r.x() + r.width() - left;
+        let y = r.y() + r.height() - CELL_PADDING - h;
+        cr.set_source_pixbuf(emblem, x.into(), y.into());
         on_err_out!(cr.paint());
         left
     }
 
-    fn do_draw_label(cr: &cairo::Context, right: i32, colour: RgbColour, r: &cairo::RectangleInt) {
-        const LABEL_SIZE: i32 = 15;
-        let x: f64 = (r.x + r.width - CELL_PADDING - right - CELL_PADDING - LABEL_SIZE).into();
-        let y: f64 = (r.y + r.height - CELL_PADDING - LABEL_SIZE).into();
+    fn do_draw_label(cr: &cairo::Context, right: f32, colour: RgbColour, r: &Rect) {
+        const LABEL_SIZE: f32 = 15.0;
+        let x = r.x() + r.width() - CELL_PADDING - right - CELL_PADDING - LABEL_SIZE;
+        let y = r.y() + r.height() - CELL_PADDING - LABEL_SIZE;
 
-        cr.rectangle(x, y, LABEL_SIZE.into(), LABEL_SIZE.into());
+        cr.rectangle(x.into(), y.into(), LABEL_SIZE.into(), LABEL_SIZE.into());
         cr.set_source_rgb(1.0, 1.0, 1.0);
         on_err_out!(cr.stroke());
-        cr.rectangle(x, y, LABEL_SIZE.into(), LABEL_SIZE.into());
-        let rgb: gdk::RGBA = colour.into();
-        cr.set_source_rgba(rgb.red(), rgb.green(), rgb.blue(), rgb.alpha());
+        cr.rectangle(x.into(), y.into(), LABEL_SIZE.into(), LABEL_SIZE.into());
+        let rgb: gdk4::RGBA = colour.into();
+        cr.set_source_rgba(
+            rgb.red().into(),
+            rgb.green().into(),
+            rgb.blue().into(),
+            rgb.alpha().into(),
+        );
         on_err_out!(cr.fill());
     }
 
@@ -300,10 +319,11 @@ impl LibraryCellRendererPriv {
 impl ObjectSubclass for LibraryCellRendererPriv {
     const NAME: &'static str = "LibraryCellRenderer";
     type Type = LibraryCellRenderer;
-    type ParentType = gtk::CellRendererPixbuf;
+    type ParentType = gtk4::CellRenderer;
 
     fn new() -> Self {
         Self {
+            pixbuf: RefCell::new(None),
             libfile: RefCell::new(None),
             status: Cell::new(FileStatus::Ok),
             size: Cell::new(160),
@@ -330,6 +350,13 @@ impl ObjectImpl for LibraryCellRendererPriv {
         use once_cell::sync::Lazy;
         static PROPERTIES: Lazy<Vec<glib::ParamSpec>> = Lazy::new(|| {
             vec![
+                glib::ParamSpecObject::new(
+                    "pixbuf",
+                    "Thumbnail",
+                    "Thumbnail to Display",
+                    gdk4::Paintable::static_type(),
+                    glib::ParamFlags::READWRITE,
+                ),
                 glib::ParamSpecBoxed::new(
                     "libfile",
                     "Library File",
@@ -375,6 +402,10 @@ impl ObjectImpl for LibraryCellRendererPriv {
         pspec: &glib::ParamSpec,
     ) {
         match pspec.name() {
+            "pixbuf" => {
+                let pixbuf = value.get::<&gdk4::Paintable>().map(|f| f.clone()).ok();
+                self.set_pixbuf(pixbuf);
+            }
             "libfile" => {
                 let libfile = value.get::<&StoreLibFile>().map(|f| f.clone()).ok();
                 self.set_libfile(libfile);
@@ -396,6 +427,7 @@ impl ObjectImpl for LibraryCellRendererPriv {
         pspec: &glib::ParamSpec,
     ) -> glib::Value {
         match pspec.name() {
+            "pixbuf" => self.pixbuf.borrow().to_value(),
             "libfile" => self.libfile.borrow().to_value(),
             "status" => (self.status.get() as i32).to_value(),
             _ => unimplemented!(),
@@ -403,10 +435,8 @@ impl ObjectImpl for LibraryCellRendererPriv {
     }
 }
 
-impl CellRendererPixbufImpl for LibraryCellRendererPriv {}
-
 impl CellRendererImpl for LibraryCellRendererPriv {
-    fn preferred_width<P: IsA<gtk::Widget>>(
+    fn preferred_width<P: IsA<gtk4::Widget>>(
         &self,
         _renderer: &LibraryCellRenderer,
         _widget: &P,
@@ -415,7 +445,7 @@ impl CellRendererImpl for LibraryCellRendererPriv {
         (maxdim, maxdim)
     }
 
-    fn preferred_height<P: IsA<gtk::Widget>>(
+    fn preferred_height<P: IsA<gtk4::Widget>>(
         &self,
         _renderer: &LibraryCellRenderer,
         _widget: &P,
@@ -424,66 +454,79 @@ impl CellRendererImpl for LibraryCellRendererPriv {
         (maxdim, maxdim)
     }
 
-    fn render<P: IsA<gtk::Widget>>(
+    fn snapshot<P: IsA<gtk4::Widget>>(
         &self,
-        _renderer: &LibraryCellRenderer,
-        cr: &cairo::Context,
+        _renderer: &Self::Type,
+        snapshot: &gtk4::Snapshot,
         widget: &P,
-        _background_area: &gdk::Rectangle,
-        cell_area: &gdk::Rectangle,
-        flags: gtk::CellRendererState,
+        _background_area: &gdk4::Rectangle,
+        cell_area: &gdk4::Rectangle,
+        flags: gtk4::CellRendererState,
     ) {
         let self_ = self.instance();
-        let xpad = self_.xpad();
-        let ypad = self_.ypad();
+        let xpad = self_.xpad() as f32;
+        let ypad = self_.ypad() as f32;
+
+        let mut r = Rect::new(
+            cell_area.x() as f32,
+            cell_area.y() as f32,
+            cell_area.width() as f32,
+            cell_area.height() as f32,
+        );
+        let cr = snapshot.append_cairo(&r);
 
-        let mut r = *cell_area.as_ref();
-        r.x += xpad as i32;
-        r.y += ypad as i32;
+        r.offset(xpad, ypad);
 
         let file = self.libfile.borrow();
 
         let style_context = widget.style_context();
 
         style_context.save();
-        style_context.set_state(if flags.contains(gtk::CellRendererState::SELECTED) {
-            gtk::StateFlags::SELECTED
+        style_context.set_state(if flags.contains(gtk4::CellRendererState::SELECTED) {
+            gtk4::StateFlags::SELECTED
         } else {
-            gtk::StateFlags::NORMAL
+            gtk4::StateFlags::NORMAL
         });
-        gtk::render_background(
+
+        gtk4::render_background(
             &style_context,
-            cr,
-            (r.x).into(),
-            (r.y).into(),
-            (r.width).into(),
-            (r.height).into(),
+            &cr,
+            r.x().into(),
+            r.y().into(),
+            r.width().into(),
+            r.height().into(),
         );
 
         if self.drawborder.get() {
-            gtk::render_frame(
+            gtk4::render_frame(
                 &style_context,
-                cr,
-                (r.x).into(),
-                (r.y).into(),
-                (r.width).into(),
-                (r.height).into(),
+                &cr,
+                r.x().into(),
+                r.y().into(),
+                r.width().into(),
+                r.height().into(),
             );
         }
         style_context.restore();
 
         if let Some(pixbuf) = self_.pixbuf() {
-            self.do_draw_thumbnail(cr, &pixbuf, &r);
+            self.do_draw_thumbnail_frame(
+                &cr,
+                pixbuf.intrinsic_width() as f32,
+                pixbuf.intrinsic_height() as f32,
+                &r,
+            );
+            self.do_draw_thumbnail(snapshot, &pixbuf);
         }
         if self.draw_rating.get() {
             let rating = match &*file {
                 Some(f) => f.0.rating(),
                 None => 0,
             };
-            let x: f64 = (r.x + CELL_PADDING).into();
-            let y: f64 = (r.y + r.height - CELL_PADDING).into();
+            let x = r.x() + CELL_PADDING;
+            let y = r.y() + r.height() - CELL_PADDING;
             RatingLabel::draw_rating(
-                cr,
+                &cr,
                 rating,
                 &RatingLabel::star(),
                 &RatingLabel::unstar(),
@@ -493,14 +536,14 @@ impl CellRendererImpl for LibraryCellRendererPriv {
         }
         if self.draw_flag.get() {
             match &*file {
-                Some(f) => Self::do_draw_flag(cr, f.0.flag(), &r),
+                Some(f) => Self::do_draw_flag(&cr, f.0.flag(), &r),
                 None => {}
             }
         }
 
         let status = self.status.get();
         if self.draw_status.get() && status != FileStatus::Ok {
-            Self::do_draw_status(cr, status, &r);
+            Self::do_draw_status(&cr, status, &r);
         }
 
         if self.draw_emblem.get() {
@@ -515,7 +558,7 @@ impl CellRendererImpl for LibraryCellRendererPriv {
                 FileType::Video => EMBLEMS.video.clone(),
                 FileType::Unknown => EMBLEMS.unknown.clone(),
             };
-            let left = Self::do_draw_format_emblem(cr, &emblem, &r);
+            let left = Self::do_draw_format_emblem(&cr, &emblem, &r);
 
             if self.draw_label.get() {
                 let label_id = match &*file {
@@ -524,22 +567,22 @@ impl CellRendererImpl for LibraryCellRendererPriv {
                 };
                 if label_id != 0 {
                     if let Some(colour) = self.get_colour(label_id) {
-                        Self::do_draw_label(cr, left, colour, &r);
+                        Self::do_draw_label(&cr, left, colour, &r);
                     }
                 }
             }
         }
     }
 
-    fn activate<P: IsA<gtk::Widget>>(
+    fn activate<P: IsA<gtk4::Widget>>(
         &self,
         _renderer: &LibraryCellRenderer,
-        _event: Option<&gdk::Event>,
+        _event: Option<&gdk4::Event>,
         _widget: &P,
         _path: &str,
-        _background_area: &gdk::Rectangle,
-        cell_area: &gdk::Rectangle,
-        _flags: gtk::CellRendererState,
+        _background_area: &gdk4::Rectangle,
+        cell_area: &gdk4::Rectangle,
+        _flags: gtk4::CellRendererState,
     ) -> bool {
         let mut instance = self.instance().downcast::<LibraryCellRenderer>().unwrap();
 
@@ -547,21 +590,25 @@ impl CellRendererImpl for LibraryCellRendererPriv {
             instance.reset_hit();
 
             // hit test with the rating region
-            let xpad = instance.xpad();
-            let ypad = instance.ypad();
-            let mut r = *cell_area.as_ref();
-            r.x += xpad as i32;
-            r.y += ypad as i32;
+            let xpad = instance.xpad() as f32;
+            let ypad = instance.ypad() as f32;
+            let mut r = Rect::new(
+                cell_area.x() as f32,
+                cell_area.y() as f32,
+                cell_area.width() as f32,
+                cell_area.height() as f32,
+            );
+            r.offset(xpad, ypad);
 
             let (rw, rh) = RatingLabel::geometry();
-            let rect = gdk::Rectangle::new(
-                r.x + CELL_PADDING,
-                r.y + r.height - rh - CELL_PADDING,
+            let rect = Rect::new(
+                r.x() + CELL_PADDING,
+                r.y() + r.height() - rh - CELL_PADDING,
                 rw,
                 rh,
             );
-            let x = instance.x();
-            let y = instance.y();
+            let x = instance.x() as f32;
+            let y = instance.y() as f32;
             dbg_out!(
                 "r({}, {}, {}, {}) p({}, {})",
                 rect.x(),
@@ -599,7 +646,7 @@ impl CellRendererImpl for LibraryCellRendererPriv {
 }
 
 // allow subclassing this
-pub trait LibraryCellRendererImpl: CellRendererPixbufImpl + 'static {}
+pub trait LibraryCellRendererImpl: CellRendererImpl + 'static {}
 
 /// # Safety
 /// Use raw pointers
@@ -607,8 +654,8 @@ pub trait LibraryCellRendererImpl: CellRendererPixbufImpl + 'static {}
 pub unsafe extern "C" fn npc_library_cell_renderer_new(
     get_colour: Option<unsafe extern "C" fn(i32, *mut RgbColour, *const c_void) -> bool>,
     callback_data: *const c_void,
-) -> *mut gtk_sys::GtkCellRenderer {
+) -> *mut gtk4_sys::GtkCellRenderer {
     LibraryCellRenderer::new(get_colour, callback_data)
-        .upcast::<gtk::CellRenderer>()
+        .upcast::<gtk4::CellRenderer>()
         .to_glib_full()
 }
diff --git a/niepce-main/src/niepce/ui/thumb_nav.rs b/niepce-main/src/niepce/ui/thumb_nav.rs
index 89ad2b2..b390586 100644
--- a/niepce-main/src/niepce/ui/thumb_nav.rs
+++ b/niepce-main/src/niepce/ui/thumb_nav.rs
@@ -24,8 +24,10 @@ use once_cell::unsync::OnceCell;
 
 use glib::subclass::prelude::*;
 use glib::translate::*;
-use gtk::prelude::*;
-use gtk::subclass::prelude::*;
+use gtk4::prelude::*;
+use gtk4::subclass::prelude::*;
+
+use npc_fwk::err_out;
 
 const SCROLL_INC: f64 = 1.;
 const SCROLL_MOVE: f64 = 20.;
@@ -68,11 +70,11 @@ impl From<i32> for ThumbNavMode {
 glib::wrapper! {
     pub struct ThumbNav(
         ObjectSubclass<ThumbNavPriv>)
-        @extends gtk::Box, gtk::Container, gtk::Widget;
+        @extends gtk4::Box, gtk4::Widget;
 }
 
 impl ThumbNav {
-    pub fn new(thumbview: &gtk::IconView, mode: ThumbNavMode, show_buttons: bool) -> Self {
+    pub fn new(thumbview: &gtk4::IconView, mode: ThumbNavMode, show_buttons: bool) -> Self {
         let mode_n: i32 = mode.into();
         glib::Object::new(&[
             ("mode", &mode_n),
@@ -86,9 +88,9 @@ impl ThumbNav {
 }
 
 struct ThumbNavWidgets {
-    button_left: gtk::Button,
-    button_right: gtk::Button,
-    sw: gtk::ScrolledWindow,
+    button_left: gtk4::Button,
+    button_right: gtk4::Button,
+    sw: gtk4::ScrolledWindow,
 }
 
 pub struct ThumbNavPriv {
@@ -98,7 +100,7 @@ pub struct ThumbNavPriv {
     left_i: Cell<f64>,
     right_i: Cell<f64>,
     widgets: OnceCell<ThumbNavWidgets>,
-    thumbview: RefCell<Option<gtk::IconView>>,
+    thumbview: RefCell<Option<gtk4::IconView>>,
 }
 
 pub trait ThumbNavExt {
@@ -151,7 +153,7 @@ impl ThumbNavPriv {
         }
     }
 
-    fn adj_changed(&self, adj: &gtk::Adjustment) {
+    fn adj_changed(&self, adj: &gtk4::Adjustment) {
         if let Some(widgets) = self.widgets.get() {
             let upper = adj.upper();
             let page_size = adj.page_size();
@@ -159,7 +161,7 @@ impl ThumbNavPriv {
         }
     }
 
-    fn adj_value_changed(&self, adj: &gtk::Adjustment) {
+    fn adj_value_changed(&self, adj: &gtk4::Adjustment) {
         let upper = adj.upper();
         let page_size = adj.page_size();
         let value = adj.value();
@@ -170,7 +172,7 @@ impl ThumbNavPriv {
         }
     }
 
-    fn scroll_left(ref_i: &Cell<f64>, adj: &gtk::Adjustment) -> glib::Continue {
+    fn scroll_left(ref_i: &Cell<f64>, adj: &gtk4::Adjustment) -> glib::Continue {
         let value = adj.value();
         let i = ref_i.get();
 
@@ -189,7 +191,7 @@ impl ThumbNavPriv {
         Continue(true)
     }
 
-    fn scroll_right(ref_i: &Cell<f64>, adj: &gtk::Adjustment) -> glib::Continue {
+    fn scroll_right(ref_i: &Cell<f64>, adj: &gtk4::Adjustment) -> glib::Continue {
         let upper = adj.upper();
         let page_size = adj.page_size();
         let value = adj.value();
@@ -213,8 +215,8 @@ impl ThumbNavPriv {
 
         let widgets = &self.widgets.get().unwrap();
         if show_buttons && self.mode.get() == ThumbNavMode::OneRow {
-            widgets.button_left.show_all();
-            widgets.button_right.show_all();
+            widgets.button_left.show();
+            widgets.button_right.show();
         } else {
             widgets.button_left.hide();
             widgets.button_right.hide();
@@ -228,13 +230,14 @@ impl ThumbNavPriv {
             ThumbNavMode::OneRow => {
                 if let Some(thumbview) = &*self.thumbview.borrow() {
                     thumbview.set_size_request(-1, -1);
-                    thumbview.set_property("item-height", &100);
+                    // XXX property is gone, need an API
+                    // thumbview.set_property("item-height", &100);
                 }
                 self.widgets
                     .get()
                     .unwrap()
                     .sw
-                    .set_policy(gtk::PolicyType::Always, gtk::PolicyType::Never);
+                    .set_policy(gtk4::PolicyType::Always, gtk4::PolicyType::Never);
 
                 self.set_show_buttons(self.show_buttons.get());
             }
@@ -245,12 +248,13 @@ impl ThumbNavPriv {
                     thumbview.set_columns(1);
 
                     thumbview.set_size_request(-1, -1);
-                    thumbview.set_property("item-height", &-1);
+                    // XXX property is gone, need an API
+                    // thumbview.set_property("item-height", &-1);
                 }
                 if let Some(widgets) = self.widgets.get() {
                     widgets
                         .sw
-                        .set_policy(gtk::PolicyType::Never, gtk::PolicyType::Always);
+                        .set_policy(gtk4::PolicyType::Never, gtk4::PolicyType::Always);
 
                     widgets.button_left.hide();
                     widgets.button_right.hide();
@@ -261,13 +265,8 @@ impl ThumbNavPriv {
     }
 
     fn add_thumbview(&self) {
-        if let Some(ref thumbview) = &*self.thumbview.borrow() {
-            if let Some(widgets) = self.widgets.get() {
-                widgets.sw.add(thumbview);
-                widgets.sw.show_all();
-            }
-        } else {
-            err_out!("No thumbview to add");
+        if let Some(widgets) = self.widgets.get() {
+            widgets.sw.set_child((*self.thumbview.borrow()).as_ref());
         }
     }
 }
@@ -276,7 +275,7 @@ impl ThumbNavPriv {
 impl ObjectSubclass for ThumbNavPriv {
     const NAME: &'static str = "NpcThumbNav";
     type Type = ThumbNav;
-    type ParentType = gtk::Box;
+    type ParentType = gtk4::Box;
 
     fn new() -> Self {
         Self {
@@ -294,39 +293,37 @@ impl ObjectImpl for ThumbNavPriv {
     fn constructed(&self, obj: &ThumbNav) {
         self.parent_constructed(obj);
 
-        let button_left = gtk::Button::new();
-        button_left.set_relief(gtk::ReliefStyle::None);
-        let arrow = gtk::Image::from_icon_name(Some(&"pan-start-symbolic"), gtk::IconSize::Button);
-        button_left.add(&arrow);
+        let button_left = gtk4::Button::from_icon_name("pan-start-symbolic");
+        // XXX
+        // button_left.set_relief(gtk4::ReliefStyle::None);
         button_left.set_size_request(20, 0);
-        obj.pack_start(&button_left, false, false, 0);
-        button_left.connect_clicked(clone!(@weak obj => move |_| {
+        obj.append(&button_left);
+        button_left.connect_clicked(glib::clone!(@weak obj => move |_| {
             let priv_ = ThumbNavPriv::from_instance(&obj);
             priv_.left_button_clicked();
         }));
 
-        let sw = gtk::ScrolledWindow::new(Option::<&gtk::Adjustment>::None,
-                                          Option::<&gtk::Adjustment>::None);
-        sw.set_shadow_type(gtk::ShadowType::In);
-        sw.set_policy(gtk::PolicyType::Always, gtk::PolicyType::Never);
+        let sw = gtk4::ScrolledWindow::new();
+        // XXX
+        // sw.set_shadow_type(gtk4::ShadowType::In);
+        sw.set_policy(gtk4::PolicyType::Always, gtk4::PolicyType::Never);
         let adj = sw.hadjustment();
-        adj.connect_changed(clone!(@weak obj => move |adj| {
+        adj.connect_changed(glib::clone!(@weak obj => move |adj| {
             let priv_ = ThumbNavPriv::from_instance(&obj);
             priv_.adj_changed(adj);
         }));
-        adj.connect_value_changed(clone!(@weak obj => move |adj| {
+        adj.connect_value_changed(glib::clone!(@weak obj => move |adj| {
             let priv_ = ThumbNavPriv::from_instance(&obj);
             priv_.adj_value_changed(adj);
         }));
-        obj.pack_start(&sw, true, true, 0);
+        obj.append(&sw);
 
-        let button_right = gtk::Button::new();
-        button_right.set_relief(gtk::ReliefStyle::None);
-        let arrow = gtk::Image::from_icon_name(Some(&"pan-end-symbolic"), gtk::IconSize::Button);
-        button_right.add(&arrow);
+        let button_right = gtk4::Button::from_icon_name("pan-end-symbolic");
+        // XXX
+        // button_right.set_relief(gtk4::ReliefStyle::None);
         button_right.set_size_request(20, 0);
-        obj.pack_start(&button_right, false, false, 0);
-        button_right.connect_clicked(clone!(@weak obj => move |_| {
+        obj.append(&button_right);
+        button_right.connect_clicked(glib::clone!(@weak obj => move |_| {
             let priv_ = ThumbNavPriv::from_instance(&obj);
             priv_.right_button_clicked();
         }));
@@ -367,7 +364,7 @@ impl ObjectImpl for ThumbNavPriv {
                     "thumbview",
                     "Thumbnail View",
                     "The internal thumbnail viewer widget",
-                    gtk::IconView::static_type(),
+                    gtk4::IconView::static_type(),
                     glib::ParamFlags::READWRITE | glib::ParamFlags::CONSTRUCT_ONLY,
                 ),
                 glib::ParamSpecInt::new(
@@ -399,7 +396,7 @@ impl ObjectImpl for ThumbNavPriv {
                 self.set_show_buttons(show_buttons);
             }
             "thumbview" => {
-                let thumbview: Option<gtk::IconView> = value
+                let thumbview: Option<gtk4::IconView> = value
                     .get()
                     .expect("type conformity checked by `Object::set_property`");
                 self.thumbview.replace(thumbview);
@@ -430,23 +427,21 @@ impl ObjectImpl for ThumbNavPriv {
 
 impl WidgetImpl for ThumbNavPriv {}
 
-impl ContainerImpl for ThumbNavPriv {}
-
 impl BoxImpl for ThumbNavPriv {}
 
 /// # Safety
 /// Use raw pointers
 #[no_mangle]
 pub unsafe extern "C" fn npc_thumb_nav_new(
-    thumbview: *mut gtk_sys::GtkIconView,
+    thumbview: *mut gtk4_sys::GtkIconView,
     mode: ThumbNavMode,
     show_buttons: bool,
-) -> *mut gtk_sys::GtkWidget {
+) -> *mut gtk4_sys::GtkWidget {
     ThumbNav::new(
-        &gtk::IconView::from_glib_full(thumbview),
+        &gtk4::IconView::from_glib_full(thumbview),
         mode,
         show_buttons,
     )
-    .upcast::<gtk::Widget>()
+    .upcast::<gtk4::Widget>()
     .to_glib_full()
 }
diff --git a/niepce-main/src/niepce/ui/thumb_strip_view.rs b/niepce-main/src/niepce/ui/thumb_strip_view.rs
index 23fc3b0..afd8742 100644
--- a/niepce-main/src/niepce/ui/thumb_strip_view.rs
+++ b/niepce-main/src/niepce/ui/thumb_strip_view.rs
@@ -22,10 +22,8 @@ use std::rc::Rc;
 
 use once_cell::unsync::OnceCell;
 
-use glib::subclass::prelude::*;
 use glib::translate::*;
-use gtk::prelude::*;
-use gtk::subclass::prelude::*;
+use gtk4::prelude::*;
 
 use crate::niepce::ui::library_cell_renderer::LibraryCellRenderer;
 
@@ -40,26 +38,6 @@ pub enum ImageListStoreColIndex {
     FileStatusIndex = 3,
 }
 
-glib::wrapper! {
-    pub struct ThumbStripView(
-        ObjectSubclass<ThumbStripViewPriv>)
-        @extends gtk::IconView, gtk::Container, gtk::Widget;
-}
-
-impl ThumbStripView {
-    pub fn new(store: &gtk::TreeModel) -> Self {
-        glib::Object::new(&[
-            ("model", store),
-            ("item-height", &THUMB_STRIP_VIEW_DEFAULT_ITEM_HEIGHT),
-            ("selection-mode", &gtk::SelectionMode::Multiple),
-            ("column-spacing", &THUMB_STRIP_VIEW_SPACING),
-            ("row-spacing", &THUMB_STRIP_VIEW_SPACING),
-            ("margin", &0),
-        ])
-        .expect("Failed to create ThumbStripView")
-    }
-}
-
 #[derive(Default)]
 struct Signals {
     model_add: Option<glib::SignalHandlerId>,
@@ -75,12 +53,12 @@ impl ItemCount {
         self.count.set(count);
     }
 
-    fn row_added(&self, view: &ThumbStripView) {
+    fn row_added(&self, view: &gtk4::IconView) {
         self.count.replace(self.count.get() + 1);
         self.update(view);
     }
 
-    fn row_deleted(&self, view: &ThumbStripView) {
+    fn row_deleted(&self, view: &gtk4::IconView) {
         let count = self.count.get();
         if count > 0 {
             self.count.replace(count + 1);
@@ -88,37 +66,77 @@ impl ItemCount {
         self.update(view);
     }
 
-    fn update(&self, view: &ThumbStripView) {
+    fn update(&self, view: &gtk4::IconView) {
         view.set_columns(self.count.get());
     }
 }
 
-pub struct ThumbStripViewPriv {
+pub struct ThumbStripView {
     item_height: Cell<i32>,
     item_count: Rc<ItemCount>,
+    icon_view: gtk4::IconView,
     renderer: OnceCell<LibraryCellRenderer>,
-    store: RefCell<Option<gtk::TreeModel>>,
+    store: RefCell<Option<gtk4::TreeModel>>,
     signals: RefCell<Signals>,
 }
 
-pub trait ThumbStripViewExt {
-    fn set_item_height(&self, height: i32);
-    fn set_model(&self, model: Option<gtk::TreeModel>);
-}
+impl std::ops::Deref for ThumbStripView {
+    type Target = gtk4::IconView;
 
-impl ThumbStripViewExt for ThumbStripView {
-    fn set_item_height(&self, height: i32) {
-        let priv_ = ThumbStripViewPriv::from_instance(self);
-        priv_.set_item_height(height);
+    fn deref(&self) -> &gtk4::IconView {
+        &self.icon_view
     }
+}
+
+impl ThumbStripView {
+    fn new(store: &gtk4::TreeModel) -> Self {
+        let tsv = Self {
+            item_height: Cell::new(THUMB_STRIP_VIEW_DEFAULT_ITEM_HEIGHT),
+            item_count: Rc::new(ItemCount {
+                count: Cell::new(0),
+            }),
+            icon_view: gtk4::IconView::with_model(store),
+            renderer: OnceCell::new(),
+            store: RefCell::new(Some(store.clone())),
+            signals: RefCell::new(Signals::default()),
+        };
+
+        let cell_renderer = LibraryCellRenderer::new_thumb_renderer();
+
+        tsv.icon_view.pack_start(&cell_renderer, false);
+        cell_renderer.set_height(100);
+        cell_renderer.set_yalign(0.5);
+        cell_renderer.set_xalign(0.5);
 
-    fn set_model(&self, model: Option<gtk::TreeModel>) {
-        let priv_ = ThumbStripViewPriv::from_instance(self);
-        priv_.set_model(model);
+        tsv.icon_view
+            .set_selection_mode(gtk4::SelectionMode::Multiple);
+        tsv.icon_view.set_column_spacing(THUMB_STRIP_VIEW_SPACING);
+        tsv.icon_view.set_row_spacing(THUMB_STRIP_VIEW_SPACING);
+        tsv.icon_view.set_margin(0);
+        tsv.icon_view.add_attribute(
+            &cell_renderer,
+            "pixbuf",
+            ImageListStoreColIndex::StripThumbIndex as i32,
+        );
+        tsv.icon_view.add_attribute(
+            &cell_renderer,
+            "libfile",
+            ImageListStoreColIndex::FileIndex as i32,
+        );
+        tsv.icon_view.add_attribute(
+            &cell_renderer,
+            "status",
+            ImageListStoreColIndex::FileStatusIndex as i32,
+        );
+        tsv.renderer
+            .set(cell_renderer)
+            .expect("ThumbStripView::constructed set cell render failed.");
+
+        tsv.setup_model();
+
+        tsv
     }
-}
 
-impl ThumbStripViewPriv {
     fn set_item_height(&self, height: i32) {
         self.item_height.set(height);
         if let Some(renderer) = self.renderer.get() {
@@ -126,7 +144,7 @@ impl ThumbStripViewPriv {
         }
     }
 
-    fn set_model(&self, model: Option<gtk::TreeModel>) {
+    fn set_model(&self, model: Option<gtk4::TreeModel>) {
         if let Some(store) = &*self.store.borrow() {
             let mut signals = self.signals.borrow_mut();
             if signals.model_add.is_some() {
@@ -139,7 +157,6 @@ impl ThumbStripViewPriv {
 
         self.store.replace(model.clone());
         self.setup_model();
-        ThumbStripViewExt::set_model(&self.instance(), model);
     }
 
     fn setup_model(&self) {
@@ -157,18 +174,19 @@ impl ThumbStripViewPriv {
             };
             self.item_count.set(count);
 
-            let view = self.instance();
             // update item count
-            self.item_count.update(&view);
+            self.item_count.update(self);
 
             let mut signals = self.signals.borrow_mut();
+            let item_count = self.item_count.clone();
+            let view = self.icon_view.clone();
             signals.model_add = Some(store.connect_row_inserted(
-                clone!(@strong self.item_count as item_count, @weak view => move |_,_,_| {
+                glib::clone!(@strong item_count, @weak view => move |_,_,_| {
                     item_count.row_added(&view);
                 }),
             ));
             signals.model_remove = Some(store.connect_row_deleted(
-                clone!(@strong self.item_count as item_count, @weak view => move |_,_| {
+                glib::clone!(@strong item_count, @weak view => move |_,_| {
                     item_count.row_deleted(&view);
                 }),
             ));
@@ -176,116 +194,29 @@ impl ThumbStripViewPriv {
     }
 }
 
-#[glib::object_subclass]
-impl ObjectSubclass for ThumbStripViewPriv {
-    const NAME: &'static str = "ThumbStripView";
-    type Type = ThumbStripView;
-    type ParentType = gtk::IconView;
-
-    fn new() -> Self {
-        Self {
-            item_height: Cell::new(THUMB_STRIP_VIEW_DEFAULT_ITEM_HEIGHT),
-            item_count: Rc::new(ItemCount {
-                count: Cell::new(0),
-            }),
-            renderer: OnceCell::new(),
-            store: RefCell::new(None),
-            signals: RefCell::new(Signals::default()),
-        }
-    }
+/// # Safety
+/// Use raw pointers
+#[no_mangle]
+pub unsafe extern "C" fn npc_thumb_strip_view_new(
+    store: *mut gtk4_sys::GtkTreeModel,
+) -> *mut ThumbStripView {
+    Box::into_raw(Box::new(ThumbStripView::new(
+        &gtk4::TreeModel::from_glib_full(store),
+    )))
 }
 
-impl ObjectImpl for ThumbStripViewPriv {
-    fn constructed(&self, obj: &Self::Type) {
-        self.parent_constructed(obj);
-
-        let cell_renderer = LibraryCellRenderer::new_thumb_renderer();
-
-        let icon_view_self = obj.clone().upcast::<gtk::IconView>();
-        icon_view_self.pack_start(&cell_renderer, false);
-        cell_renderer.set_height(100);
-        cell_renderer.set_yalign(0.5);
-        cell_renderer.set_xalign(0.5);
-
-        icon_view_self.add_attribute(
-            &cell_renderer,
-            "pixbuf",
-            ImageListStoreColIndex::StripThumbIndex as i32,
-        );
-        icon_view_self.add_attribute(
-            &cell_renderer,
-            "libfile",
-            ImageListStoreColIndex::FileIndex as i32,
-        );
-        icon_view_self.add_attribute(
-            &cell_renderer,
-            "status",
-            ImageListStoreColIndex::FileStatusIndex as i32,
-        );
-        self.renderer
-            .set(cell_renderer)
-            .expect("ThumbStripView::constructed set cell render failed.");
-        let model = icon_view_self.model();
-
-        self.setup_model();
-        self.store.replace(model);
-    }
-
-    fn properties() -> &'static [glib::ParamSpec] {
-        use once_cell::sync::Lazy;
-        static PROPERTIES: Lazy<Vec<glib::ParamSpec>> = Lazy::new(|| {
-            vec![glib::ParamSpecInt::new(
-                "item-height",
-                "Item Height",
-                "The Item Height",
-                -1,
-                100,
-                THUMB_STRIP_VIEW_DEFAULT_ITEM_HEIGHT, // Default value
-                glib::ParamFlags::READWRITE,
-            )]
-        });
-        PROPERTIES.as_ref()
-    }
-
-    fn set_property(
-        &self,
-        _obj: &ThumbStripView,
-        _id: usize,
-        value: &glib::Value,
-        pspec: &glib::ParamSpec,
-    ) {
-        match pspec.name() {
-            "item-height" => {
-                let height: i32 = value
-                    .get()
-                    .expect("type conformity checked by `Object::set_property`");
-                self.set_item_height(height);
-            }
-            _ => unimplemented!(),
-        }
-    }
-
-    fn property(&self, _obj: &ThumbStripView, _id: usize, pspec: &glib::ParamSpec) -> glib::Value {
-        match pspec.name() {
-            "item-height" => self.item_height.get().to_value(),
-            _ => unimplemented!(),
-        }
-    }
+/// # Safety
+/// Use raw pointers
+#[no_mangle]
+pub unsafe extern "C" fn npc_thumb_strip_view_get_icon_view(
+    stripview: &ThumbStripView,
+) -> *mut gtk4_sys::GtkIconView {
+    stripview.icon_view.to_glib_none().0
 }
 
-impl WidgetImpl for ThumbStripViewPriv {}
-
-impl ContainerImpl for ThumbStripViewPriv {}
-
-impl IconViewImpl for ThumbStripViewPriv {}
-
 /// # Safety
 /// Use raw pointers
 #[no_mangle]
-pub unsafe extern "C" fn npc_thumb_strip_view_new(
-    store: *mut gtk_sys::GtkTreeModel,
-) -> *mut gtk_sys::GtkWidget {
-    ThumbStripView::new(&gtk::TreeModel::from_glib_full(store))
-        .upcast::<gtk::Widget>()
-        .to_glib_full()
+pub unsafe extern "C" fn npc_thumb_strip_view_release(stripview: *mut ThumbStripView) {
+    Box::from_raw(stripview);
 }
diff --git a/src/Makefile.am b/src/Makefile.am
index 6238fbe..bc11e91 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -1,7 +1,7 @@
 
 AM_CPPFLAGS = -DNIEPCE_LOCALEDIR=\"${NIEPCE_LOCALEDIR}\"
 
-SUBDIRS = . ext fwk \
+SUBDIRS = . fwk \
        engine libraryclient ncr niepce
 
 RUST_SOURCES = \
diff --git a/src/ext/Makefile.am b/src/ext/Makefile.am
index 8afd99f..803a6d9 100644
--- a/src/ext/Makefile.am
+++ b/src/ext/Makefile.am
@@ -1,5 +1,4 @@
 
 EXTRA_DIST = README
 
-SUBDIRS = libview
-# DIST_SUBDIRS = 
+SUBDIRS =
diff --git a/src/fwk/toolkit/application.cpp b/src/fwk/toolkit/application.cpp
index bb06134..f6b72f8 100644
--- a/src/fwk/toolkit/application.cpp
+++ b/src/fwk/toolkit/application.cpp
@@ -1,7 +1,7 @@
 /*
- * niepce - framework/application.cpp
+ * niepce - fwk/toolkit/application.cpp
  *
- * Copyright (C) 2007-2019 Hubert Figuière
+ * Copyright (C) 2007-2022 Hubert Figuière
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -19,7 +19,7 @@
 
 #include <glibmm/i18n.h>
 #include <glibmm/miscutils.h>
-#include <gtkmm/main.h>
+
 #include <gtkmm/aboutdialog.h>
 #include <gtkmm/settings.h>
 
@@ -38,7 +38,7 @@ Application::Application(int & argc, char** &argv, const char* app_id,
                          const char * name)
     : m_config(Configuration::make_config_path(name))
     , m_module_manager(new ModuleManager())
-    , m_gtkapp(Gtk::Application::create(argc, argv, app_id))
+    , m_gtkapp(Gtk::Application::create(app_id))
 {
     Glib::set_prgname(app_id);
     m_gtkapp->signal_startup().connect(
@@ -61,7 +61,7 @@ Application::Ptr Application::app()
 
 Glib::RefPtr<Gtk::IconTheme> Application::getIconTheme() const
 {
-    return Gtk::IconTheme::get_default();
+    return Gtk::IconTheme::get_for_display(Gdk::Display::get_default());
 }
 
 bool Application::get_use_dark_theme() const
@@ -182,7 +182,7 @@ void Application::on_action_file_open()
 void Application::on_about()
 {
     Gtk::AboutDialog dlg;
-    dlg.run();
+    dlg.show();
 }
 
 std::shared_ptr<UndoTransaction> Application::begin_undo(const std::string & label)
diff --git a/src/fwk/toolkit/application.hpp b/src/fwk/toolkit/application.hpp
index 78242ae..14f9eac 100644
--- a/src/fwk/toolkit/application.hpp
+++ b/src/fwk/toolkit/application.hpp
@@ -1,7 +1,7 @@
 /*
- * niepce - framework/application.h
+ * niepce - fwk/toolkit/application.hpp
  *
- * Copyright (C) 2007-2014 Hubert Figuiere
+ * Copyright (C) 2007-2022 Hubert Figuière
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -17,14 +17,12 @@
  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
  */
 
-
-#ifndef _FRAMEWORK_APPLICATION_H_
-#define _FRAMEWORK_APPLICATION_H_
+#pragma once
 
 #include <glibmm/refptr.h>
+#include <giomm/application.h>
 #include <giomm/menu.h>
 #include <gtkmm/application.h>
-#include <gtkmm/uimanager.h>
 #include <gtkmm/icontheme.h>
 
 #include "fwk/toolkit/configuration.hpp"
@@ -104,7 +102,6 @@ private:
 
 }
 
-#endif
 /*
   Local Variables:
   mode:c++
diff --git a/src/fwk/toolkit/configuration.cpp b/src/fwk/toolkit/configuration.cpp
index 4a0925f..10b08cd 100644
--- a/src/fwk/toolkit/configuration.cpp
+++ b/src/fwk/toolkit/configuration.cpp
@@ -1,7 +1,7 @@
 /*
- * niepce - framework/configuration.cpp
+ * niepce - fwk/toolkit/configuration.cpp
  *
- * Copyright (C) 2007-2013 Hubert Figuiere
+ * Copyright (C) 2007-2022 Hubert Figuière
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -47,10 +47,11 @@ Glib::ustring Configuration::make_config_path(const Glib::ustring & file)
 
 Configuration::Configuration(const Glib::ustring & file)
     : m_filename(file)
+    , m_keyfile(Glib::KeyFile::create())
     , m_root("main")
 {
     try {
-        m_keyfile.load_from_file(m_filename);
+        m_keyfile->load_from_file(m_filename);
     }
     catch(...) {
         DBG_OUT("conf file %s not found - will be created", m_filename.c_str());
@@ -64,13 +65,13 @@ Configuration::~Configuration()
 
 void Configuration::save()
 {
-    Glib::file_set_contents(m_filename, m_keyfile.to_data());
+    Glib::file_set_contents(m_filename, m_keyfile->to_data());
 }
 
 bool Configuration::hasKey(const Glib::ustring & key) const
 {
     //
-    return m_keyfile.has_group(m_root) && m_keyfile.has_key(m_root, key);
+    return m_keyfile->has_group(m_root) && m_keyfile->has_key(m_root, key);
 }
 
 
@@ -78,17 +79,17 @@ const Glib::ustring Configuration::getValue(const Glib::ustring & key,
                                             const Glib::ustring & def) const
 {
     bool has_key = hasKey(key);
-    if(!has_key) {
+    if (!has_key) {
         return def;
     }
 
-    return m_keyfile.get_string(m_root, key);
+    return m_keyfile->get_string(m_root, key);
 }
 
-void Configuration::setValue(const Glib::ustring & key, 
+void Configuration::setValue(const Glib::ustring & key,
                              const Glib::ustring & value)
 {
-    m_keyfile.set_string(m_root, key, value);
+    m_keyfile->set_string(m_root, key, value);
     save();
 }
 
diff --git a/src/fwk/toolkit/configuration.hpp b/src/fwk/toolkit/configuration.hpp
index df0efcc..b26d980 100644
--- a/src/fwk/toolkit/configuration.hpp
+++ b/src/fwk/toolkit/configuration.hpp
@@ -1,7 +1,7 @@
 /*
  * niepce - fwk/toolkit/configuration.hpp
  *
- * Copyright (C) 2007-2013 Hubert Figuiere
+ * Copyright (C) 2007-2022 Hubert Figuière
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -17,9 +17,7 @@
  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
  */
 
-
-#ifndef _FRAMEWORK_CONFIGURATION_H_
-#define _FRAMEWORK_CONFIGURATION_H_
+#pragma once
 
 #include <memory>
 
@@ -48,13 +46,11 @@ private:
     void save();
 
     Glib::ustring          m_filename;
-    Glib::KeyFile          m_keyfile;
+    Glib::RefPtr<Glib::KeyFile> m_keyfile;
     Glib::ustring          m_root;
 };
 
 }
-
-#endif
 /*
   Local Variables:
   mode:c++
diff --git a/src/fwk/toolkit/dialog.cpp b/src/fwk/toolkit/dialog.cpp
index 8446265..7d72791 100644
--- a/src/fwk/toolkit/dialog.cpp
+++ b/src/fwk/toolkit/dialog.cpp
@@ -1,7 +1,7 @@
 /*
  * niepce - fwk/toolkit/dialog.cpp
  *
- * Copyright (C) 2009-2014 Hubert Figuiere
+ * Copyright (C) 2009-2022 Hubert Figuière
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -19,8 +19,9 @@
 
 
 #include <boost/format.hpp>
-#include "libview/header.hh"
 
+#include <gtkmm/box.h>
+#include <gtkmm/label.h>
 #include "dialog.hpp"
 
 
@@ -30,24 +31,22 @@ namespace fwk {
 
 void Dialog::add_header(const std::string & label)
 {
-    Gtk::Box * vbox;
-
-    builder()->get_widget("dialog-vbox1", vbox);
+    Gtk::Box * vbox = builder()->get_widget<Gtk::Box>("dialog-vbox1");
     auto markup = str(boost::format("<span size=\"x-large\">%1%</span>") % label);
-    auto header = manage(new view::Header(markup));
-    header->show();
-    vbox->pack_start(*header, false, true);
+    auto header = Gtk::manage(new Gtk::Label(markup));
+    vbox->append(*header);
 }
 
 int Dialog::run_modal(const Frame::Ptr & parent)
 {
-    int result;
+    int result = 0;
     if(!m_is_setup) {
         setup_widget();
     }
     gtkDialog().set_transient_for(parent->gtkWindow());
-    gtkDialog().set_default_response(Gtk::RESPONSE_CLOSE);
-    result = gtkDialog().run();
+    gtkDialog().set_default_response(Gtk::ResponseType::CLOSE);
+    gtkDialog().set_modal();
+    gtkDialog().show();
     gtkDialog().hide();
     return result;
 }
diff --git a/src/fwk/toolkit/dockable.cpp b/src/fwk/toolkit/dockable.cpp
index 0ae1533..d6b1b81 100644
--- a/src/fwk/toolkit/dockable.cpp
+++ b/src/fwk/toolkit/dockable.cpp
@@ -1,7 +1,7 @@
 /*
  * niepce - fwk/toolkit/dockable.cpp
  *
- * Copyright (C) 2008-2011 Hubert Figuiere
+ * Copyright (C) 2008-2022 Hubert Figuière
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -34,7 +34,7 @@ Dockable::Dockable(const Glib::ustring& name,
 Gtk::Box *
 Dockable::build_vbox()
 {
-    m_box = Gtk::manage(new Gtk::Box(Gtk::ORIENTATION_VERTICAL));
+    m_box = Gtk::manage(new Gtk::Box(Gtk::Orientation::VERTICAL));
     // do the label, the name, etc.
     m_box->set_margin_start(8);
     m_box->set_margin_end(8);
diff --git a/src/fwk/toolkit/frame.cpp b/src/fwk/toolkit/frame.cpp
index 2839f70..32232ae 100644
--- a/src/fwk/toolkit/frame.cpp
+++ b/src/fwk/toolkit/frame.cpp
@@ -1,7 +1,7 @@
 /*
  * niepce - fwk/toolkit/application.cpp
  *
- * Copyright (C) 2007-2014 Hubert Figuiere
+ * Copyright (C) 2007-2022 Hubert Figuière
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -23,6 +23,7 @@
 #include <glibmm/i18n.h>
 #include <giomm/simpleaction.h>
 #include <gtkmm/dialog.h>
+#include <gtkmm/label.h>
 
 #include "fwk/base/debug.hpp"
 #include "fwk/base/geometry.hpp"
@@ -55,7 +56,7 @@ Frame::Frame(const std::string & gladeFile,
       m_layout_cfg_key(layout_cfg_key)
 {
     if (m_builder) {
-        m_builder->get_widget(widgetName, m_window);
+        m_window = m_builder->get_widget<Gtk::Window>(widgetName);
         connectSignals();
         frameRectFromConfig();
     }
@@ -64,10 +65,12 @@ Frame::Frame(const std::string & gladeFile,
 
 void Frame::connectSignals()
 {
-    m_window->signal_delete_event().connect(
-        sigc::hide(sigc::mem_fun(this, &Frame::_close)));
-    m_window->signal_hide().connect(
-        sigc::retype_return<void>(sigc::mem_fun(this, &Frame::_close)));
+    m_window->signal_close_request().connect([this]() -> bool {
+        return this->_close();
+    }, true);
+    m_window->signal_hide().connect([this] {
+        this->_close();
+    });
 }
 
 Frame::~Frame()
@@ -78,32 +81,17 @@ Frame::~Frame()
 }
 
 
+/// XXX do we really need this now?
 void Frame::set_icon_from_theme(const Glib::ustring & name)
 {
-    using Glib::RefPtr;
-    using Gtk::IconTheme;
-    using std::vector;
-    using std::list;
-
-    RefPtr< IconTheme > icon_theme(Application::app()->getIconTheme());
-    vector<int> icon_sizes(icon_theme->get_icon_sizes(name));
-
-    vector< RefPtr <Gdk::Pixbuf> > icons;
-
-    for_each(icon_sizes.begin(), icon_sizes.end(),
-             [&icons, icon_theme, name](int s) {
-                 icons.push_back(
-                     icon_theme->load_icon(name, s,
-                                           Gtk::ICON_LOOKUP_USE_BUILTIN));
-             }
-        );
-    gtkWindow().set_icon_list(icons);
+    gtkWindow().set_icon_name(name);
 }
 
 void Frame::set_title(const std::string & title)
 {
     if (m_header) {
-        m_header->set_title(Glib::ustring(title));
+        auto label = Gtk::manage(new Gtk::Label(title));
+        m_header->set_title_widget(*label);
     }
     else {
         gtkWindow().set_title(Glib::ustring(title));
@@ -189,8 +177,8 @@ void Frame::frameRectFromConfig()
         if(!val.empty()) {
             try {
                 fwk::Rect r(val);
-                m_window->move(r.x(), r.y());
-                m_window->resize(r.w(), r.h());
+                // XXX the position is now ignored
+                m_window->set_default_size(r.w(), r.h());
             }
             catch (const std::bad_cast&) {
                 ERR_OUT("wrong value in configuration: %s", val.c_str());
@@ -207,8 +195,8 @@ void Frame::frameRectToConfig()
         Configuration & cfg = Application::app()->config();
         int x, y, w, h;
         x = y = w = h = 0;
-        m_window->get_position(x, y);
-        m_window->get_size(w, h);
+        // XXX the position is now ignored
+        m_window->get_default_size(w, h);
         fwk::Rect r(x, y, w, h);
         cfg.setValue(m_layout_cfg_key, std::to_string(r));
     }
diff --git a/src/fwk/toolkit/frame.hpp b/src/fwk/toolkit/frame.hpp
index 9bceccc..18ff5be 100644
--- a/src/fwk/toolkit/frame.hpp
+++ b/src/fwk/toolkit/frame.hpp
@@ -1,7 +1,7 @@
 /*
  * niepce - fwk/toolkit/frame.hpp
  *
- * Copyright (C) 2007-2014 Hubert Figuiere
+ * Copyright (C) 2007-2022 Hubert Figuière
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -17,9 +17,7 @@
  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
  */
 
-
-#ifndef _FRAMEWORK_FRAME_H_
-#define _FRAMEWORK_FRAME_H_
+#pragma once
 
 #include <memory>
 #include <string>
@@ -96,8 +94,8 @@ public:
 
     void toggle_tools_visible();
 
-    sigc::signal<void> signal_hide_tools;
-    sigc::signal<void> signal_show_tools;
+    sigc::signal<void(void)> signal_hide_tools;
+    sigc::signal<void(void)> signal_show_tools;
 protected:
 
     void undo_state();
@@ -137,4 +135,3 @@ private:
   fill-column:80
   End:
 */
-#endif
diff --git a/src/fwk/toolkit/gdkutils.cpp b/src/fwk/toolkit/gdkutils.cpp
index 780e55b..a931a54 100644
--- a/src/fwk/toolkit/gdkutils.cpp
+++ b/src/fwk/toolkit/gdkutils.cpp
@@ -1,7 +1,7 @@
 /*
  * niepce - fwk/toolkit/gdkutils.cpp
  *
- * Copyright (C) 2008-2009 Hubert Figuiere
+ * Copyright (C) 2008-2022 Hubert Figuiere
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -39,8 +39,8 @@ namespace fwk {
                double ratio = (double)dim / (double)orig_dim;
                width = (int)(ratio * orig_w);
                height = (int)(ratio * orig_h);
-               return pix->scale_simple(width, height, 
-                                                                Gdk::INTERP_BILINEAR);
+               return pix->scale_simple(width, height,
+                                                                Gdk::InterpType::BILINEAR);
        }
 
        Glib::RefPtr<Gdk::Pixbuf> gdkpixbuf_exif_rotate(const Glib::RefPtr<Gdk::Pixbuf> & tmp,
@@ -53,25 +53,25 @@ namespace fwk {
                        pixbuf = tmp;
                        break;
                case 2:
-                       pixbuf = tmp->flip(TRUE);
+                       pixbuf = tmp->flip(true);
                        break;
                case 3:
-                       pixbuf = tmp->rotate_simple(Gdk::PIXBUF_ROTATE_UPSIDEDOWN);
+                       pixbuf = tmp->rotate_simple(Gdk::Pixbuf::Rotation::UPSIDEDOWN);
                        break;
                case 4:
-                       pixbuf = tmp->rotate_simple(Gdk::PIXBUF_ROTATE_UPSIDEDOWN)->flip(TRUE);
+                       pixbuf = tmp->rotate_simple(Gdk::Pixbuf::Rotation::UPSIDEDOWN)->flip(true);
                        break;
                case 5:
-                       pixbuf = tmp->rotate_simple(Gdk::PIXBUF_ROTATE_CLOCKWISE)->flip(FALSE);
+                       pixbuf = tmp->rotate_simple(Gdk::Pixbuf::Rotation::CLOCKWISE)->flip(false);
                        break;
                case 6:
-                       pixbuf =  tmp->rotate_simple(Gdk::PIXBUF_ROTATE_CLOCKWISE);
+                       pixbuf =  tmp->rotate_simple(Gdk::Pixbuf::Rotation::CLOCKWISE);
                        break;
                case 7:
-                       pixbuf =  tmp->rotate_simple(Gdk::PIXBUF_ROTATE_COUNTERCLOCKWISE)->flip(FALSE);
+                       pixbuf =  tmp->rotate_simple(Gdk::Pixbuf::Rotation::COUNTERCLOCKWISE)->flip(false);
                        break;
                case 8:
-                       pixbuf =  tmp->rotate_simple(Gdk::PIXBUF_ROTATE_COUNTERCLOCKWISE);
+                       pixbuf =  tmp->rotate_simple(Gdk::Pixbuf::Rotation::COUNTERCLOCKWISE);
                        break;
                default:
                        break;
diff --git a/src/fwk/toolkit/gphoto.cpp b/src/fwk/toolkit/gphoto.cpp
index c2b7eea..68a6c42 100644
--- a/src/fwk/toolkit/gphoto.cpp
+++ b/src/fwk/toolkit/gphoto.cpp
@@ -284,7 +284,7 @@ bool GpCamera::try_unmount_camera()
     try {
         auto mount_op = Gio::MountOperation::create();
         // We need to pass a callback or gvfs will crash
-        to_unmount->unmount(mount_op, [] (Glib::RefPtr<Gio::AsyncResult>&) {}, Gio::MOUNT_UNMOUNT_NONE);
+        to_unmount->unmount(mount_op, [] (Glib::RefPtr<Gio::AsyncResult>&) {}, 
Gio::Mount::UnmountFlags::NONE);
     } catch(const Gio::Error& e) {
         ERR_OUT("Gio::Error unmounting camera %d", e.code());
         return false;
diff --git a/src/fwk/toolkit/mapcontroller.cpp b/src/fwk/toolkit/mapcontroller.cpp
index 68f3e76..bd8029b 100644
--- a/src/fwk/toolkit/mapcontroller.cpp
+++ b/src/fwk/toolkit/mapcontroller.cpp
@@ -1,7 +1,7 @@
 /*
  * niepce - fwk/toolkit/mapcontroller.cpp
  *
- * Copyright (C) 2014-2019 Hubert Figuière
+ * Copyright (C) 2014-2022 Hubert Figuière
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -20,7 +20,11 @@
 #include "mapcontroller.hpp"
 
 #include <gtkmm/widget.h>
+#include <gtkmm/label.h>
+
+#if 0
 #include <osm-gps-map.h>
+#endif
 
 namespace fwk {
 
@@ -32,12 +36,16 @@ public:
         }
     ~Priv()
         {
+            // XXX
+            #if 0
             if (m_map) {
                 g_object_unref(G_OBJECT(m_map));
             }
+            #endif
         }
     void create_widget()
         {
+            #if 0
             m_map = OSM_GPS_MAP(osm_gps_map_new());
             g_object_ref(m_map);
 
@@ -54,8 +62,14 @@ public:
                               nullptr));
             osm_gps_map_layer_add(OSM_GPS_MAP(m_map), osd);
             g_object_unref(G_OBJECT(osd));
+            #endif
         }
-    OsmGpsMap* m_map;
+    #if 0
+    OsmGpsMap*
+    #else
+    GtkWidget*
+    #endif
+    m_map;
 };
 
 MapController::MapController()
@@ -78,7 +92,7 @@ MapController::buildWidget()
 
     m_priv->create_widget();
 
-    m_widget = Gtk::manage(Glib::wrap(GTK_WIDGET(m_priv->m_map)));
+    m_widget = Gtk::manage(new Gtk::Label("This is a map")); //Glib::wrap(GTK_WIDGET(m_priv->m_map)));
 
     // Default position. Somewhere over Montréal, QC
     setZoomLevel(10);
@@ -89,22 +103,30 @@ MapController::buildWidget()
 
 void MapController::centerOn(double lat, double longitude)
 {
+    #if 0
     osm_gps_map_set_center(m_priv->m_map, lat, longitude);
+    #endif
 }
 
 void MapController::zoomIn()
 {
+    #if 0
     osm_gps_map_zoom_in(m_priv->m_map);
+    #endif
 }
 
 void MapController::zoomOut()
 {
+    #if 0
     osm_gps_map_zoom_out(m_priv->m_map);
+    #endif
 }
 
 void MapController::setZoomLevel(uint8_t level)
 {
+    #if 0
     osm_gps_map_set_zoom(m_priv->m_map, level);
+    #endif
 }
 
 }
diff --git a/src/fwk/toolkit/metadatawidget.cpp b/src/fwk/toolkit/metadatawidget.cpp
index 417a320..1170ce4 100644
--- a/src/fwk/toolkit/metadatawidget.cpp
+++ b/src/fwk/toolkit/metadatawidget.cpp
@@ -1,7 +1,7 @@
 /*
  * niepce - fwk/toolkit/metadatawidget.cpp
  *
- * Copyright (C) 2008-2021 Hubert Figuière
+ * Copyright (C) 2008-2022 Hubert Figuière
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -25,10 +25,11 @@
 #include <boost/rational.hpp>
 
 #include <glibmm/i18n.h>
-#include <gtkmm/label.h>
+#include <gtkmm/drawingarea.h>
 #include <gtkmm/entry.h>
+#include <gtkmm/eventcontrollerfocus.h>
+#include <gtkmm/label.h>
 #include <gtkmm/textview.h>
-#include <gtkmm/drawingarea.h>
 
 #include "fwk/base/debug.hpp"
 #include "fwk/base/autoflag.hpp"
@@ -53,7 +54,7 @@ MetaDataWidget::MetaDataWidget(const Glib::ustring & title)
     m_table.insert_column(0);
     m_table.insert_column(0);
     m_table.set_margin_start(8);
-    add(m_table);
+    set_child(m_table);
     set_sensitive(false);
 }
 
@@ -86,13 +87,13 @@ MetaDataWidget::create_star_rating_widget(bool readonly, ffi::NiepcePropertyIdx
 Gtk::Widget*
 MetaDataWidget::create_string_array_widget(bool readonly, ffi::NiepcePropertyIdx id)
 {
-    fwk::TokenTextView * ttv = Gtk::manage(new fwk::TokenTextView());
-    if(!readonly) {
-        ttv->signal_focus_out_event().connect(
-            sigc::bind(
-                sigc::mem_fun(*this, 
-                              &MetaDataWidget::on_string_array_changed),
-                ttv, id));
+    fwk::TokenTextView* ttv = Gtk::manage(new fwk::TokenTextView());
+    if (!readonly) {
+        auto ctrl = Gtk::EventControllerFocus::create();
+        ctrl->signal_leave().connect([this, ttv, id] {
+            this->on_string_array_changed(ttv, id);
+        });
+        ttv->add_controller(ctrl);
     }
     return ttv;
 }
@@ -105,18 +106,19 @@ MetaDataWidget::create_text_widget(bool readonly, ffi::NiepcePropertyIdx id)
         l->set_xalign(0.0f);
         l->set_yalign(0.5f);
         // This will prevent the label from being expanded.
-        l->set_ellipsize(Pango::ELLIPSIZE_MIDDLE);
+        l->set_ellipsize(Pango::EllipsizeMode::MIDDLE);
         return l;
     }
 
     Gtk::TextView * e = Gtk::manage(new NoTabTextView());
     e->set_editable(true);
-    e->set_wrap_mode(Gtk::WRAP_WORD);
-    e->signal_focus_out_event().connect(
-        sigc::bind(
-            sigc::mem_fun(*this,
-                          &MetaDataWidget::on_text_changed),
-            e->get_buffer(), id));
+    e->set_wrap_mode(Gtk::WrapMode::WORD);
+    auto ctrl = Gtk::EventControllerFocus::create();
+    e->add_controller(ctrl);
+    auto buffer = e->get_buffer();
+    ctrl->signal_leave().connect([this, buffer, id] {
+        this->on_text_changed(buffer, id);
+    });
     return e;
 }
 
@@ -128,17 +130,17 @@ MetaDataWidget::create_string_widget(bool readonly, ffi::NiepcePropertyIdx id)
         l->set_xalign(0.0f);
         l->set_yalign(0.5f);
         // This will prevent the label from being expanded.
-        l->set_ellipsize(Pango::ELLIPSIZE_MIDDLE);
+        l->set_ellipsize(Pango::EllipsizeMode::MIDDLE);
         return l;
     }
 
     Gtk::Entry * e = Gtk::manage(new Gtk::Entry());
     e->set_has_frame(false); // TODO make that a custom widget
-    e->signal_focus_out_event().connect(
-        sigc::bind(
-            sigc::mem_fun(*this,
-                          &MetaDataWidget::on_str_changed),
-            e, id));
+    auto ctrl = Gtk::EventControllerFocus::create();
+    e->add_controller(ctrl);
+    ctrl->signal_leave().connect([this, e, id] {
+        this->on_str_changed(e, id);
+    });
     return e;
 }
 
@@ -192,13 +194,12 @@ MetaDataWidget::create_widgets_for_format(const MetaDataSectionFormat * fmt)
 
         m_table.insert_row(n_row + 1);
         m_table.attach(*labelw, 0, n_row, 1, 1);
-        m_table.attach_next_to(*w, *labelw, Gtk::POS_RIGHT, 1, 1);
+        m_table.attach_next_to(*w, *labelw, Gtk::PositionType::RIGHT, 1, 1);
         m_data_map.insert(std::make_pair(current->id, w));
 
         current++;
         n_row++;
     }
-    m_table.show_all();
 }
 
 void MetaDataWidget::clear_widget(const std::pair<const ffi::NiepcePropertyIdx, Gtk::Widget *> & p)
@@ -460,7 +461,7 @@ void MetaDataWidget::add_data(const MetaDataFormat& current,
     }
 }
 
-bool MetaDataWidget::on_str_changed(GdkEventFocus*, Gtk::Entry *e,
+bool MetaDataWidget::on_str_changed(Gtk::Entry *e,
                                     ffi::NiepcePropertyIdx prop)
 {
     if(m_update) {
@@ -470,8 +471,7 @@ bool MetaDataWidget::on_str_changed(GdkEventFocus*, Gtk::Entry *e,
     return true;
 }
 
-bool MetaDataWidget::on_text_changed(GdkEventFocus*,
-                                     Glib::RefPtr<Gtk::TextBuffer> b,
+bool MetaDataWidget::on_text_changed(Glib::RefPtr<Gtk::TextBuffer> b,
                                      ffi::NiepcePropertyIdx prop)
 {
     if(m_update) {
@@ -482,8 +482,7 @@ bool MetaDataWidget::on_text_changed(GdkEventFocus*,
     return true;
 }
 
-bool MetaDataWidget::on_string_array_changed(GdkEventFocus*,
-                                             fwk::TokenTextView * ttv,
+bool MetaDataWidget::on_string_array_changed(fwk::TokenTextView * ttv,
                                              ffi::NiepcePropertyIdx prop)
 {
     if(m_update) {
diff --git a/src/fwk/toolkit/metadatawidget.hpp b/src/fwk/toolkit/metadatawidget.hpp
index 7e7f8a1..7a63a9d 100644
--- a/src/fwk/toolkit/metadatawidget.hpp
+++ b/src/fwk/toolkit/metadatawidget.hpp
@@ -1,7 +1,7 @@
 /*
- * niepce - fwk/toolkit/metadatawidget.h
+ * niepce - fwk/toolkit/metadatawidget.hpp
  *
- * Copyright (C) 2008-2021 Hubert Figuiere
+ * Copyright (C) 2008-2022 Hubert Figuière
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -17,8 +17,7 @@
  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
  */
 
-#ifndef __NIEPCE_FRAMEWORK_META_DATA_WIDGET_H__
-#define __NIEPCE_FRAMEWORK_META_DATA_WIDGET_H__
+#pragma once
 
 #include <map>
 #include <string>
@@ -74,11 +73,11 @@ public:
     void set_data_format(const MetaDataSectionFormat* fmt);
     void set_data_source(const fwk::PropertyBagPtr& properties);
 
-    sigc::signal<void, const fwk::PropertyBagPtr &, const fwk::PropertyBagPtr &> signal_metadata_changed;
+    sigc::signal<void(const fwk::PropertyBagPtr &, const fwk::PropertyBagPtr &)> signal_metadata_changed;
 protected:
-    bool on_str_changed(GdkEventFocus*, Gtk::Entry *, ffi::NiepcePropertyIdx prop);
-    bool on_text_changed(GdkEventFocus*, Glib::RefPtr<Gtk::TextBuffer> b, ffi::NiepcePropertyIdx prop);
-    bool on_string_array_changed(GdkEventFocus*, fwk::TokenTextView * ttv,
+    bool on_str_changed(Gtk::Entry *, ffi::NiepcePropertyIdx prop);
+    bool on_text_changed(Glib::RefPtr<Gtk::TextBuffer> b, ffi::NiepcePropertyIdx prop);
+    bool on_string_array_changed(fwk::TokenTextView * ttv,
                                  ffi::NiepcePropertyIdx prop);
     void on_int_changed(int, ffi::NiepcePropertyIdx prop);
 private:
@@ -127,5 +126,3 @@ private:
   fill-column:80
   End:
 */
-
-#endif
diff --git a/src/fwk/toolkit/mimetype.cpp b/src/fwk/toolkit/mimetype.cpp
index 744492f..1f3b363 100644
--- a/src/fwk/toolkit/mimetype.cpp
+++ b/src/fwk/toolkit/mimetype.cpp
@@ -1,7 +1,7 @@
 /*
- * niepce - fwk/mimetype.cpp
+ * niepce - fwk/toolkit/mimetype.cpp
  *
- * Copyright (C) 2008-2013 Hubert Figuiere
+ * Copyright (C) 2008-2022 Hubert Figuière
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -18,6 +18,8 @@
  */
 
 #include <string>
+#include <exception>
+
 #include <giomm/contenttype.h>
 
 #include "config.h"
@@ -41,7 +43,7 @@ MimeType::MimeType(const std::string & filename)
         auto fileinfo = file->query_info();
         m_type = fileinfo->get_content_type();
     }
-    catch(const Glib::Exception &e) {
+    catch(const std::exception &e) {
         gboolean uncertainty = false;
         gchar *content_type = g_content_type_guess(filename.c_str(),
                                                    nullptr, 0, &uncertainty);
diff --git a/src/fwk/toolkit/notificationcenter.cpp b/src/fwk/toolkit/notificationcenter.cpp
index 329175c..fcf622f 100644
--- a/src/fwk/toolkit/notificationcenter.cpp
+++ b/src/fwk/toolkit/notificationcenter.cpp
@@ -1,7 +1,7 @@
 /*
  * niepce - fwk/toolkit/notification.cpp
  *
- * Copyright (C) 2007-2017 Hubert Figuière
+ * Copyright (C) 2007-2022 Hubert Figuière
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -39,8 +39,9 @@ public:
 NotificationCenter::NotificationCenter()
     : p( new Priv )
 {
-    p->m_dispatcher.connect(
-        sigc::mem_fun(this, &NotificationCenter::_dispatch));
+    p->m_dispatcher.connect([this] {
+        this->_dispatch();
+    });
 }
 
 NotificationCenter::~NotificationCenter()
diff --git a/src/fwk/toolkit/notificationcenter.hpp b/src/fwk/toolkit/notificationcenter.hpp
index bf259aa..8748c55 100644
--- a/src/fwk/toolkit/notificationcenter.hpp
+++ b/src/fwk/toolkit/notificationcenter.hpp
@@ -1,7 +1,7 @@
 /*
  * niepce - fwk/toolkit/notification.hpp
  *
- * Copyright (C) 2007-2013 Hubert Figuiere
+ * Copyright (C) 2007-2022 Hubert Figuière
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -17,9 +17,7 @@
  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
  */
 
-
-#ifndef __FWK_NOTIFICATIONCENTER_H__
-#define __FWK_NOTIFICATIONCENTER_H__
+#pragma once
 
 #include <memory>
 #include <sigc++/trackable.h>
@@ -35,7 +33,7 @@ class NotificationCenter
 {
 public:
     typedef std::shared_ptr< NotificationCenter > Ptr;
-    typedef sigc::slot<void, Notification::Ptr> subscriber_t;
+    typedef sigc::slot<void(Notification::Ptr)> subscriber_t;
 
     ~NotificationCenter();
 
@@ -49,7 +47,7 @@ protected:
     NotificationCenter();
 
 private:
-    typedef sigc::signal<void, Notification::Ptr> subscription_t;
+    typedef sigc::signal<void(Notification::Ptr)> subscription_t;
 
     void _dispatch(void);
 
@@ -58,8 +56,6 @@ private:
 };
 
 }
-
-#endif
 /*
   Local Variables:
   mode:c++
diff --git a/src/fwk/toolkit/uiresult.hpp b/src/fwk/toolkit/uiresult.hpp
index 5c10a21..e157ccc 100644
--- a/src/fwk/toolkit/uiresult.hpp
+++ b/src/fwk/toolkit/uiresult.hpp
@@ -2,7 +2,7 @@
 /*
  * niepce - fwk/toolkit/uiresult.hpp
  *
- * Copyright (C) 2017 Hubert Figuière
+ * Copyright (C) 2017-2022 Hubert Figuière
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -35,11 +35,11 @@ class UIResult
 public:
     virtual void clear() = 0;
 
-    sigc::connection connect(sigc::slot<void>&& slot) {
+    sigc::connection connect(sigc::slot<void()> slot) {
         return m_notifier.connect(std::move(slot));
     }
 
-    void run(std::function<void ()>&& f);
+    void run(std::function<void()>&& f);
 protected:
     Glib::Dispatcher m_notifier;
     std::mutex m_data_mutex;
diff --git a/src/fwk/toolkit/undo.hpp b/src/fwk/toolkit/undo.hpp
index eae811d..297646a 100644
--- a/src/fwk/toolkit/undo.hpp
+++ b/src/fwk/toolkit/undo.hpp
@@ -1,7 +1,7 @@
 /*
  * niepce - fwk/toolkit/undo.hpp
  *
- * Copyright (C) 2008-2013 Hubert Figuiere
+ * Copyright (C) 2008-2022 Hubert Figuière
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -17,9 +17,7 @@
  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
  */
 
-
-#ifndef _FRAMEWORK_UNDO_H_
-#define _FRAMEWORK_UNDO_H_
+#pragma once
 
 #include <list>
 #include <stack>
@@ -95,15 +93,13 @@ public:
     std::string next_redo() const;
 
     // called when the undo history change.
-    sigc::signal<void> signal_changed;
+    sigc::signal<void(void)> signal_changed;
 private:
     std::list<std::shared_ptr<UndoTransaction>> m_undos;
     std::list<std::shared_ptr<UndoTransaction>> m_redos;
 };
 
 }
-
-#endif
 /*
   Local Variables:
   mode:c++
diff --git a/src/fwk/toolkit/widgets/addinstreemodel.cpp b/src/fwk/toolkit/widgets/addinstreemodel.cpp
index 9fc0b4e..c0e1c79 100644
--- a/src/fwk/toolkit/widgets/addinstreemodel.cpp
+++ b/src/fwk/toolkit/widgets/addinstreemodel.cpp
@@ -1,8 +1,8 @@
 /*
  * gnote
  *
- * Copyright (C) 2009-2013 Hubert Figuiere
- * 
+ * Copyright (C) 2009-2022 Hubert Figuière
+ *
  * Permission is hereby granted, free of charge, to any person obtaining a
  * copy of this software and associated documentation files (the "Software"),
  * to deal in the Software without restriction, including without limitation
@@ -49,7 +49,7 @@ namespace fwk {
     set_column_types(m_columns);
   }
 
-  fwk::DynamicModule * AddinsTreeModel::get_module(const Gtk::TreeIter & iter)
+  fwk::DynamicModule * AddinsTreeModel::get_module(const Gtk::TreeModel::iterator & iter)
   {
     fwk::DynamicModule * module = nullptr;
     if(iter) {
@@ -66,9 +66,9 @@ namespace fwk {
   }
 
 
-  Gtk::TreeIter AddinsTreeModel::append(const fwk::DynamicModule *module)
+  Gtk::TreeModel::iterator AddinsTreeModel::append(const fwk::DynamicModule *module)
   {
-    Gtk::TreeIter iter = Gtk::TreeStore::append();
+    Gtk::TreeModel::iterator iter = Gtk::TreeStore::append();
     iter->set_value(0, std::string(module->name()));
     iter->set_value(1, std::string(module->description()));
     iter->set_value(2, module);
diff --git a/src/fwk/toolkit/widgets/addinstreemodel.hpp b/src/fwk/toolkit/widgets/addinstreemodel.hpp
index 8c397bd..8b631b2 100644
--- a/src/fwk/toolkit/widgets/addinstreemodel.hpp
+++ b/src/fwk/toolkit/widgets/addinstreemodel.hpp
@@ -1,8 +1,8 @@
 /*
  * gnote
  *
- * Copyright (C) 2009 Hubert Figuiere
- * 
+ * Copyright (C) 2009-2022 Hubert Figuière
+ *
  * Permission is hereby granted, free of charge, to any person obtaining a
  * copy of this software and associated documentation files (the "Software"),
  * to deal in the Software without restriction, including without limitation
@@ -22,11 +22,7 @@
  * DEALINGS IN THE SOFTWARE.
  */
 
-
-
-
-#ifndef __FWK_UTILS_ADDINSTREEMODEL_HPP_
-#define __FWK_UTILS_ADDINSTREEMODEL_HPP_
+#pragma once
 
 #include <gtkmm/treestore.h>
 #include <gtkmm/treeview.h>
@@ -42,17 +38,17 @@ public:
   typedef Glib::RefPtr<AddinsTreeModel> Ptr;
   static Ptr create(Gtk::TreeView * treeview);
 
-  DynamicModule * get_module(const Gtk::TreeIter &);
+  DynamicModule * get_module(const Gtk::TreeModel::iterator &);
 
-  Gtk::TreeIter append(const fwk::DynamicModule *);
+  Gtk::TreeModel::iterator append(const fwk::DynamicModule *);
   class AddinsColumns
     : public Gtk::TreeModelColumnRecord
   {
   public:
     AddinsColumns()
       {
-        add(name); 
-        add(description); 
+        add(name);
+        add(description);
         add(addin);
       }
 
@@ -66,10 +62,6 @@ protected:
   AddinsTreeModel();
   void set_columns(Gtk::TreeView *v);
 private:
-  
 };
 
 }
-
-
-#endif
diff --git a/src/fwk/toolkit/widgets/dock.cpp b/src/fwk/toolkit/widgets/dock.cpp
index a33e76d..d96a7cf 100644
--- a/src/fwk/toolkit/widgets/dock.cpp
+++ b/src/fwk/toolkit/widgets/dock.cpp
@@ -1,7 +1,7 @@
 /*
  * niepce - fwk/toolkit/dock.hpp
  *
- * Copyright (C) 2011 Hubert Figuiere
+ * Copyright (C) 2011-2022 Hubert Figuière
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -24,10 +24,10 @@ namespace fwk {
 
 
 Dock::Dock()
-  : m_vbox(Gtk::ORIENTATION_VERTICAL)
+  : m_vbox(Gtk::Orientation::VERTICAL)
 {
-  set_policy(Gtk::POLICY_NEVER, Gtk::POLICY_ALWAYS);
-  add(m_vbox);
+  set_policy(Gtk::PolicyType::NEVER, Gtk::PolicyType::ALWAYS);
+  set_child(m_vbox);
 }
 
 }
diff --git a/src/fwk/toolkit/widgets/dock.hpp b/src/fwk/toolkit/widgets/dock.hpp
index d21075d..0541b38 100644
--- a/src/fwk/toolkit/widgets/dock.hpp
+++ b/src/fwk/toolkit/widgets/dock.hpp
@@ -1,7 +1,7 @@
 /*
  * niepce - fwk/toolkit/dock.hpp
  *
- * Copyright (C) 2011 Hubert Figuiere
+ * Copyright (C) 2011-2022 Hubert Figuière
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -17,16 +17,13 @@
  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
  */
 
-#ifndef __FWK_TOOLKIT_DOCK_HPP__
-#define __FWK_TOOLKIT_DOCK_HPP__
+#pragma once
 
 #include <gtkmm/box.h>
 #include <gtkmm/scrolledwindow.h>
 
-
 namespace fwk {
 
-
 class Dock
   : public Gtk::ScrolledWindow
 {
@@ -39,7 +36,6 @@ private:
     Gtk::Box m_vbox;
 };
 
-
 }
 /*
   Local Variables:
@@ -50,4 +46,3 @@ private:
   fill-column:80
   End:
 */
-#endif
diff --git a/src/fwk/toolkit/widgets/editablehscale.cpp b/src/fwk/toolkit/widgets/editablehscale.cpp
index 9d47ffc..a762c0e 100644
--- a/src/fwk/toolkit/widgets/editablehscale.cpp
+++ b/src/fwk/toolkit/widgets/editablehscale.cpp
@@ -1,7 +1,7 @@
 /*
  * niepce - fwk/widgets/editablehscale.cpp
  *
- * Copyright (C) 2008-2013 Hubert Figuiere
+ * Copyright (C) 2008-2022 Hubert Figuière
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -20,6 +20,8 @@
 #include <boost/lexical_cast.hpp>
 
 #include <glibmm/property.h>
+#include <gtkmm/adjustment.h>
+#include <gtkmm/gestureclick.h>
 
 #include "fwk/base/debug.hpp"
 #include "editablehscale.hpp"
@@ -28,20 +30,19 @@
 namespace fwk {
 
 EditableHScale::EditableHScale(double min, double max, double step)
-    : Gtk::Box(Gtk::ORIENTATION_HORIZONTAL),
+    : Gtk::Box(Gtk::Orientation::HORIZONTAL),
       m_icon(nullptr),
       m_adj(Gtk::Adjustment::create(0, min, max, step)),
-      m_scale(m_adj, Gtk::ORIENTATION_HORIZONTAL),
+      m_scale(m_adj, Gtk::Orientation::HORIZONTAL),
       m_entry(m_adj),
       m_dirty(false)
 {
     _init();
 }
 
-
 EditableHScale::EditableHScale(const std::string & icon_path,
                                double min, double max, double step)
-    : Gtk::Box(Gtk::ORIENTATION_HORIZONTAL),
+    : Gtk::Box(Gtk::Orientation::HORIZONTAL),
       m_icon(Gtk::manage(new Gtk::Image(Gdk::Pixbuf::create_from_resource(icon_path, -1, -1)))),
       m_adj(Gtk::Adjustment::create(0, min, max, step)),
       m_scale(m_adj), m_entry(m_adj),
@@ -55,64 +56,56 @@ EditableHScale::EditableHScale(const std::string & icon_path,
 void EditableHScale::_init()
 {
     if(m_icon) {
-        pack_start(*m_icon, false, true);
+        append(*m_icon);
     }
     m_scale.property_draw_value() = false;
-    m_scale.add_events(Gdk::BUTTON_RELEASE_MASK);
-    m_scale.signal_button_release_event()
-        .connect(sigc::mem_fun(*this, &EditableHScale::on_button_press_event));
-    pack_start(m_scale, Gtk::PACK_EXPAND_WIDGET);
+
+    auto gesture = Gtk::GestureClick::create();
+    gesture->set_button(1);
+    gesture->signal_released()
+        .connect([this] (int, double, double) {
+            this->on_button_press_event();
+        });
+    m_scale.add_controller(gesture);
+    append(m_scale);
     m_entry.set_width_chars(4);
     m_entry.set_digits(2);
     m_entry.set_editable(true);
-    m_entry.add_events(Gdk::BUTTON_RELEASE_MASK);
-    m_entry.signal_button_release_event()
-        .connect(sigc::mem_fun(*this, &EditableHScale::on_button_press_event));
-    pack_start(m_entry, Gtk::PACK_SHRINK);
+
+    m_entry.add_controller(gesture);
+    append(m_entry);
 
     m_adj->signal_value_changed()
-        .connect(sigc::mem_fun(*this, &EditableHScale::on_adj_value_changed));
-    add_events(Gdk::BUTTON_RELEASE_MASK);
+        .connect([this] {
+            this->on_adj_value_changed();
+        });
 }
 
-
-bool EditableHScale::on_button_press_event(GdkEventButton *_event)
+void EditableHScale::on_button_press_event()
 {
-    DBG_OUT("button %d released", _event->button);
-    if (_event->type == GDK_BUTTON_RELEASE && _event->button != 1) {
-        return false;
-    } 
-    else {
-        Gtk::Widget::on_button_release_event(_event);
-        if(m_dirty) {
-            m_dirty = false;
-            DBG_OUT("value_change.emit(%f)", m_adj->get_value());
-            m_sig_value_changed.emit(m_adj->get_value());
-        }
-        return false;
+    if(m_dirty) {
+        m_dirty = false;
+        DBG_OUT("value_change.emit(%f)", m_adj->get_value());
+        m_sig_value_changed.emit(m_adj->get_value());
     }
 }
 
-
 void EditableHScale::on_adj_value_changed()
 {
     m_dirty = true;
     m_sig_value_changing.emit(m_adj->get_value());
 }
 
-
-sigc::signal<void,double> & EditableHScale::signal_value_changed()
+sigc::signal<void(double)>& EditableHScale::signal_value_changed()
 {
     return m_sig_value_changed;
 }
 
-
-sigc::signal<void,double> & EditableHScale::signal_value_changing()
+sigc::signal<void(double)>& EditableHScale::signal_value_changing()
 {
     return m_sig_value_changing;
 }
 
-
 }
 
 /*
diff --git a/src/fwk/toolkit/widgets/editablehscale.hpp b/src/fwk/toolkit/widgets/editablehscale.hpp
index 1e53a4a..269dbf9 100644
--- a/src/fwk/toolkit/widgets/editablehscale.hpp
+++ b/src/fwk/toolkit/widgets/editablehscale.hpp
@@ -1,7 +1,7 @@
 /*
- * niepce - fwk/widgets/editablehscale.h
+ * niepce - fwk/widgets/editablehscale.hpp
  *
- * Copyright (C) 2008-2017 Hubert Figuière
+ * Copyright (C) 2008-2022 Hubert Figuière
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -17,15 +17,12 @@
  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
  */
 
-
-#ifndef _FWK_EDITABLEHSCALE_H_
-#define _FWK_EDITABLEHSCALE_H_
-
+#pragma once
 
 #include <gtkmm/box.h>
-#include <gtkmm/spinbutton.h>
+#include <gtkmm/image.h>
 #include <gtkmm/scale.h>
-
+#include <gtkmm/spinbutton.h>
 
 namespace fwk {
 
@@ -35,16 +32,16 @@ class EditableHScale
 {
 public:
     EditableHScale(double min, double max, double step);
-    EditableHScale(const std::string & icon_name, 
+    EditableHScale(const std::string & icon_name,
                    double min, double max, double step);
 
     const Glib::RefPtr<Gtk::Adjustment>  & get_adjustment() const
         { return m_adj; }
 
-    sigc::signal<void,double> & signal_value_changed();
-    sigc::signal<void,double> & signal_value_changing();
+    sigc::signal<void(double)> & signal_value_changed();
+    sigc::signal<void(double)> & signal_value_changing();
 
-    bool on_button_press_event(GdkEventButton *event) override;
+    void on_button_press_event();
 
 private:
 
@@ -58,15 +55,12 @@ private:
     Gtk::SpinButton  m_entry;
     bool             m_dirty;
     /** emitted once the value changed */
-    sigc::signal<void,double> m_sig_value_changed;
+    sigc::signal<void(double)> m_sig_value_changed;
     /** emitted when the value is changing (think live update) */
-    sigc::signal<void,double> m_sig_value_changing;
+    sigc::signal<void(double)> m_sig_value_changing;
 };
 
-
 }
-
-#endif
 /*
   Local Variables:
   mode:c++
diff --git a/src/fwk/toolkit/widgets/tokentextview.cpp b/src/fwk/toolkit/widgets/tokentextview.cpp
index f6b7b0a..d7edbd1 100644
--- a/src/fwk/toolkit/widgets/tokentextview.cpp
+++ b/src/fwk/toolkit/widgets/tokentextview.cpp
@@ -1,7 +1,7 @@
 /*
  * niepce - fwk/toolkit/widgets/tokentextview.cpp
  *
- * Copyright (C) 2012-2014 Hubert Figuiere
+ * Copyright (C) 2012-2022 Hubert Figuière
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -28,7 +28,7 @@ namespace fwk {
 TokenTextView::TokenTextView()
     : NoTabTextView()
 {
-    set_wrap_mode(Gtk::WRAP_WORD);
+    set_wrap_mode(Gtk::WrapMode::WORD);
 }
 
 void TokenTextView::set_tokens(const Tokens & tokens)
diff --git a/src/fwk/utils/init.cpp b/src/fwk/utils/init.cpp
index 14ac565..f19f1d5 100644
--- a/src/fwk/utils/init.cpp
+++ b/src/fwk/utils/init.cpp
@@ -1,7 +1,7 @@
 /*
  * niepce - fwk/utils/init.cpp
  *
- * Copyright (C) 2009 Hubert Figuiere
+ * Copyright (C) 2009-2022 Hubert Figuière
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -18,7 +18,6 @@
  */
 
 
-#include <glibmm/threads.h>
 #include <giomm/init.h>
 
 
diff --git a/src/fwk/utils/modulemanager.cpp b/src/fwk/utils/modulemanager.cpp
index 0199482..875ea2a 100644
--- a/src/fwk/utils/modulemanager.cpp
+++ b/src/fwk/utils/modulemanager.cpp
@@ -3,8 +3,8 @@
  * copied from
  * gnote
  *
- * Copyright (C) 2009-2017 Hubert Figuiere
- * 
+ * Copyright (C) 2009-2022 Hubert Figuière
+ *
  * Permission is hereby granted, free of charge, to any person obtaining a
  * copy of this software and associated documentation files (the "Software"),
  * to deal in the Software without restriction, including without limitation
@@ -76,7 +76,7 @@ namespace fwk {
 
         auto file_path = fwk::RustFfiString(ffi::fwk_file_list_at(l.get(), i));
         Glib::Module module(*iter + "/" + path_basename(file_path.c_str()),
-                            Glib::MODULE_BIND_LOCAL);
+                            Glib::Module::Flags::LOCAL);
         DBG_OUT("load module %s", path_basename(file_path.c_str()).c_str());
 
         if(!module) {
diff --git a/src/fwk/utils/pathutils.cpp b/src/fwk/utils/pathutils.cpp
index a4964b4..bfaaf72 100644
--- a/src/fwk/utils/pathutils.cpp
+++ b/src/fwk/utils/pathutils.cpp
@@ -1,7 +1,7 @@
 /*
  * niepce - fwk/utils/pathutils.cpp
  *
- * Copyright (C) 2009-2015 Hubert Figuière
+ * Copyright (C) 2009-2022 Hubert Figuière
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -117,7 +117,7 @@ void _path_remove_recursive(const Glib::RefPtr<Gio::File> & dir)
     while(file_info = enumerator->next_file()) {
         Glib::RefPtr<Gio::File> child;
         child = dir->get_child(file_info->get_name());
-        if(file_info->get_type() == Gio::FILE_TYPE_DIRECTORY) {
+        if(file_info->get_file_type() == Gio::FileType::DIRECTORY) {
             _path_remove_recursive(child);
         }
         child->remove();
diff --git a/src/ncr/image.cpp b/src/ncr/image.cpp
index e722515..c28d4ca 100644
--- a/src/ncr/image.cpp
+++ b/src/ncr/image.cpp
@@ -418,7 +418,7 @@ Cairo::RefPtr<Cairo::ImageSurface> Image::cairo_surface_for_display()
     const Babl* format = babl_format("B'aG'aR'aA u8");
 
     Cairo::RefPtr<Cairo::ImageSurface> surface
-        = Cairo::ImageSurface::create(Cairo::FORMAT_ARGB32, w, h);
+        = Cairo::ImageSurface::create(Cairo::Surface::Format::ARGB32, w, h);
     gegl_node_blit(priv->m_scale, 1.0, &roi, format,
                    (void*)surface->get_data(), surface->get_stride(),
                    (GeglBlitFlags)(GEGL_BLIT_CACHE | GEGL_BLIT_DIRTY));
diff --git a/src/ncr/image.hpp b/src/ncr/image.hpp
index ed609f8..7cc9a2c 100644
--- a/src/ncr/image.hpp
+++ b/src/ncr/image.hpp
@@ -1,7 +1,7 @@
 /*
  * niepce - ncr/image.h
  *
- * Copyright (C) 2008-2013 Hubert Figuiere
+ * Copyright (C) 2008-2022 Hubert Figuière
  *
  * This program is free software; you can redistribute it and/or
  * modify it under the terms of the GNU Lesser General Public
@@ -14,14 +14,12 @@
  * Lesser General Public License for more details.
  *
  * You should have received a copy of the GNU Lesser General Public
- * License along with this program; if not, see 
+ * License along with this program; if not, see
  * <http://www.gnu.org/licenses/>.
  */
 
 
-
-#ifndef _NCR_IMAGE_H_
-#define _NCR_IMAGE_H_
+#pragma once
 
 #include <memory>
 
@@ -86,7 +84,7 @@ public:
 
     /** this signal is emitted each time the
         image is changed. */
-    sigc::signal<void> signal_update;
+    sigc::signal<void(void)> signal_update;
 private:
 
     /** rotate by x degrees (orientation)
@@ -99,9 +97,6 @@ private:
 };
 
 }
-
-#endif
-
 /*
   Local Variables:
   mode:c++
diff --git a/src/niepce/modules/darkroom/darkroommodule.cpp b/src/niepce/modules/darkroom/darkroommodule.cpp
index 27e46d7..c31db1e 100644
--- a/src/niepce/modules/darkroom/darkroommodule.cpp
+++ b/src/niepce/modules/darkroom/darkroommodule.cpp
@@ -1,7 +1,7 @@
 /*
- * niepce - ui/darkroommodule.cpp
+ * niepce - modules/darkroom/darkroommodule.cpp
  *
- * Copyright (C) 2008-2020 Hubert Figuiere
+ * Copyright (C) 2008-2022 Hubert Figuière
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -32,14 +32,15 @@ namespace dr {
 
 DarkroomModule::DarkroomModule(const ui::IModuleShell & shell)
     : m_shell(shell)
-    , m_dr_splitview(Gtk::ORIENTATION_HORIZONTAL)
-    , m_vbox(Gtk::ORIENTATION_VERTICAL)
+    , m_dr_splitview(Gtk::Orientation::HORIZONTAL)
+    , m_vbox(Gtk::Orientation::VERTICAL)
     , m_image(new ncr::Image)
     , m_active(false)
     , m_need_reload(true)
 {
-    m_shell.get_selection_controller()->signal_selected.connect(
-        sigc::mem_fun(*this, &DarkroomModule::on_selected));
+    m_shell.get_selection_controller()->signal_selected.connect([this] (eng::library_id_t id) {
+        this->on_selected(id);
+    });
 }
 
 void DarkroomModule::reload_image()
@@ -107,17 +108,17 @@ Gtk::Widget * DarkroomModule::buildWidget()
     m_imagecanvas = Gtk::manage(new ImageCanvas());
 // TODO set a proper canvas size
 //    m_canvas_scroll.add(*m_imagecanvas);
-    m_vbox.pack_start(*m_imagecanvas, Gtk::PACK_EXPAND_WIDGET);
+    m_vbox.append(*m_imagecanvas);
 
     m_imagecanvas->set_image(m_image);
 
     // build the toolbar.
     auto toolbar = ffi::image_toolbar_new();
-    gtk_box_pack_start(m_vbox.gobj(), GTK_WIDGET(toolbar), false, false, 0);
+    gtk_box_append(m_vbox.gobj(), GTK_WIDGET(toolbar));
 
-    m_dr_splitview.pack1(m_vbox, Gtk::EXPAND);
+    m_dr_splitview.set_start_child(m_vbox);
     m_dock = Gtk::manage(new fwk::Dock());
-    m_dr_splitview.pack2(*m_dock, Gtk::SHRINK);
+    m_dr_splitview.set_end_child(*m_dock);
 
     m_databinders.add_binder(new fwk::ConfigDataBinder<int>(
                                  m_dr_splitview.property_position(),
@@ -126,7 +127,7 @@ Gtk::Widget * DarkroomModule::buildWidget()
 
     m_toolbox_ctrl = ToolboxController::Ptr(new ToolboxController);
     add(m_toolbox_ctrl);
-    m_dock->vbox().pack_start(*m_toolbox_ctrl->buildWidget());
+    m_dock->vbox().append(*m_toolbox_ctrl->buildWidget());
 
     return m_widget;
 }
diff --git a/src/niepce/modules/darkroom/darkroommodule.hpp b/src/niepce/modules/darkroom/darkroommodule.hpp
index 8605b7e..ab4deb8 100644
--- a/src/niepce/modules/darkroom/darkroommodule.hpp
+++ b/src/niepce/modules/darkroom/darkroommodule.hpp
@@ -1,7 +1,7 @@
 /*
  * niepce - modules/darkroom/darkroommodule.hpp
  *
- * Copyright (C) 2008-2017 Hubert Figuière
+ * Copyright (C) 2008-2022 Hubert Figuière
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -17,14 +17,11 @@
  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
  */
 
-
-#ifndef _DARKROOM_MODULE_H__
-#define _DARKROOM_MODULE_H__
+#pragma once
 
 #include <gtkmm/widget.h>
 #include <gtkmm/paned.h>
 #include <gtkmm/box.h>
-#include <gtkmm/actiongroup.h>
 #include <gtkmm/scrolledwindow.h>
 
 #include "fwk/toolkit/controller.hpp"
@@ -84,10 +81,7 @@ private:
     bool                         m_need_reload;
 };
 
-
 }
-
-#endif
 /*
   Local Variables:
   mode:c++
diff --git a/src/niepce/modules/darkroom/dritemwidget.cpp b/src/niepce/modules/darkroom/dritemwidget.cpp
index 619daf2..74bd481 100644
--- a/src/niepce/modules/darkroom/dritemwidget.cpp
+++ b/src/niepce/modules/darkroom/dritemwidget.cpp
@@ -1,7 +1,7 @@
 /*
  * niepce - darkroom/dritem.cpp
  *
- * Copyright (C) 2008-2017 Hubert Figuière
+ * Copyright (C) 2008-2022 Hubert Figuière
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -25,17 +25,17 @@ namespace dr {
 
 DrItemWidget::DrItemWidget(const Glib::ustring & title)
     : fwk::ToolboxItemWidget(title)
-    , m_box(Gtk::ORIENTATION_VERTICAL)
+    , m_box(Gtk::Orientation::VERTICAL)
 {
-    add(m_box);
-    m_box.set_border_width(6);
+    set_child(m_box);
+//    m_box.set_border_width(6);
 }
 
 void DrItemWidget::add_widget(const Glib::ustring & label, Gtk::Widget & w)
 {
-    Gtk::Label *l = manage(new Gtk::Label(label, Gtk::ALIGN_START, Gtk::ALIGN_CENTER));
-    m_box.pack_start(*l, Gtk::PACK_SHRINK);
-    m_box.pack_start(w, Gtk::PACK_SHRINK);
+    Gtk::Label *l = manage(new Gtk::Label(label, Gtk::Align::START, Gtk::Align::CENTER));
+    m_box.append(*l);
+    m_box.append(w);
 }
 
 
diff --git a/src/niepce/modules/darkroom/imagecanvas.cpp b/src/niepce/modules/darkroom/imagecanvas.cpp
index 2cf07f0..79eb057 100644
--- a/src/niepce/modules/darkroom/imagecanvas.cpp
+++ b/src/niepce/modules/darkroom/imagecanvas.cpp
@@ -1,7 +1,7 @@
 /*
- * niepce - darkroom/imagecanvas.cpp
+ * niepce - modules/darkroom/imagecanvas.cpp
  *
- * Copyright (C) 2008-2019 Hubert Figuière
+ * Copyright (C) 2008-2022 Hubert Figuière
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -19,6 +19,7 @@
 
 #include <cairomm/context.h>
 #include <gdkmm.h>
+#include <gtkmm/snapshot.h>
 
 #include "fwk/base/debug.hpp"
 #include "fwk/base/geometry.hpp"
@@ -36,9 +37,11 @@ ImageCanvas::ImageCanvas()
       m_resized(false),
       m_zoom_mode(ZoomMode::FIT)
 {
+    set_draw_func([this] (const Cairo::RefPtr<Cairo::Context>& cr, int w, int h) {
+        this->on_draw(cr, w, h);
+    });
 }
 
-
 void ImageCanvas::set_image(const ncr::Image::Ptr & img)
 {
     m_need_redisplay = true;
@@ -48,7 +51,6 @@ void ImageCanvas::set_image(const ncr::Image::Ptr & img)
         sigc::mem_fun(*this, &ImageCanvas::on_image_reloaded));
 }
 
-
 void ImageCanvas::on_image_reloaded()
 {
     m_need_redisplay = true;
@@ -57,10 +59,7 @@ void ImageCanvas::on_image_reloaded()
 
 void ImageCanvas::invalidate()
 {
-    Glib::RefPtr<Gdk::Window> w = get_window();
-    if(w) {
-        w->invalidate(false);
-    }
+    queue_draw();
 }
 
 double ImageCanvas::_calc_image_scale(int img_w, int img_h)
@@ -74,7 +73,6 @@ double ImageCanvas::_calc_image_scale(int img_w, int img_h)
     return std::min(scale_w, scale_h);
 }
 
-
 void ImageCanvas::_calc_image_frame(int img_w, int img_h,
                                    double & x, double & y,
                                    double & width, double & height)
@@ -87,45 +85,43 @@ void ImageCanvas::_calc_image_frame(int img_w, int img_h,
     y = (b_h - img_h) / 2;
     width = img_w;
     height = img_h;
-//    DBG_OUT("image frame %f %f %f %f", x, y, width, height);  
+//    DBG_OUT("image frame %f %f %f %f", x, y, width, height);
 }
 
-Cairo::RefPtr<Cairo::ImageSurface> ImageCanvas::_get_error_placeholder()
+Glib::RefPtr<Gdk::Paintable> ImageCanvas::_get_error_placeholder()
 {
-    Cairo::RefPtr<Cairo::ImageSurface> s;
+    Glib::RefPtr<Gdk::Texture> s;
     try {
-        auto pixbuf = Gdk::Pixbuf::create_from_resource(
-            "/org/gnome/Niepce/pixmaps/niepce-image-generic.png", -1, -1);
-        s = Gdk::Cairo::create_surface_from_pixbuf(pixbuf, 0);
+        s = Gdk::Texture::create_from_resource(
+            "/org/gnome/Niepce/pixmaps/niepce-image-generic.png");
     }
     catch(...) {
     }
 
-    return s;
+    return std::static_pointer_cast<Gdk::Paintable>(s);
 }
 
-Cairo::RefPtr<Cairo::ImageSurface> ImageCanvas::_get_missing_placeholder()
+Glib::RefPtr<Gdk::Paintable> ImageCanvas::_get_missing_placeholder()
 {
-    Cairo::RefPtr<Cairo::ImageSurface> s;
+    Glib::RefPtr<Gdk::Texture> s;
     try {
-        auto pixbuf = Gdk::Pixbuf::create_from_resource(
-            "/org/gnome/Niepce/pixmaps/niepce-image-missing.png", -1, -1);
-        s = Gdk::Cairo::create_surface_from_pixbuf(pixbuf, 0);
+        s = Gdk::Texture::create_from_resource(
+            "/org/gnome/Niepce/pixmaps/niepce-image-missing.png");
     }
     catch(...) {
     }
 
-    return s;
+    return std::static_pointer_cast<Gdk::Paintable>(s);
 }
 
-
-void ImageCanvas::on_size_allocate(Gtk::Allocation &   allocation)
+void ImageCanvas::on_resize(int x, int y)
 {
     m_resized = true;
-    DrawingArea::on_size_allocate(allocation);
+
+    Gtk::DrawingArea::on_resize(x, y);
 }
 
-bool ImageCanvas::on_draw(const Cairo::RefPtr<Cairo::Context>& context)
+bool ImageCanvas::on_draw(const Cairo::RefPtr<Cairo::Context>& context, int, int)
 {
     // no image, just pass.
     if(!m_image) {
@@ -155,17 +151,9 @@ bool ImageCanvas::on_draw(const Cairo::RefPtr<Cairo::Context>& context)
 
             // query the image.
             img_s = m_image->cairo_surface_for_display();
-        }
-        if(!img_s) {
-            DBG_OUT("no image loaded");
-            if (m_image->get_status() == ncr::Image::Status::NOT_FOUND) {
-                img_s = _get_missing_placeholder();
-            } else {
-                img_s = _get_error_placeholder();
-            }
-            DBG_ASSERT(!!img_s, "img_s not loaded");
-            img_w = img_s->get_width();
-            img_h = img_s->get_height();
+        } else {
+            // XXX fix this
+            img_w = img_h = 32;
         }
 
         int canvas_h, canvas_w;
@@ -179,17 +167,15 @@ bool ImageCanvas::on_draw(const Cairo::RefPtr<Cairo::Context>& context)
         Cairo::RefPtr<Cairo::Context> sc
             = Cairo::Context::create(m_backingstore);
 
-
 //        sc->set_antialias(Cairo::ANTIALIAS_NONE);
 
         // paint the background
         auto ctxt = get_style_context();
         ctxt->context_save();
-        ctxt->set_state(Gtk::STATE_FLAG_NORMAL);
+        ctxt->set_state(Gtk::StateFlags::NORMAL);
         ctxt->render_background(sc, 0, 0, canvas_w, canvas_h);
         ctxt->context_restore();
 
-
         double out_w = (img_w * scale);
         double out_h = (img_h * scale);
         double x = (canvas_w - out_w) / 2;
@@ -200,8 +186,27 @@ bool ImageCanvas::on_draw(const Cairo::RefPtr<Cairo::Context>& context)
         sc->set_source_rgb(0.0, 0.0, 0.0);
         sc->fill();
 
-        sc->set_source(img_s, x, y);
-        sc->paint();
+        if (img_s) {
+            sc->set_source(img_s, x, y);
+            sc->paint();
+        } else  {
+            DBG_OUT("no image loaded");
+            Glib::RefPtr<Gdk::Paintable> icon;
+            if (m_image->get_status() == ncr::Image::Status::NOT_FOUND) {
+                icon = _get_missing_placeholder();
+            } else {
+                icon = _get_error_placeholder();
+            }
+            img_w = icon->get_intrinsic_width();
+            img_h = icon->get_intrinsic_height();
+            auto snapshot = Gtk::Snapshot::create();
+            icon->snapshot(snapshot, img_w, img_h);
+            DBG_ASSERT(!!img_s, "img_s not loaded");
+            GskRenderNode* node = gtk_snapshot_to_node(snapshot->gobj());
+            auto surface = Cairo::Surface::create(m_backingstore, 0, 0, img_w, img_h);
+            gsk_render_node_draw(node, sc->cobj());
+        }
+
 
 //        sc->set_source_rgb(1.0, 1.0, 1.0);
 //        sc->set_line_width(1.0);
diff --git a/src/niepce/modules/darkroom/imagecanvas.hpp b/src/niepce/modules/darkroom/imagecanvas.hpp
index afbf7da..4a9e8a1 100644
--- a/src/niepce/modules/darkroom/imagecanvas.hpp
+++ b/src/niepce/modules/darkroom/imagecanvas.hpp
@@ -1,7 +1,7 @@
 /*
  * niepce - darkroom/imagecanvas.hpp
  *
- * Copyright (C) 2008-2018 Hubert Figuiere
+ * Copyright (C) 2008-2022 Hubert Figuière
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -17,7 +17,7 @@
  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
  */
 
-
+#pragma once
 
 #include <gdk/gdk.h>
 #include <gdkmm/pixbuf.h>
@@ -54,11 +54,12 @@ public:
             return m_zoom_mode;
         }
 protected:
-    virtual void on_size_allocate(Gtk::Allocation & allocation) override;
-    virtual bool on_draw(const Cairo::RefPtr<Cairo::Context>& cr) override;
+    void on_resize(int width, int height) override;
+
 private:
     void invalidate();
 
+    bool on_draw(const Cairo::RefPtr<Cairo::Context>& cr, int, int);
     void on_image_reloaded();
 
     void _calc_image_frame(int img_w, int img_h,
@@ -67,8 +68,8 @@ private:
     double _calc_image_scale(int img_w, int img_h);
     /** cause to "recalulate" the content. */
     void _redisplay();
-    Cairo::RefPtr<Cairo::ImageSurface> _get_error_placeholder();
-    Cairo::RefPtr<Cairo::ImageSurface> _get_missing_placeholder();
+    Glib::RefPtr<Gdk::Paintable> _get_error_placeholder();
+    Glib::RefPtr<Gdk::Paintable> _get_missing_placeholder();
 
     bool                           m_need_redisplay;
     bool                           m_resized;
diff --git a/src/niepce/modules/darkroom/toolboxcontroller.cpp 
b/src/niepce/modules/darkroom/toolboxcontroller.cpp
index 4e0df36..0ac448d 100644
--- a/src/niepce/modules/darkroom/toolboxcontroller.cpp
+++ b/src/niepce/modules/darkroom/toolboxcontroller.cpp
@@ -1,7 +1,7 @@
 /*
  * niepce - darkroom/toolboxcontroller.cpp
  *
- * Copyright (C) 2008-2014 Hubert Figuiere
+ * Copyright (C) 2008-2022 Hubert Figuière
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -54,18 +54,18 @@ ToolboxController::buildWidget()
     DBG_ASSERT(toolbox, "vbox not found.");
 
     item = manage(new DrItemWidget(_("Crop")));
-    toolbox->pack_start(*item, Gtk::PACK_SHRINK);
+    toolbox->append(*item);
     s = Gtk::manage(new fwk::EditableHScale("/org/gnome/Niepce/pixmaps/niepce-transform-rotate.png",
                                             -45.0, 45.0, 0.5));
     item->add_widget(_("Tilt"), *s);
 
     item = manage(new DrItemWidget(_("White balance")));
-    toolbox->pack_start(*item, Gtk::PACK_SHRINK);
+    toolbox->append(*item);
     s = Gtk::manage(new fwk::EditableHScale(0.0, 100.0, 1.0));
     item->add_widget(_("Color temperature"), *s);
 
     item = manage(new DrItemWidget(_("Tone and colour")));
-    toolbox->pack_start(*item, Gtk::PACK_SHRINK);
+    toolbox->append(*item);
     s = Gtk::manage(new fwk::EditableHScale(-5.0, 5.0, 0.1));
 //    s->signal_value_changed().connect();
     item->add_widget(_("Exposure"), *s);
diff --git a/src/niepce/modules/map/mapmodule.cpp b/src/niepce/modules/map/mapmodule.cpp
index f62d7fd..98b68f1 100644
--- a/src/niepce/modules/map/mapmodule.cpp
+++ b/src/niepce/modules/map/mapmodule.cpp
@@ -52,14 +52,14 @@ Gtk::Widget * MapModule::buildWidget()
         return m_widget;
     }
 
-    m_box = Gtk::manage(new Gtk::Box(Gtk::ORIENTATION_VERTICAL));
+    m_box = Gtk::manage(new Gtk::Box(Gtk::Orientation::VERTICAL));
     m_widget = m_box;
 
     m_map = fwk::MapController::Ptr(new fwk::MapController());
     add(m_map);
     auto w = m_map->buildWidget();
     if (w) {
-        m_box->pack_start(*w);
+        m_box->append(*w);
     }
 
     return m_widget;
diff --git a/src/niepce/modules/map/mapmodule.hpp b/src/niepce/modules/map/mapmodule.hpp
index 359351d..0d2e37d 100644
--- a/src/niepce/modules/map/mapmodule.hpp
+++ b/src/niepce/modules/map/mapmodule.hpp
@@ -1,7 +1,7 @@
 /*
  * niepce - modules/map/mapmodule.hpp
  *
- * Copyright (C) 2014 Hubert Figuiere
+ * Copyright (C) 2014-2022 Hubert Figuiere
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -17,13 +17,10 @@
  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
  */
 
-
-#ifndef _MAP_MODULE_H__
-#define _MAP_MODULE_H__
+#pragma once
 
 #include <gtkmm/widget.h>
 #include <gtkmm/box.h>
-#include <gtkmm/actiongroup.h>
 
 #include "fwk/toolkit/controller.hpp"
 #include "fwk/toolkit/mapcontroller.hpp"
@@ -64,10 +61,7 @@ private:
     bool                         m_active;
 };
 
-
 }
-
-#endif
 /*
   Local Variables:
   mode:c++
diff --git a/src/niepce/notificationcenter.hpp b/src/niepce/notificationcenter.hpp
index f96f807..0e0fc54 100644
--- a/src/niepce/notificationcenter.hpp
+++ b/src/niepce/notificationcenter.hpp
@@ -1,7 +1,7 @@
 /*
  * niepce - niepce/notificationcenter.hpp
  *
- * Copyright (C) 2009-2019 Hubert Figuière
+ * Copyright (C) 2009-2022 Hubert Figuière
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -41,7 +41,7 @@ public:
       return nc;
     }
 
-  sigc::signal<void, const eng::LibNotification &> signal_lib_notification;
+  sigc::signal<void(const eng::LibNotification &)> signal_lib_notification;
 
   const std::shared_ptr<ffi::LcChannel>& get_channel() const
     { return m_channel; };
diff --git a/src/niepce/ui/dialogs/editlabels.cpp b/src/niepce/ui/dialogs/editlabels.cpp
index 4a34515..f7738c9 100644
--- a/src/niepce/ui/dialogs/editlabels.cpp
+++ b/src/niepce/ui/dialogs/editlabels.cpp
@@ -1,7 +1,7 @@
 /*
  * niepce - niepce/ui/dialogs/editlabels.cpp
  *
- * Copyright (C) 2009-2017 Hubert Figuière
+ * Copyright (C) 2009-2022 Hubert Figuière
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -17,7 +17,6 @@
  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
  */
 
-
 #include <algorithm>
 
 #include <boost/format.hpp>
@@ -56,17 +55,15 @@ void EditLabels::setup_widget()
 
     const char * colour_fmt = "colorbutton%1%";
     const char * value_fmt = "value%1%";
-    for(size_t i = 0; i < 5; i++) {
+    for (size_t i = 0; i < 5; i++) {
         bool has_label = m_labels.size() > i;
 
         Gtk::ColorButton *colourbutton;
         Gtk::Entry *labelentry;
 
-        _builder->get_widget(str(boost::format(colour_fmt) % (i+1)), colourbutton);
-        m_colours[i] = colourbutton;
-        _builder->get_widget(str(boost::format(value_fmt) % (i+1)), labelentry);
+        m_colours[i] = colourbutton = _builder->get_widget<Gtk::ColorButton>(str(boost::format(colour_fmt) % 
(i+1)));
         DBG_ASSERT(labelentry, "couldn't find label");
-        m_entries[i] = labelentry;
+        m_entries[i] = labelentry = _builder->get_widget<Gtk::Entry>(str(boost::format(value_fmt) % (i+1)));
 
         if(has_label) {
             Gdk::RGBA colour = fwk::rgbcolour_to_gdkcolor(
diff --git a/src/niepce/ui/dialogs/importdialog.cpp b/src/niepce/ui/dialogs/importdialog.cpp
index 354ce30..d1c0e42 100644
--- a/src/niepce/ui/dialogs/importdialog.cpp
+++ b/src/niepce/ui/dialogs/importdialog.cpp
@@ -1,7 +1,7 @@
 /*
  * niepce - niepce/ui/dialogs/importdialog.cpp
  *
- * Copyright (C) 2008-2017 Hubert Figuière
+ * Copyright (C) 2008-2022 Hubert Figuière
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -56,7 +56,7 @@ ImportDialog::ImportDialog()
     fwk::Configuration & cfg = fwk::Application::app()->config();
     m_base_dest_dir = cfg.getValue("base_import_dest_dir",
                                    Glib::get_user_special_dir(
-                                       Glib::USER_DIRECTORY_PICTURES));
+                                       Glib::UserDirectory::PICTURES));
     DBG_OUT("base_dest_dir set to %s", m_base_dest_dir.c_str());
 }
 
@@ -69,7 +69,6 @@ void ImportDialog::add_importer_ui(IImporterUI& importer)
     m_import_source_combo->append(importer.id(), importer.name());
     Gtk::Widget* importer_widget = importer.setup_widget(
         std::static_pointer_cast<Frame>(shared_from_this()));
-    importer_widget->show_all();
     m_importer_ui_stack->add(*importer_widget, importer.id());
     importer.set_source_selected_callback(
         [this] (const std::string& source, const std::string& dest_dir) {
@@ -86,16 +85,18 @@ void ImportDialog::setup_widget()
     fwk::Configuration & cfg = fwk::Application::app()->config();
 
     Glib::RefPtr<Gtk::Builder> a_builder = builder();
-    a_builder->get_widget("date_tz_combo", m_date_tz_combo);
-    a_builder->get_widget("ufraw_import_check", m_ufraw_import_check);
-    a_builder->get_widget("rawstudio_import_check", m_rawstudio_import_check);
-    a_builder->get_widget("destinationFolder", m_destination_folder);
+    m_date_tz_combo = a_builder->get_widget<Gtk::ComboBox>("date_tz_combo");
+    m_ufraw_import_check = a_builder->get_widget<Gtk::CheckButton>("ufraw_import_check");
+    m_rawstudio_import_check = a_builder->get_widget<Gtk::CheckButton>("rawstudio_import_check");
+    m_destination_folder = a_builder->get_widget<Gtk::Entry>("destinationFolder");
 
     // Sources
-    a_builder->get_widget("importer_ui_stack", m_importer_ui_stack);
-    a_builder->get_widget("import_source_combo", m_import_source_combo);
+    m_importer_ui_stack = a_builder->get_widget<Gtk::Stack>("importer_ui_stack");
+    m_import_source_combo = a_builder->get_widget<Gtk::ComboBoxText>("import_source_combo");
     m_import_source_combo->signal_changed()
-      .connect(sigc::mem_fun(*this, &ImportDialog::import_source_changed));
+        .connect([this]() {
+            this->import_source_changed();
+        });
 
     std::shared_ptr<IImporterUI> importer = std::make_shared<DirectoryImporterUI>();
     m_importers[importer->id()] = importer;
@@ -108,15 +109,14 @@ void ImportDialog::setup_widget()
     m_import_source_combo->set_active_id(last_importer);
 
     // Metadata pane.
-    a_builder->get_widget("attributes_scrolled", m_attributes_scrolled);
+    m_attributes_scrolled = a_builder->get_widget<Gtk::ScrolledWindow>("attributes_scrolled");
     m_metadata_pane = MetaDataPaneController::Ptr(new MetaDataPaneController);
     auto w = m_metadata_pane->buildWidget();
     add(m_metadata_pane);
-    m_attributes_scrolled->add(*w);
-    w->show_all();
+    m_attributes_scrolled->set_child(*w);
 
     // Gridview of previews.
-    a_builder->get_widget("images_list_scrolled", m_images_list_scrolled);
+    m_images_list_scrolled = a_builder->get_widget<Gtk::ScrolledWindow>("images_list_scrolled");
     m_images_list_model = Gtk::ListStore::create(m_grid_columns);
     m_gridview = Gtk::manage(
         Glib::wrap(GTK_ICON_VIEW(ffi::npc_image_grid_view_new(
@@ -124,14 +124,15 @@ void ImportDialog::setup_widget()
     m_gridview->set_pixbuf_column(m_grid_columns.pixbuf);
     m_gridview->set_text_column(m_grid_columns.filename);
     m_gridview->set_item_width(100);
-    m_gridview->show();
-    m_images_list_scrolled->add(*m_gridview);
-    m_images_list_scrolled->set_policy(Gtk::POLICY_AUTOMATIC, Gtk::POLICY_AUTOMATIC);
-
-    m_previews_to_import.connect(
-      sigc::mem_fun(this, &ImportDialog::preview_received));
-    m_files_to_import.connect(
-      sigc::mem_fun(this, &ImportDialog::append_files_to_import));
+    m_images_list_scrolled->set_child(*m_gridview);
+    m_images_list_scrolled->set_policy(Gtk::PolicyType::AUTOMATIC, Gtk::PolicyType::AUTOMATIC);
+
+    m_previews_to_import.connect([this]() {
+        this->preview_received();
+    });
+    m_files_to_import.connect([this]() {
+        this->append_files_to_import();
+    });
 
     m_is_setup = true;
 }
diff --git a/src/niepce/ui/dialogs/importdialog.hpp b/src/niepce/ui/dialogs/importdialog.hpp
index d4cc2b7..b9ee2ec 100644
--- a/src/niepce/ui/dialogs/importdialog.hpp
+++ b/src/niepce/ui/dialogs/importdialog.hpp
@@ -1,7 +1,7 @@
 /*
  * niepce - niepce/ui/dialogs/importdialog.h
  *
- * Copyright (C) 2008-2017 Hubert Figuière
+ * Copyright (C) 2008-2022 Hubert Figuière
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -17,8 +17,6 @@
  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
  */
 
-
-
 #pragma once
 
 #include <list>
@@ -112,7 +110,7 @@ private:
     Gtk::ScrolledWindow *m_images_list_scrolled;
     PreviewGridModel m_grid_columns;
     Glib::RefPtr<Gtk::ListStore> m_images_list_model;
-    std::map<std::string, Gtk::TreeIter> m_images_list_map;
+    std::map<std::string, Gtk::TreeModel::iterator> m_images_list_map;
 
     Gtk::IconView *m_gridview;
 
diff --git a/src/niepce/ui/dialogs/importers/cameraimporterui.cpp 
b/src/niepce/ui/dialogs/importers/cameraimporterui.cpp
index fbaf50f..dbf99a5 100644
--- a/src/niepce/ui/dialogs/importers/cameraimporterui.cpp
+++ b/src/niepce/ui/dialogs/importers/cameraimporterui.cpp
@@ -2,7 +2,7 @@
 /*
  * niepce - ui/dialogs/importer/cameraimporterui.cpp
  *
- * Copyright (C) 2017 Hubert Figuière
+ * Copyright (C) 2017-2022 Hubert Figuière
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -41,14 +41,13 @@ CameraImporterUI::CameraImporterUI()
 
 Gtk::Widget* CameraImporterUI::setup_widget(const fwk::Frame::Ptr&)
 {
-    Gtk::Grid* main_widget;
     m_builder = Gtk::Builder::create_from_resource("/org/gnome/Niepce/ui/cameraimporterui.ui",
                                                    "main_widget");
-    m_builder->get_widget("main_widget", main_widget);
-    m_builder->get_widget("select_camera_btn", m_select_camera_btn);
+    Gtk::Grid* main_widget = m_builder->get_widget<Gtk::Grid>("main_widget");
+    m_select_camera_btn = m_builder->get_widget<Gtk::Button>("select_camera_btn");
     m_select_camera_btn->signal_clicked()
         .connect(sigc::mem_fun(*this, &CameraImporterUI::select_camera));
-    m_builder->get_widget("camera_list_combo", m_camera_list_combo);
+    m_camera_list_combo = m_builder->get_widget<Gtk::ComboBoxText>("camera_list_combo");
 
     fwk::GpDeviceList::obj().detect();
 
diff --git a/src/niepce/ui/dialogs/importers/directoryimporterui.cpp 
b/src/niepce/ui/dialogs/importers/directoryimporterui.cpp
index 8ba6a3c..e11e43c 100644
--- a/src/niepce/ui/dialogs/importers/directoryimporterui.cpp
+++ b/src/niepce/ui/dialogs/importers/directoryimporterui.cpp
@@ -2,7 +2,7 @@
 /*
  * niepce - ui/dialogs/importer/directoryimporterui.cpp
  *
- * Copyright (C) 2017 Hubert Figuière
+ * Copyright (C) 2017-2022 Hubert Figuière
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -39,15 +39,13 @@ DirectoryImporterUI::DirectoryImporterUI()
 Gtk::Widget* DirectoryImporterUI::setup_widget(const fwk::Frame::Ptr& frame)
 {
     m_frame = frame;
-    Gtk::Button* select_directories = nullptr;
     m_builder = Gtk::Builder::create_from_resource("/org/gnome/Niepce/ui/directoryimporterui.ui",
                                                    "main_widget");
-    Gtk::Box* main_widget = nullptr;
-    m_builder->get_widget("main_widget", main_widget);
-    m_builder->get_widget("select_directories", select_directories);
+    Gtk::Box* main_widget = m_builder->get_widget<Gtk::Box>("main_widget");
+    Gtk::Button* select_directories = m_builder->get_widget<Gtk::Button>("select_directories");
     select_directories->signal_clicked().connect(
         sigc::mem_fun(*this, &DirectoryImporterUI::do_select_directories));
-    m_builder->get_widget("directory_name", m_directory_name);
+    m_directory_name = m_builder->get_widget<Gtk::Label>("directory_name");
     return main_widget;
 }
 
@@ -67,22 +65,24 @@ std::string DirectoryImporterUI::select_source()
     {
         auto frame = m_frame.lock();
         Gtk::FileChooserDialog dialog(frame->gtkWindow(), _("Import picture folder"),
-                                      Gtk::FILE_CHOOSER_ACTION_SELECT_FOLDER);
+                                      Gtk::FileChooser::Action::SELECT_FOLDER);
 
-        dialog.add_button(_("Cancel"), Gtk::RESPONSE_CANCEL);
-        dialog.add_button(_("Select"), Gtk::RESPONSE_OK);
+        dialog.add_button(_("Cancel"), Gtk::ResponseType::CANCEL);
+        dialog.add_button(_("Select"), Gtk::ResponseType::OK);
         dialog.set_select_multiple(false);
 
         std::string last_import_location = cfg.getValue("last_import_location", "");
         if (!last_import_location.empty()) {
-            dialog.set_filename(last_import_location);
+            dialog.set_current_name(last_import_location);
         }
 
-        int result = dialog.run();
+        dialog.show();
+        // XXX fix this Gtk4
+        int result = 0;
         switch(result)
         {
-        case Gtk::RESPONSE_OK:
-            filename = dialog.get_filename();
+        case Gtk::ResponseType::OK:
+            filename = dialog.get_current_name();
             break;
         default:
             break;
diff --git a/src/niepce/ui/dialogs/preferencesdialog.cpp b/src/niepce/ui/dialogs/preferencesdialog.cpp
index 3e22c56..05b5593 100644
--- a/src/niepce/ui/dialogs/preferencesdialog.cpp
+++ b/src/niepce/ui/dialogs/preferencesdialog.cpp
@@ -1,7 +1,7 @@
 /*
  * niepce - ui/dialogs/preferencesdialog.cpp
  *
- * Copyright (C) 2009-2018 Hubert Figuiere
+ * Copyright (C) 2009-2022 Hubert Figuière
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -49,8 +49,7 @@ void PreferencesDialog::setup_widget()
             fwk::DataBinderPool::destroy(binder_pool);
         });
 
-    builder()->get_widget("dark_theme_checkbox", theme_checkbutton);
-
+    theme_checkbutton = builder()->get_widget<Gtk::CheckButton>("dark_theme_checkbox");
     theme_checkbutton->set_active(fwk::Application::app()
                             ->get_use_dark_theme());
     auto app = fwk::Application::app();
@@ -59,12 +58,12 @@ void PreferencesDialog::setup_widget()
             app->set_use_dark_theme(theme_checkbutton->property_active());
         });
 
-    builder()->get_widget("reopen_checkbutton", reopen_checkbutton);
+    reopen_checkbutton = builder()->get_widget<Gtk::CheckButton>("reopen_checkbutton");
     binder_pool->add_binder(new fwk::ConfigDataBinder<bool>(
                                    reopen_checkbutton->property_active(),
                                    fwk::Application::app()->config(),
                                    "reopen_last_catalog"));
-    builder()->get_widget("write_xmp_checkbutton", write_xmp_checkbutton);
+    write_xmp_checkbutton = builder()->get_widget<Gtk::CheckButton>("write_xmp_checkbutton");
     binder_pool->add_binder(new fwk::ConfigDataBinder<bool>(
                                  write_xmp_checkbutton->property_active(),
                                  fwk::Application::app()->config(),
diff --git a/src/niepce/ui/filmstripcontroller.cpp b/src/niepce/ui/filmstripcontroller.cpp
index 555fff1..7b5b9f9 100644
--- a/src/niepce/ui/filmstripcontroller.cpp
+++ b/src/niepce/ui/filmstripcontroller.cpp
@@ -1,7 +1,7 @@
 /*
  * niepce - niepce/ui/filmstripcontroller.cpp
  *
- * Copyright (C) 2008-2020 Hubert Figuière
+ * Copyright (C) 2008-2022 Hubert Figuière
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -39,16 +39,21 @@ Gtk::Widget * FilmStripController::buildWidget()
         return m_widget;
     }
     DBG_ASSERT(static_cast<bool>(m_store), "m_store NULL");
+    m_thumb_strip_view = std::shared_ptr<ffi::ThumbStripView>(
+        ffi::npc_thumb_strip_view_new(
+            GTK_TREE_MODEL(g_object_ref(m_store->gobjmm()->gobj()))),
+        ffi::npc_thumb_strip_view_release);
     // We need to ref m_store since it's held by the RefPtr<>
     // and the ThumbStripView in Rust gets full ownership.
-    m_thumbview = manage(
-        Glib::wrap(GTK_ICON_VIEW(ffi::npc_thumb_strip_view_new(
-                                     GTK_TREE_MODEL(g_object_ref(m_store->gobjmm()->gobj()))))));
+    m_thumbview = Gtk::manage(
+        Glib::wrap(GTK_ICON_VIEW(ffi::npc_thumb_strip_view_get_icon_view(m_thumb_strip_view.get()))));
     GtkWidget *thn = ffi::npc_thumb_nav_new(m_thumbview->gobj(),
                                             ffi::ThumbNavMode::OneRow, true);
-    m_thumbview->set_selection_mode(Gtk::SELECTION_SINGLE);
+    m_thumbview->set_selection_mode(Gtk::SelectionMode::SINGLE);
+    m_thumbview->set_hexpand(true);
     m_widget = Glib::wrap(thn);
     m_widget->set_size_request(-1, 134);
+    m_widget->set_hexpand(true);
     return m_widget;
 }
 
diff --git a/src/niepce/ui/filmstripcontroller.hpp b/src/niepce/ui/filmstripcontroller.hpp
index 0f4a62f..dfe3169 100644
--- a/src/niepce/ui/filmstripcontroller.hpp
+++ b/src/niepce/ui/filmstripcontroller.hpp
@@ -49,6 +49,7 @@ public:
 
 private:
        libraryclient::UIDataProviderWeakPtr m_ui_data_provider;
+       std::shared_ptr<ffi::ThumbStripView> m_thumb_strip_view;
        Gtk::IconView * m_thumbview;
        ImageListStorePtr m_store;
 };
diff --git a/src/niepce/ui/gridviewmodule.cpp b/src/niepce/ui/gridviewmodule.cpp
index 48d0731..a66f76e 100644
--- a/src/niepce/ui/gridviewmodule.cpp
+++ b/src/niepce/ui/gridviewmodule.cpp
@@ -1,7 +1,7 @@
 /*
  * niepce - ui/gridviewmodule.cpp
  *
- * Copyright (C) 2009-2020 Hubert Figuière
+ * Copyright (C) 2009-2022 Hubert Figuière
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -17,7 +17,6 @@
  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
  */
 
-
 #include <gtkmm/liststore.h>
 #include <gtkmm/treestore.h>
 #include <gtkmm/treeselection.h>
@@ -41,7 +40,7 @@ GridViewModule::GridViewModule(const IModuleShell & shell,
   : m_shell(shell)
   , m_model(store)
   , m_librarylistview(nullptr)
-  , m_lib_splitview(Gtk::ORIENTATION_HORIZONTAL)
+  , m_lib_splitview(Gtk::Orientation::HORIZONTAL)
   , m_dock(nullptr)
   , m_context_menu(nullptr)
 {
@@ -117,23 +116,30 @@ Gtk::Widget * GridViewModule::buildWidget()
     return m_widget;
   }
   m_widget = &m_lib_splitview;
+  m_image_grid_view = std::shared_ptr<ffi::ImageGridView>(
+      ffi::npc_image_grid_view_new(
+          GTK_TREE_MODEL(g_object_ref(
+                             m_model->gobjmm()->gobj()))),
+      ffi::npc_image_grid_view_release);
   m_librarylistview = Gtk::manage(
-      Glib::wrap(GTK_ICON_VIEW(ffi::npc_image_grid_view_new(
-                                   GTK_TREE_MODEL(g_object_ref(m_model->gobjmm()->gobj()))))));
-  m_librarylistview->set_selection_mode(Gtk::SELECTION_SINGLE);
+      Glib::wrap(
+          GTK_ICON_VIEW(ffi::npc_image_grid_view_get_icon_view(m_image_grid_view.get())))
+      );
+  m_librarylistview->set_selection_mode(Gtk::SelectionMode::SINGLE);
   m_librarylistview->property_row_spacing() = 0;
   m_librarylistview->property_column_spacing() = 0;
   m_librarylistview->property_spacing() = 0;
   m_librarylistview->property_margin() = 0;
+  m_librarylistview->set_vexpand(true);
 
   auto shell_menu = m_shell.getMenu();
-  m_context_menu = Gtk::manage(new Gtk::Menu(shell_menu));
-  m_context_menu->attach_to_widget(*m_librarylistview);
+  m_context_menu = Gtk::manage(new Gtk::PopoverMenu(shell_menu));
 
-  m_librarylistview->signal_button_press_event()
-      .connect(sigc::mem_fun(*this, &GridViewModule::on_librarylistview_click));
-  m_librarylistview->signal_popup_menu()
-      .connect(sigc::mem_fun(*this, &GridViewModule::on_popup_menu));
+  auto gesture = Gtk::GestureClick::create();
+  m_librarylistview->add_controller(gesture);
+  gesture->signal_pressed().connect([this, gesture] (int, double x, double y) {
+      this->on_librarylistview_click(gesture, x, y);
+  });
 
   // the main cell
   Gtk::CellRenderer* libcell = manage(
@@ -151,24 +157,24 @@ Gtk::Widget * GridViewModule::buildWidget()
   cell_area->add_attribute(*libcell, "status",
                            static_cast<gint>(ffi::ColIndex::FileStatus));
 
-  m_scrollview.add(*m_librarylistview);
-  m_scrollview.set_policy(Gtk::POLICY_AUTOMATIC, Gtk::POLICY_AUTOMATIC);
+  m_scrollview.set_child(*m_librarylistview);
+  m_scrollview.set_policy(Gtk::PolicyType::AUTOMATIC, Gtk::PolicyType::AUTOMATIC);
   m_lib_splitview.set_wide_handle(true);
 
   // build the toolbar
-  auto box = Gtk::manage(new Gtk::Box(Gtk::ORIENTATION_VERTICAL));
-  box->pack_start(m_scrollview);
+  auto box = Gtk::manage(new Gtk::Box(Gtk::Orientation::VERTICAL));
+  box->append(m_scrollview);
   auto toolbar = ffi::image_toolbar_new();
-  gtk_box_pack_end(box->gobj(), GTK_WIDGET(toolbar), false, false, 0);
-  m_lib_splitview.pack1(*box);
+  gtk_box_append(box->gobj(), GTK_WIDGET(toolbar));
+  m_lib_splitview.set_start_child(*box);
 
   m_dock = new fwk::Dock();
   m_metapanecontroller = MetaDataPaneController::Ptr(new MetaDataPaneController);
   m_metapanecontroller->signal_metadata_changed.connect(
       sigc::mem_fun(*this, &GridViewModule::on_metadata_changed));
   add(m_metapanecontroller);
-  m_lib_splitview.pack2(*m_dock);
-  m_dock->vbox().pack_start(*m_metapanecontroller->buildWidget());
+  m_lib_splitview.set_end_child(*m_dock);
+  m_dock->vbox().append(*m_metapanecontroller->buildWidget());
 
   m_databinders.add_binder(new fwk::ConfigDataBinder<int>(
                              m_lib_splitview.property_position(),
@@ -233,38 +239,21 @@ void GridViewModule::on_rating_changed(GtkCellRenderer*, eng::library_id_t /*id*
     self->m_shell.get_selection_controller()->set_rating(rating);
 }
 
-bool GridViewModule::on_popup_menu()
+void GridViewModule::on_librarylistview_click(const Glib::RefPtr<Gtk::GestureClick>& gesture, double x, 
double y)
 {
-    if (m_context_menu && !m_librarylistview->get_selected_items().empty()) {
-        m_context_menu->popup_at_widget(m_librarylistview, Gdk::GRAVITY_CENTER, Gdk::GRAVITY_NORTH, nullptr);
-        return true;
-    }
-    return false;
-}
+    auto button = gesture->get_current_button();
+    if (button == 3 && !m_librarylistview->get_selected_items().empty()) {
+        m_context_menu->set_pointing_to(Gdk::Rectangle(x, y, 1, 1));
+        m_context_menu->popup();
 
-bool GridViewModule::on_librarylistview_click(GdkEventButton *e)
-{
-    GdkEvent* event = (GdkEvent*)e;
-    if (gdk_event_triggers_context_menu(event)
-        && gdk_event_get_event_type(event) == GDK_BUTTON_PRESS
-        && !m_librarylistview->get_selected_items().empty()) {
-        m_context_menu->popup_at_pointer(event);
+        return;
     }
-    double x, y;
-    int bx, by;
-    bx = by = 0;
-    x = e->x;
-    y = e->y;
     Gtk::TreeModel::Path path;
     Gtk::CellRenderer * renderer = nullptr;
     DBG_OUT("click (%f, %f)", x, y);
-    m_librarylistview->convert_widget_to_bin_window_coords(x, y, bx, by);
-    if (m_librarylistview->get_item_at_pos(bx, by, path, renderer)){
+    if (m_librarylistview->get_item_at_pos(x, y, path, renderer)){
         DBG_OUT("found an item");
-
-        return true;
     }
-    return false;
 }
 
 }
diff --git a/src/niepce/ui/gridviewmodule.hpp b/src/niepce/ui/gridviewmodule.hpp
index 756c6ec..80391a8 100644
--- a/src/niepce/ui/gridviewmodule.hpp
+++ b/src/niepce/ui/gridviewmodule.hpp
@@ -1,7 +1,7 @@
 /*
  * niepce - ui/gridviewmodule.hpp
  *
- * Copyright (C) 2009-2020 Hubert Figuière
+ * Copyright (C) 2009-2022 Hubert Figuière
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -19,10 +19,12 @@
 
 #pragma once
 
+#include <gtkmm/gestureclick.h>
 #include <gtkmm/iconview.h>
-#include <gtkmm/scrolledwindow.h>
-#include <gtkmm/paned.h>
 #include <gtkmm/liststore.h>
+#include <gtkmm/paned.h>
+#include <gtkmm/popovermenu.h>
+#include <gtkmm/scrolledwindow.h>
 #include <gtkmm/treestore.h>
 
 #include "fwk/base/propertybag.hpp"
@@ -78,19 +80,19 @@ private:
   void on_metadata_changed(const fwk::PropertyBagPtr&, const fwk::PropertyBagPtr& old);
   static void on_rating_changed(GtkCellRenderer*, eng::library_id_t id, int rating,
                                 gpointer user_data);
-  bool on_popup_menu();
-  bool on_librarylistview_click(GdkEventButton *e);
+  void on_librarylistview_click(const Glib::RefPtr<Gtk::GestureClick>& gesture, double, double);
 
   const IModuleShell &               m_shell;
   ImageListStorePtr m_model;
 
   // library split view
+  std::shared_ptr<ffi::ImageGridView> m_image_grid_view;
   Gtk::IconView* m_librarylistview;
   Gtk::ScrolledWindow          m_scrollview;
   MetaDataPaneController::Ptr  m_metapanecontroller;
   Gtk::Paned                   m_lib_splitview;
   fwk::Dock                   *m_dock;
-  Gtk::Menu* m_context_menu;
+  Gtk::PopoverMenu* m_context_menu;
 };
 
 }
diff --git a/src/niepce/ui/imageliststore.cpp b/src/niepce/ui/imageliststore.cpp
index bbfd421..b88b99b 100644
--- a/src/niepce/ui/imageliststore.cpp
+++ b/src/niepce/ui/imageliststore.cpp
@@ -1,7 +1,7 @@
 /*
  * niepce - ui/imageliststore.cpp
  *
- * Copyright (C) 2008-2020 Hubert Figuière
+ * Copyright (C) 2008-2022 Hubert Figuière
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -43,30 +43,30 @@ ImageListStore::~ImageListStore()
     ffi::npc_image_list_store_delete(m_store);
 }
 
-Gtk::TreeIter ImageListStore::get_iter_from_id(eng::library_id_t id) const
+Gtk::TreeModel::iterator ImageListStore::get_iter_from_id(eng::library_id_t id) const
 {
     if (m_store == nullptr) {
-        return Gtk::TreeIter();
+        return Gtk::TreeModel::iterator();
     }
     auto iter = ffi::npc_image_list_store_get_iter_from_id(m_store, id);
     if (!iter) {
-        return Gtk::TreeIter();
+        return Gtk::TreeModel::iterator();
     }
-    return Gtk::TreeIter(GTK_TREE_MODEL(m_store_wrap->gobj()), iter);
+    return Gtk::TreeModel::iterator(GTK_TREE_MODEL(m_store_wrap->gobj()), iter);
 }
 
-Gtk::TreePath ImageListStore::get_path_from_id(eng::library_id_t id) const
+Gtk::TreeModel::Path ImageListStore::get_path_from_id(eng::library_id_t id) const
 {
     if (m_store) {
-        Gtk::TreeIter iter = get_iter_from_id(id);
+        Gtk::TreeModel::iterator iter = get_iter_from_id(id);
         if (iter) {
             return m_store_wrap->get_path(iter);
         }
     }
-    return Gtk::TreePath();
+    return Gtk::TreeModel::Path();
 }
 
-eng::library_id_t ImageListStore::get_libfile_id_at_path(const Gtk::TreePath &path) const
+eng::library_id_t ImageListStore::get_libfile_id_at_path(const Gtk::TreeModel::Path& path) const
 {
     return ffi::npc_image_list_store_get_file_id_at_path(m_store, path.gobj());
 }
diff --git a/src/niepce/ui/imageliststore.hpp b/src/niepce/ui/imageliststore.hpp
index 6d0bd91..37c1356 100644
--- a/src/niepce/ui/imageliststore.hpp
+++ b/src/niepce/ui/imageliststore.hpp
@@ -1,7 +1,7 @@
 /*
  * niepce - ui/imageliststore.h
  *
- * Copyright (C) 2008-2020 Hubert Figuière
+ * Copyright (C) 2008-2022 Hubert Figuière
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -40,9 +40,9 @@ public:
     ~ImageListStore();
     Glib::RefPtr<Gtk::ListStore> gobjmm() const
         { return m_store_wrap; }
-    Gtk::TreePath get_path_from_id(eng::library_id_t id) const;
-    Gtk::TreeIter get_iter_from_id(eng::library_id_t id) const;
-    eng::library_id_t get_libfile_id_at_path(const Gtk::TreePath& path) const;
+    Gtk::TreeModel::Path get_path_from_id(eng::library_id_t id) const;
+    Gtk::TreeModel::iterator get_iter_from_id(eng::library_id_t id) const;
+    eng::library_id_t get_libfile_id_at_path(const Gtk::TreeModel::Path& path) const;
     eng::LibFilePtr get_file(eng::library_id_t id) const;
     size_t get_count() const
         { return m_store_wrap->children().size(); }
diff --git a/src/niepce/ui/metadatapanecontroller.cpp b/src/niepce/ui/metadatapanecontroller.cpp
index 088d8a2..8c336e7 100644
--- a/src/niepce/ui/metadatapanecontroller.cpp
+++ b/src/niepce/ui/metadatapanecontroller.cpp
@@ -1,7 +1,7 @@
 /*
  * niepce - niepce/ui/metadatapanecontroller.cpp
  *
- * Copyright (C) 2008-2021 Hubert Figuière
+ * Copyright (C) 2008-2022 Hubert Figuière
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -130,7 +130,7 @@ MetaDataPaneController::buildWidget()
     auto current = formats.begin();
     while (current != formats.end()) {
         auto w = Gtk::manage(new fwk::MetaDataWidget(current->section));
-        box->pack_start(*w, Gtk::PACK_SHRINK, 0);
+        box->append(*w);
         w->set_data_format(&*current);
         m_widgets.push_back(w);
         w->signal_metadata_changed.connect(
diff --git a/src/niepce/ui/metadatapanecontroller.hpp b/src/niepce/ui/metadatapanecontroller.hpp
index 9ff7de6..6c53065 100644
--- a/src/niepce/ui/metadatapanecontroller.hpp
+++ b/src/niepce/ui/metadatapanecontroller.hpp
@@ -1,7 +1,7 @@
 /*
- * niepce - ui/metadatapanecontroller.h
+ * niepce - niepce/ui/metadatapanecontroller.h
  *
- * Copyright (C) 2008-2013 Hubert Figuiere
+ * Copyright (C) 2008-2022 Hubert Figuière
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -17,8 +17,7 @@
  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
  */
 
-#ifndef __UI_METADATAPANECONTROLLER_H__
-#define __UI_METADATAPANECONTROLLER_H__
+#pragma once
 
 #include <gtkmm/box.h>
 
@@ -47,7 +46,7 @@ public:
     eng::library_id_t displayed_file() const
         { return m_fileid; }
 
-    sigc::signal<void, const fwk::PropertyBagPtr &, const fwk::PropertyBagPtr &> signal_metadata_changed;
+    sigc::signal<void(const fwk::PropertyBagPtr &, const fwk::PropertyBagPtr &)> signal_metadata_changed;
 private:
     void on_metadata_changed(const fwk::PropertyBagPtr &,
                              const fwk::PropertyBagPtr & old);
@@ -61,9 +60,6 @@ private:
 };
 
 }
-
-
-
 /*
   Local Variables:
   mode:c++
@@ -73,4 +69,3 @@ private:
   fill-column:80
   End:
 */
-#endif
diff --git a/src/niepce/ui/moduleshellwidget.cpp b/src/niepce/ui/moduleshellwidget.cpp
index 76b49ae..262cd7f 100644
--- a/src/niepce/ui/moduleshellwidget.cpp
+++ b/src/niepce/ui/moduleshellwidget.cpp
@@ -1,7 +1,7 @@
 /*
  * niepce - ui/moduleshellwidget.cpp
  *
- * Copyright (C) 2007-2014 Hubert Figuiere
+ * Copyright (C) 2007-2022 Hubert Figuière
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -27,25 +27,23 @@
 namespace ui {
 
 ModuleShellWidget::ModuleShellWidget()
-    : Gtk::Box(Gtk::ORIENTATION_VERTICAL)
-    , m_mainbox(Gtk::ORIENTATION_HORIZONTAL)
-    , m_mainbar(Gtk::ORIENTATION_HORIZONTAL)
+    : Gtk::Box(Gtk::Orientation::VERTICAL)
+    , m_mainbox(Gtk::Orientation::HORIZONTAL)
+    , m_mainbar(Gtk::Orientation::HORIZONTAL)
 {
     set_spacing(4);
-    m_mainbar.set_layout(Gtk::BUTTONBOX_START);
+    // XXX m_mainbar.set_layout(Gtk::BUTTONBOX_START);
     m_mainbar.set_spacing(4);
-    m_menubutton.set_direction(Gtk::ARROW_NONE);
-    auto icon = Gtk::manage(new Gtk::Image());
-    icon->set_from_icon_name("view-more-symbolic", Gtk::ICON_SIZE_BUTTON);
-    m_menubutton.set_image(*icon);
-    m_mainbox.pack_end(m_menubutton, Gtk::PACK_SHRINK);
-    m_mainbox.pack_start(m_mainbar, Gtk::PACK_EXPAND_WIDGET);
-    pack_start(m_mainbox, Gtk::PACK_SHRINK);
+    m_menubutton.set_direction(Gtk::ArrowType::NONE);
+    m_menubutton.set_icon_name("view-more-symbolic");
+    m_mainbox.append(m_menubutton);
+    m_mainbox.append(m_mainbar);
+    append(m_mainbox);
 
-    m_mainbox.pack_start(m_switcher);
+    m_mainbox.append(m_switcher);
     m_stack.property_visible_child().signal_changed().connect(
         sigc::mem_fun(*this, &ModuleShellWidget::stack_changed));
-    pack_start(m_stack);
+    append(m_stack);
 
     m_switcher.set_stack(m_stack);
     m_current_module = m_stack.get_visible_child_name();
@@ -67,7 +65,7 @@ void ModuleShellWidget::stack_changed()
     signal_activated(m_current_module);
 }
 
-void ModuleShellWidget::activatePage(const std::string & name)
+void ModuleShellWidget::activatePage(const std::string& name)
 {
     if (m_current_module != name) {
         m_stack.set_visible_child(name);
diff --git a/src/niepce/ui/moduleshellwidget.hpp b/src/niepce/ui/moduleshellwidget.hpp
index 7eb5227..e2e86c9 100644
--- a/src/niepce/ui/moduleshellwidget.hpp
+++ b/src/niepce/ui/moduleshellwidget.hpp
@@ -1,7 +1,7 @@
 /*
  * niepce - ui/moduleshellwidget.hpp
  *
- * Copyright (C) 2007-2014 Hubert Figuiere
+ * Copyright (C) 2007-2022 Hubert Figuière
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -17,14 +17,12 @@
  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
  */
 
-#ifndef __MODULE_SHELL_WIDGET_H__
-#define __MODULE_SHELL_WIDGET_H__
+#pragma once
 
 #include <vector>
 #include <utility>
 
 #include <gtkmm/box.h>
-#include <gtkmm/buttonbox.h>
 #include <gtkmm/menubutton.h>
 #include <gtkmm/stack.h>
 #include <gtkmm/stackswitcher.h>
@@ -49,15 +47,13 @@ public:
 
     Gtk::MenuButton & getMenuButton()
         { return m_menubutton; }
-//    Gtk::Stack* getStack() const
-//        { return m_stack; }
 
-    sigc::signal<void, const std::string &> signal_activated;
-    sigc::signal<void, const std::string &> signal_deactivated;
+    sigc::signal<void(const std::string &)> signal_activated;
+    sigc::signal<void(const std::string &)> signal_deactivated;
 
 private:
-    Gtk::Box                m_mainbox;
-    Gtk::ButtonBox          m_mainbar;
+    Gtk::Box m_mainbox;
+    Gtk::Box m_mainbar;
     Gtk::MenuButton         m_menubutton;
     Gtk::Stack              m_stack;
     Gtk::StackSwitcher      m_switcher;
@@ -67,9 +63,6 @@ private:
 };
 
 }
-
-
-#endif
 /*
   Local Variables:
   mode:c++
diff --git a/src/niepce/ui/niepceapplication.cpp b/src/niepce/ui/niepceapplication.cpp
index 822551c..b48232f 100644
--- a/src/niepce/ui/niepceapplication.cpp
+++ b/src/niepce/ui/niepceapplication.cpp
@@ -1,7 +1,7 @@
 /*
  * niepce - ui/niepceapplication.cpp
  *
- * Copyright (C) 2007-2019 Hubert Figuière
+ * Copyright (C) 2007-2022 Hubert Figuière
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -75,12 +75,12 @@ void NiepceApplication::on_about()
     dlg.set_program_name("Niepce Digital");
     dlg.set_version(VERSION);
     dlg.set_logo_icon_name("org.gnome.Niepce");
-    dlg.set_license_type(Gtk::LICENSE_GPL_3_0);
+    dlg.set_license_type(Gtk::License::GPL_3_0);
     dlg.set_comments(Glib::ustring(_("A digital photo application.\n\n"
                                      "Build options: ")) +
                      NIEPCE_BUILD_CONFIG);
     dlg.set_transient_for(m_main_frame.lock()->gtkWindow());
-    dlg.run();
+    dlg.show();
 }
 
 void NiepceApplication::on_action_preferences()
diff --git a/src/niepce/ui/niepcewindow.cpp b/src/niepce/ui/niepcewindow.cpp
index e9ab2b9..d87aff7 100644
--- a/src/niepce/ui/niepcewindow.cpp
+++ b/src/niepce/ui/niepcewindow.cpp
@@ -1,7 +1,7 @@
 /*
  * niepce - ui/niepcewindow.cpp
  *
- * Copyright (C) 2007-2018 Hubert Figuière
+ * Copyright (C) 2007-2022 Hubert Figuière
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -24,7 +24,6 @@
 #include <giomm/menu.h>
 #include <gtkmm/window.h>
 #include <gtkmm/accelkey.h>
-#include <gtkmm/action.h>
 #include <gtkmm/separator.h>
 #include <gtkmm/filechooserdialog.h>
 
@@ -56,19 +55,19 @@ namespace ui {
 
 NiepceWindow::NiepceWindow()
     : fwk::AppFrame("mainWindow-frame")
-    , m_vbox(Gtk::ORIENTATION_VERTICAL)
-    , m_hbox(Gtk::ORIENTATION_HORIZONTAL)
+    , m_vbox(Gtk::Orientation::VERTICAL)
+    , m_hbox(Gtk::Orientation::HORIZONTAL)
 {
     // headerbar.
     Gtk::HeaderBar *header = Gtk::manage(new Gtk::HeaderBar);
-    header->set_show_close_button(true);
-    header->set_has_subtitle(true);
+    header->set_show_title_buttons(true);
+    // XXX header->set_has_subtitle(true);
 
     Gtk::MenuButton* menu_btn = Gtk::manage(new Gtk::MenuButton);
-    menu_btn->set_direction(Gtk::ARROW_NONE);
+    menu_btn->set_direction(Gtk::ArrowType::NONE);
     m_main_menu = Gio::Menu::create();
     menu_btn->set_menu_model(m_main_menu);
-    header->pack_end(*menu_btn);
+    header->pack_start(*menu_btn);
 
     // Undo redo buttons
     Gtk::Box *button_box = Gtk::manage(new Gtk::Box);
@@ -76,13 +75,13 @@ NiepceWindow::NiepceWindow()
     Gtk::Button *undo_button = Gtk::manage(new Gtk::Button);
     undo_button->set_image_from_icon_name("edit-undo-symbolic");
     undo_button->set_label(_("Undo"));
-    undo_button->set_always_show_image(true);
+    // XXX undo_button->set_always_show_image(true);
     gtk_actionable_set_action_name(GTK_ACTIONABLE(undo_button->gobj()), "win.Undo");
     Gtk::Button *redo_button = Gtk::manage(new Gtk::Button);
     redo_button->set_image_from_icon_name("edit-redo-symbolic");
     gtk_actionable_set_action_name(GTK_ACTIONABLE(redo_button->gobj()), "win.Redo");
-    button_box->pack_start(*undo_button, false, false, 0);
-    button_box->pack_start(*redo_button, false, false, 0);
+    button_box->append(*undo_button);
+    button_box->append(*redo_button);
     header->pack_start(*button_box);
 
     Gtk::Button *import_button = Gtk::manage(new Gtk::Button);
@@ -118,38 +117,40 @@ NiepceWindow::_createModuleShell()
                      *m_moduleshell->get_gridview(),
                      &GridViewModule::on_lib_notification));
     m_notifcenter->signal_lib_notification
-        .connect(sigc::mem_fun(
-                     m_moduleshell->get_list_store().get(),
-                     &ImageListStore::on_lib_notification));
+        .connect([this] (const eng::LibNotification& notification) {
+            m_moduleshell->get_list_store()->on_lib_notification(notification);
+        });
     m_notifcenter->signal_lib_notification
-        .connect(sigc::mem_fun(
-                     *m_moduleshell->get_map_module(),
-                     &mapm::MapModule::on_lib_notification));
+        .connect([this] (const eng::LibNotification& notification) {
+            m_moduleshell->get_map_module()->on_lib_notification(notification);
+        });
 
     // workspace treeview
     auto workspace_actions = Gio::SimpleActionGroup::create();
     gtkWindow().insert_action_group("workspace", workspace_actions);
     m_workspacectrl = WorkspaceController::Ptr(new WorkspaceController(workspace_actions));
-    m_workspacectrl->libtree_selection_changed.connect(
-        sigc::mem_fun(*m_moduleshell, &ModuleShell::on_content_will_change));
+    m_workspacectrl->libtree_selection_changed.connect([this] {
+        m_moduleshell->on_content_will_change();
+    });
 
     m_notifcenter->signal_lib_notification
-        .connect(sigc::mem_fun(*m_workspacectrl,
-                               &WorkspaceController::on_lib_notification));
+        .connect([this] (const eng::LibNotification& notification) {
+            m_workspacectrl->on_lib_notification(notification);
+        });
     add(m_workspacectrl);
 
-    m_hbox.set_border_width(4);
+    // m_hbox.set_border_width(4);
     m_hbox.set_wide_handle(true);
-    m_hbox.pack1(*(m_workspacectrl->buildWidget()), Gtk::EXPAND);
-    m_hbox.pack2(*(m_moduleshell->buildWidget()), Gtk::EXPAND);
+    m_hbox.set_start_child(*(m_workspacectrl->buildWidget()));
+    m_hbox.set_end_child(*(m_moduleshell->buildWidget()));
     m_databinders.add_binder(new fwk::ConfigDataBinder<int>(m_hbox.property_position(),
                                                                   Application::app()->config(),
                                                                   "workspace_splitter"));
 
-    static_cast<Gtk::Window*>(m_widget)->add(m_vbox);
+    static_cast<Gtk::Window*>(m_widget)->set_child(m_vbox);
 
     static_cast<Gtk::ApplicationWindow&>(gtkWindow()).set_show_menubar(true);
-    m_vbox.pack_start(m_hbox);
+    m_vbox.append(m_hbox);
 
 
     SelectionController::Ptr selection_controller = m_moduleshell->get_selection_controller();
@@ -159,16 +160,13 @@ NiepceWindow::_createModuleShell()
             libraryclient::UIDataProviderWeakPtr(m_moduleshell->get_ui_data_provider())));
     add(m_filmstrip);
 
-    m_vbox.pack_start(*(m_filmstrip->buildWidget()), Gtk::PACK_SHRINK);
+    m_vbox.append(*(m_filmstrip->buildWidget()));
 
     // status bar
-    m_vbox.pack_start(m_statusBar, Gtk::PACK_SHRINK);
+    m_vbox.append(m_statusBar);
     m_statusBar.push(Glib::ustring(_("Ready")));
 
     selection_controller->add_selectable(m_filmstrip);
-
-    m_vbox.show_all_children();
-    m_vbox.show();
 }
 
 
@@ -194,7 +192,6 @@ NiepceWindow::buildWidget()
         sigc::mem_fun(*this, &NiepceWindow::on_lib_notification));
 
     win.set_size_request(600, 400);
-    win.show_all_children();
     on_open_library();
     return &win;
 }
@@ -341,17 +338,19 @@ std::string NiepceWindow::prompt_open_library()
 {
     std::string libMoniker;
     Gtk::FileChooserDialog dialog(gtkWindow(), _("Open catalog"),
-                                  Gtk::FILE_CHOOSER_ACTION_SELECT_FOLDER);
-    dialog.add_button(_("Cancel"), Gtk::RESPONSE_CANCEL);
-    dialog.add_button(_("Open"), Gtk::RESPONSE_OK);
+                                  Gtk::FileChooser::Action::SELECT_FOLDER);
+    dialog.add_button(_("Cancel"), Gtk::ResponseType::CANCEL);
+    dialog.add_button(_("Open"), Gtk::ResponseType::OK);
 
-    int result = dialog.run();
+    int result = 0;
+    // XXX todo
+    dialog.show();
     Glib::ustring libraryToCreate;
     switch(result)
     {
-    case Gtk::RESPONSE_OK: {
+    case Gtk::ResponseType::OK: {
         Configuration & cfg = Application::app()->config();
-        libraryToCreate = dialog.get_filename();
+        libraryToCreate = dialog.get_current_name();
         // pass it to the library
         libMoniker = "local:";
         libMoniker += libraryToCreate.c_str();
diff --git a/src/niepce/ui/niepcewindow.hpp b/src/niepce/ui/niepcewindow.hpp
index e2ee763..8616252 100644
--- a/src/niepce/ui/niepcewindow.hpp
+++ b/src/niepce/ui/niepcewindow.hpp
@@ -1,7 +1,7 @@
 /*
  * niepce - ui/niepcewindow.hpp
  *
- * Copyright (C) 2007-2018 Hubert Figuiere
+ * Copyright (C) 2007-2022 Hubert Figuière
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -17,15 +17,11 @@
  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
  */
 
-
-#ifndef _UI_NIEPCEWINDOW_H_
-#define _UI_NIEPCEWINDOW_H_
-
+#pragma once
 
 #include <giomm/simpleactiongroup.h>
 #include <gtkmm/treemodel.h>
 #include <gtkmm/box.h>
-#include <gtkmm/menubar.h>
 #include <gtkmm/statusbar.h>
 #include <gtkmm/paned.h>
 
@@ -89,10 +85,6 @@ private:
 };
 
 }
-
-
-#endif
-
 /*
   Local Variables:
   mode:c++
diff --git a/src/niepce/ui/selectioncontroller.cpp b/src/niepce/ui/selectioncontroller.cpp
index 0c641b3..ff81c60 100644
--- a/src/niepce/ui/selectioncontroller.cpp
+++ b/src/niepce/ui/selectioncontroller.cpp
@@ -174,7 +174,7 @@ void SelectionController::rotate(int angle)
     DBG_OUT("angle = %d", angle);
     auto selection = get_selection();
     if(selection >= 0) {
-        Gtk::TreeIter iter = m_imageliststore->get_iter_from_id(selection);
+        Gtk::TreeModel::iterator iter = m_imageliststore->get_iter_from_id(selection);
         if(iter) {
             // @todo
         }
diff --git a/src/niepce/ui/selectioncontroller.hpp b/src/niepce/ui/selectioncontroller.hpp
index 0a105fe..4f48bf4 100644
--- a/src/niepce/ui/selectioncontroller.hpp
+++ b/src/niepce/ui/selectioncontroller.hpp
@@ -1,7 +1,7 @@
 /*
  * niepce - ui/selectioncontroller.h
  *
- * Copyright (C) 2008-2014 Hubert Figuiere
+ * Copyright (C) 2008-2022 Hubert Figuière
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -17,9 +17,7 @@
  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
  */
 
-
-#ifndef _UI_SELECTIONCONTROLLER_H__
-#define _UI_SELECTIONCONTROLLER_H__
+#pragma once
 
 #include <memory>
 
@@ -76,10 +74,10 @@ public:
         { return m_imageliststore; }
 
     // the signal to call when selection is changed.
-    sigc::signal<void, eng::library_id_t> signal_selected;
+    sigc::signal<void(eng::library_id_t)> signal_selected;
 
     // signal for when the item is activated (ie double-click)
-    sigc::signal<void, eng::library_id_t> signal_activated;
+    sigc::signal<void(eng::library_id_t)> signal_activated;
 
     /////////
     /** select the previous image. Emit the signal */
@@ -145,5 +143,3 @@ private:
   fill-column:80
   End:
 */
-
-#endif
diff --git a/src/niepce/ui/workspacecontroller.cpp b/src/niepce/ui/workspacecontroller.cpp
index ef3c609..897c405 100644
--- a/src/niepce/ui/workspacecontroller.cpp
+++ b/src/niepce/ui/workspacecontroller.cpp
@@ -1,7 +1,7 @@
 /*
- * niepce - ui/workspacecontroller.cpp
+ * niepce - niepce/ui/workspacecontroller.cpp
  *
- * Copyright (C) 2007-2020 Hubert Figuière
+ * Copyright (C) 2007-2022 Hubert Figuière
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -21,6 +21,7 @@
 
 #include <gtkmm/icontheme.h>
 #include <gtkmm/box.h>
+#include <gtkmm/gestureclick.h>
 #include <gtkmm/iconview.h>
 #include <gtkmm/image.h>
 
@@ -47,7 +48,7 @@ namespace ui {
 WorkspaceController::WorkspaceController(const Glib::RefPtr<Gio::SimpleActionGroup>& action_group)
     : fwk::UiController()
     , m_action_group(action_group)
-    , m_vbox(Gtk::ORIENTATION_VERTICAL)
+    , m_vbox(Gtk::Orientation::VERTICAL)
     , m_context_menu(nullptr)
 {
     static struct _Icons {
@@ -66,13 +67,12 @@ WorkspaceController::WorkspaceController(const Glib::RefPtr<Gio::SimpleActionGro
     int i = 0;
     while(icons[i].icon_name) {
         try {
-            m_icons[icons[i].icon_id] = icon_theme->load_icon(
-                Glib::ustring(icons[i].icon_name), 16,
-                Gtk::ICON_LOOKUP_USE_BUILTIN);
+            m_icons[icons[i].icon_id] = icon_theme->lookup_icon(
+                Glib::ustring(icons[i].icon_name), 16, 1);
         }
         catch(const Gtk::IconThemeError & e)
         {
-            ERR_OUT("Exception %s.", e.what().c_str());
+            ERR_OUT("Exception %s.", e.what());
         }
         i++;
     }
@@ -180,7 +180,7 @@ void WorkspaceController::on_lib_notification(const eng::LibNotification &ln)
     {
         auto count = engine_library_notification_get_count(&ln);
         DBG_OUT("count for container %Ld is %Ld", (long long)count->id, (long long)count->count);
-        std::map<eng::library_id_t, Gtk::TreeIter>::const_iterator iter;
+        std::map<eng::library_id_t, Gtk::TreeModel::iterator>::const_iterator iter;
         switch (type) {
         case eng::NotificationType::FOLDER_COUNTED:
             iter = m_folderidmap.find(count->id);
@@ -206,7 +206,7 @@ void WorkspaceController::on_lib_notification(const eng::LibNotification &ln)
         auto count = engine_library_notification_get_count(&ln);
         DBG_OUT("count change for container %Ld is %Ld", (long long)count->id,
                 (long long)count->count);
-        std::map<eng::library_id_t, Gtk::TreeIter>::const_iterator iter;
+        std::map<eng::library_id_t, Gtk::TreeModel::iterator>::const_iterator iter;
         switch (type) {
         case eng::NotificationType::FOLDER_COUNT_CHANGE:
             iter = m_folderidmap.find(count->id);
@@ -277,13 +277,13 @@ void WorkspaceController::on_libtree_selection()
         DBG_OUT("selected something not a folder");
     }
 
-    Glib::RefPtr<Gio::SimpleAction>::cast_dynamic(
+    std::dynamic_pointer_cast<Gio::SimpleAction>(
         m_action_group->lookup_action("DeleteFolder"))->set_enabled(type == FOLDER_ITEM);
     libtree_selection_changed.emit();
 }
 
-void WorkspaceController::on_row_expanded_collapsed(const Gtk::TreeIter& iter,
-                                                    const Gtk::TreePath& /*path*/,
+void WorkspaceController::on_row_expanded_collapsed(const Gtk::TreeModel::iterator& iter,
+                                                    const Gtk::TreeModel::Path& /*path*/,
                                                     bool expanded)
 {
     int type = (*iter)[m_librarycolumns.m_type];
@@ -305,14 +305,14 @@ void WorkspaceController::on_row_expanded_collapsed(const Gtk::TreeIter& iter,
     }
 }
 
-void WorkspaceController::on_row_expanded(const Gtk::TreeIter& iter,
-                                          const Gtk::TreePath& path)
+void WorkspaceController::on_row_expanded(const Gtk::TreeModel::iterator& iter,
+                                          const Gtk::TreeModel::Path& path)
 {
     on_row_expanded_collapsed(iter, path, true);
 }
 
-void WorkspaceController::on_row_collapsed(const Gtk::TreeIter& iter,
-                                           const Gtk::TreePath& path)
+void WorkspaceController::on_row_collapsed(const Gtk::TreeModel::iterator& iter,
+                                           const Gtk::TreeModel::Path& path)
 {
     on_row_expanded_collapsed(iter, path, false);
 }
@@ -369,8 +369,8 @@ void WorkspaceController::add_folder_item(const eng::LibFolder* f)
 Gtk::TreeModel::iterator
 WorkspaceController::add_item(const Glib::RefPtr<Gtk::TreeStore> &treestore,
                               const Gtk::TreeNodeChildren & childrens,
-                              const Glib::RefPtr<Gdk::Pixbuf> & icon,
-                              const Glib::ustring & label, 
+                              const Glib::RefPtr<Gdk::Paintable> & icon,
+                              const Glib::ustring & label,
                               eng::library_id_t id, int type) const
 {
     Gtk::TreeModel::iterator iter;
@@ -378,7 +378,7 @@ WorkspaceController::add_item(const Glib::RefPtr<Gtk::TreeStore> &treestore,
     iter = treestore->append(childrens);
     row = *iter;
     row[m_librarycolumns.m_icon] = icon;
-    row[m_librarycolumns.m_label] = label; 
+    row[m_librarycolumns.m_label] = label;
     row[m_librarycolumns.m_id] = id;
     row[m_librarycolumns.m_type] = type;
     row[m_librarycolumns.m_count] = "--";
@@ -398,11 +398,11 @@ Gtk::Widget * WorkspaceController::buildWidget()
         "Model isn't persistent");
 
     m_folderNode = add_item(m_treestore, m_treestore->children(),
-                            m_icons[ICON_FOLDER], 
+                            m_icons[ICON_FOLDER],
                             Glib::ustring(_("Pictures")), 0,
                             FOLDERS_ITEM);
     m_projectNode = add_item(m_treestore, m_treestore->children(),
-                             m_icons[ICON_PROJECT], 
+                             m_icons[ICON_PROJECT],
                              Glib::ustring(_("Projects")), 0,
                              PROJECTS_ITEM);
     m_keywordsNode = add_item(m_treestore, m_treestore->children(),
@@ -418,17 +418,15 @@ Gtk::Widget * WorkspaceController::buildWidget()
     col = m_librarytree.get_column(num - 1);
     col->set_alignment(1.0f);
 
-    Gtk::Box* header = Gtk::manage(new Gtk::Box(Gtk::ORIENTATION_HORIZONTAL));
+    Gtk::Box* header = Gtk::manage(new Gtk::Box(Gtk::Orientation::HORIZONTAL));
     header->set_spacing(4);
     // TODO make it a mnemonic
     m_label.set_text_with_mnemonic(Glib::ustring(_("_Workspace")));
     m_label.set_mnemonic_widget(m_librarytree);
-    header->pack_start(m_label, Gtk::PACK_SHRINK);
+    header->append(m_label);
     Gtk::MenuButton* add_btn = Gtk::manage(new Gtk::MenuButton);
-    add_btn->set_direction(Gtk::ARROW_NONE);
-    auto icon = Gtk::manage(new Gtk::Image());
-    icon->set_from_icon_name("view-more-symbolic", Gtk::ICON_SIZE_BUTTON);
-    add_btn->set_image(*icon);
+    add_btn->set_direction(Gtk::ArrowType::NONE);
+    add_btn->set_icon_name("view-more-symbolic");
 
     auto menu = Gio::Menu::create();
 
@@ -457,34 +455,38 @@ Gtk::Widget * WorkspaceController::buildWidget()
 
     add_btn->set_menu_model(menu);
 
-    header->pack_end(*add_btn, Gtk::PACK_SHRINK);
-    m_vbox.pack_start(*header, Gtk::PACK_SHRINK);
+    header->append(*add_btn);
+    m_vbox.append(*header);
     Gtk::ScrolledWindow* scrolled = Gtk::manage(new Gtk::ScrolledWindow);
-    m_vbox.pack_start(*scrolled);
-    scrolled->add(m_librarytree);
-
-    m_context_menu = Gtk::manage(new Gtk::Menu(menu));
-    m_context_menu->attach_to_widget(m_librarytree);
-
-    m_librarytree.get_selection()->signal_changed().connect (
-        sigc::mem_fun(this,
-                      &WorkspaceController::on_libtree_selection));
-    m_librarytree.signal_row_expanded().connect(
-        sigc::mem_fun(this,
-                      &WorkspaceController::on_row_expanded));
-    m_librarytree.signal_row_collapsed().connect(
-        sigc::mem_fun(this,
-                      &WorkspaceController::on_row_collapsed));
-    m_librarytree.signal_popup_menu()
-        .connect(sigc::mem_fun(*this, &WorkspaceController::on_popup_menu));
-    m_librarytree.signal_button_press_event()
-        .connect_notify(sigc::mem_fun(*this, &WorkspaceController::on_button_press_event));
+    m_vbox.append(*scrolled);
+    m_librarytree.set_vexpand(true);
+    scrolled->set_child(m_librarytree);
+
+    m_context_menu = Gtk::manage(new Gtk::PopoverMenu(menu));
+
+    m_librarytree.get_selection()->signal_changed().connect([this] {
+        this->on_libtree_selection();
+    });
+    m_librarytree.signal_row_expanded().connect([this] (const Gtk::TreeModel::iterator& iter, const 
Gtk::TreeModel::Path& path) {
+        this->on_row_expanded(iter, path);
+    });
+    m_librarytree.signal_row_collapsed().connect([this] (const Gtk::TreeModel::iterator& iter, const 
Gtk::TreeModel::Path& path) {
+        this->on_row_collapsed(iter, path);
+    });
+
+    auto gesture = Gtk::GestureClick::create();
+    gesture->set_button(3);
+    m_librarytree.add_controller(gesture);
+    gesture->signal_pressed()
+        .connect([this] (int, double x, double y) {
+            this->on_button_press_event(x, y);
+        });
     return m_widget;
 }
 
 /** Expand treenode from a cfg key */
 void WorkspaceController::expand_from_cfg(const char* key,
-                                          const Gtk::TreeIter& treenode)
+                                          const Gtk::TreeModel::iterator& treenode)
 {
     fwk::Configuration::Ptr cfg = getLibraryConfig();
 
@@ -504,24 +506,11 @@ void WorkspaceController::on_ready()
     }
 }
 
-bool WorkspaceController::on_popup_menu()
+void WorkspaceController::on_button_press_event(double x, double y)
 {
-    if (m_context_menu && m_librarytree.get_selection()->count_selected_rows() != 0) {
-        m_context_menu->popup_at_widget(&m_librarytree, Gdk::GRAVITY_CENTER,
-                                        Gdk::GRAVITY_NORTH, nullptr);
-        return true;
-    }
-    return false;
-}
-
-void WorkspaceController::on_button_press_event(GdkEventButton* e)
-{
-    GdkEvent* event = (GdkEvent*)e;
-    if (gdk_event_triggers_context_menu(event)
-        && gdk_event_get_event_type(event) == GDK_BUTTON_PRESS
-        && m_librarytree.get_selection()->count_selected_rows() != 0) {
-
-        m_context_menu->popup_at_pointer(event);
+    if (m_librarytree.get_selection()->count_selected_rows() != 0) {
+        m_context_menu->set_pointing_to(Gdk::Rectangle(x, y, 1, 1));
+        m_context_menu->popup();
     }
 }
 
diff --git a/src/niepce/ui/workspacecontroller.hpp b/src/niepce/ui/workspacecontroller.hpp
index ff15f29..e6dc1eb 100644
--- a/src/niepce/ui/workspacecontroller.hpp
+++ b/src/niepce/ui/workspacecontroller.hpp
@@ -1,7 +1,7 @@
 /*
- * niepce - ui/workspacecontroller.h
+ * niepce - niepce/ui/workspacecontroller.hpp
  *
- * Copyright (C) 2007-2014 Hubert Figuiere
+ * Copyright (C) 2007-2022 Hubert Figuière
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -17,8 +17,7 @@
  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
  */
 
-#ifndef __UI_WORKSPACECONTROLLER_H__
-#define __UI_WORKSPACECONTROLLER_H__
+#pragma once
 
 #include <array>
 
@@ -32,10 +31,6 @@
 #include "fwk/toolkit/notification.hpp"
 #include "niepce/ui/niepcewindow.hpp"
 
-namespace Gtk {
-}
-
-
 namespace ui {
 
 class WorkspaceController
@@ -68,7 +63,7 @@ public:
                 add(m_count);
                 add(m_count_n);
             }
-        Gtk::TreeModelColumn<Glib::RefPtr<Gdk::Pixbuf> > m_icon;
+        Gtk::TreeModelColumn<Glib::RefPtr<Gdk::Paintable> > m_icon;
         Gtk::TreeModelColumn<eng::library_id_t> m_id;
         Gtk::TreeModelColumn<Glib::ustring> m_label;
         Gtk::TreeModelColumn<int> m_type;
@@ -84,7 +79,7 @@ public:
 
     virtual Gtk::Widget * buildWidget() override;
 
-    sigc::signal<void> libtree_selection_changed;
+    sigc::signal<void(void)> libtree_selection_changed;
 private:
     /** Return the selected folder id. 0 if not a folder or no selection*/
     eng::library_id_t get_selected_folder_id();
@@ -95,14 +90,14 @@ private:
     /** action to import images */
     void action_file_import();
 
-    void on_row_expanded_collapsed(const Gtk::TreeIter& iter,
-                                   const Gtk::TreePath& path, bool expanded);
-    void on_row_expanded(const Gtk::TreeIter& iter,
-                         const Gtk::TreePath& path);
-    void on_row_collapsed(const Gtk::TreeIter& iter,
-                          const Gtk::TreePath& path);
+    void on_row_expanded_collapsed(const Gtk::TreeModel::iterator& iter,
+                                   const Gtk::TreeModel::Path& path, bool expanded);
+    void on_row_expanded(const Gtk::TreeModel::iterator& iter,
+                         const Gtk::TreeModel::Path& path);
+    void on_row_collapsed(const Gtk::TreeModel::iterator& iter,
+                          const Gtk::TreeModel::Path& path);
     bool on_popup_menu();
-    void on_button_press_event(GdkEventButton *event);
+    void on_button_press_event(double x, double y);
 
     libraryclient::LibraryClientPtr getLibraryClient() const;
     fwk::Configuration::Ptr getLibraryConfig() const;
@@ -121,13 +116,13 @@ private:
      * @param id the item id (in the database)
      * @paran type the type of node
      */
-    Gtk::TreeModel::iterator add_item(const Glib::RefPtr<Gtk::TreeStore> & treestore, 
+    Gtk::TreeModel::iterator add_item(const Glib::RefPtr<Gtk::TreeStore> & treestore,
                                       const Gtk::TreeNodeChildren & childrens,
-                                      const Glib::RefPtr<Gdk::Pixbuf> & icon,
-                                      const Glib::ustring & label, 
+                                      const Glib::RefPtr<Gdk::Paintable>& icon,
+                                      const Glib::ustring & label,
                                       eng::library_id_t id, int type) const;
 
-    void expand_from_cfg(const char* key, const Gtk::TreeIter& treenode);
+    void expand_from_cfg(const char* key, const Gtk::TreeModel::iterator& treenode);
 
     enum {
         ICON_FOLDER = 0,
@@ -140,25 +135,22 @@ private:
 
     Glib::RefPtr<Gio::SimpleActionGroup> m_action_group;
 
-    std::array< Glib::RefPtr<Gdk::Pixbuf>, _ICON_SIZE > m_icons;
+    std::array<Glib::RefPtr<Gdk::Paintable>, _ICON_SIZE> m_icons;
     WorkspaceTreeColumns           m_librarycolumns;
     Gtk::Box                       m_vbox;
     Gtk::Label                     m_label;
     Gtk::TreeView                  m_librarytree;
-    Gtk::Menu* m_context_menu;
+    Gtk::PopoverMenu* m_context_menu;
     Gtk::TreeModel::iterator       m_folderNode;  /**< the folder node */
     Gtk::TreeModel::iterator       m_projectNode; /**< the project node */
     Gtk::TreeModel::iterator       m_keywordsNode; /**< the keywords node */
     Glib::RefPtr<Gtk::TreeStore>   m_treestore;   /**< the treestore */
-    std::map<eng::library_id_t, Gtk::TreeIter>   m_folderidmap;
-    std::map<eng::library_id_t, Gtk::TreeIter>   m_projectidmap;
-    std::map<eng::library_id_t, Gtk::TreeIter>   m_keywordsidmap;
+    std::map<eng::library_id_t, Gtk::TreeModel::iterator>   m_folderidmap;
+    std::map<eng::library_id_t, Gtk::TreeModel::iterator>   m_projectidmap;
+    std::map<eng::library_id_t, Gtk::TreeModel::iterator>   m_keywordsidmap;
 };
 
-
 }
-
-#endif
 /*
   Local Variables:
   mode:c++


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