[niepce] rust: Port MetadataWidget to Rust



commit 6cc1b30d772f05dbf1f638a2141f54797b3bf26c
Author: Hubert Figuière <hub figuiere net>
Date:   Sun Oct 16 17:58:57 2022 -0400

    rust: Port MetadataWidget to Rust
    
    - Added metadata_widget.rs
    - Improved RatingLabel API
    - Added cxx binding in niepce-main
    - Deleted metadatawidget.* notabtextview.* and tokentextview.*
    - Metadata formats are defined in Rust
    - Remove cbindgen from npc-fwk

 .gitignore                                         |   4 +
 Cargo.lock                                         |   2 +-
 crates/npc-engine/build.rs                         |   1 +
 crates/npc-engine/src/db/libmetadata.rs            |  24 +
 crates/npc-engine/src/lib.rs                       |  57 ++-
 crates/npc-fwk/Cargo.toml                          |   5 -
 crates/npc-fwk/build.rs                            |  38 --
 crates/npc-fwk/src/base/fractions.rs               |  28 +-
 crates/npc-fwk/src/base/propertybag.rs             |   5 +
 crates/npc-fwk/src/base/propertyvalue.rs           |  40 +-
 crates/npc-fwk/src/lib.rs                          |  40 +-
 crates/npc-fwk/src/toolkit/widgets.rs              |   6 +
 .../npc-fwk/src/toolkit/widgets/metadata_widget.rs | 539 +++++++++++++++++++++
 crates/npc-fwk/src/toolkit/widgets/rating_label.rs |  50 +-
 .../npc-fwk/src/toolkit/widgets/token_text_view.rs |   7 +-
 crates/npc-fwk/src/toolkit/widgets/toolbox_item.rs |   4 +
 niepce-main/Cargo.toml                             |  11 +-
 niepce-main/src/lib.rs                             |  25 +-
 niepce-main/src/niepce/ui.rs                       |   3 +-
 .../src/niepce/ui/metadata_pane_controller.rs      |  61 +++
 src/Makefile.am                                    |   2 +-
 src/engine/db/libmetadata.cpp                      |   8 +-
 src/engine/db/libmetadata.hpp                      |   4 +-
 src/fwk/Makefile.am                                |  23 +-
 src/fwk/base/propertybag.cpp                       |   9 +-
 src/fwk/base/propertybag.hpp                       |   7 +-
 src/fwk/cxx_prelude.hpp                            |  12 +
 src/fwk/toolkit/Makefile.am                        |   5 +-
 src/fwk/toolkit/metadatawidget.cpp                 | 527 --------------------
 src/fwk/toolkit/metadatawidget.hpp                 | 129 -----
 src/fwk/toolkit/widgets/notabtextview.cpp          |  40 --
 src/fwk/toolkit/widgets/notabtextview.hpp          |  46 --
 src/fwk/toolkit/widgets/tokentextview.cpp          |  60 ---
 src/fwk/toolkit/widgets/tokentextview.hpp          |  52 --
 src/fwk/utils/init.cpp                             |   2 +-
 src/niepce/ui/gridviewmodule.cpp                   |   4 +-
 src/niepce/ui/gridviewmodule.hpp                   |   3 +-
 src/niepce/ui/metadatapanecontroller.cpp           | 116 ++---
 src/niepce/ui/metadatapanecontroller.hpp           |  18 +-
 src/niepce/ui/selectioncontroller.cpp              |  18 +-
 src/niepce/ui/selectioncontroller.hpp              |   8 +-
 src/rust_bindings.hpp                              |   3 +-
 42 files changed, 970 insertions(+), 1076 deletions(-)
---
diff --git a/.gitignore b/.gitignore
index 44aace74..0e3f8e6a 100644
--- a/.gitignore
+++ b/.gitignore
@@ -97,6 +97,10 @@ m4/xsize.m4
 /src/fwk/cxx_fwk_bindings.hpp
 /src/fwk/cxx_eng_bindings.cpp
 /src/fwk/cxx_eng_bindings.hpp
+/src/fwk/cxx_widgets_bindings.cpp
+/src/fwk/cxx_widgets_bindings.hpp
+/src/fwk/cxx_npc_bindings.cpp
+/src/fwk/cxx_npc_bindings.hpp
 /src/libraryclient/test_worker
 /src/engine/test_library
 /src/engine/test_filebundle
diff --git a/Cargo.lock b/Cargo.lock
index 7d6be421..6ba724a8 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -749,6 +749,7 @@ dependencies = [
  "async-channel",
  "cairo-rs",
  "cbindgen",
+ "cxx",
  "gdk-pixbuf",
  "gdk-pixbuf-sys",
  "gdk4",
@@ -791,7 +792,6 @@ version = "0.1.0"
 dependencies = [
  "async-channel",
  "cairo-rs",
- "cbindgen",
  "chrono",
  "cxx",
  "exempi",
diff --git a/crates/npc-engine/build.rs b/crates/npc-engine/build.rs
index 98867eda..24690538 100644
--- a/crates/npc-engine/build.rs
+++ b/crates/npc-engine/build.rs
@@ -39,6 +39,7 @@ fn main() {
             .exclude_item("NiepcePropertyBag")
             .exclude_item("PropertySet")
             .exclude_item("PropertyBag")
+            .exclude_item("WrappedPropertyBag")
             .exclude_item("Option")
             .with_crate(&crate_dir)
             .generate()
diff --git a/crates/npc-engine/src/db/libmetadata.rs b/crates/npc-engine/src/db/libmetadata.rs
index 38642e9a..f6bd50aa 100644
--- a/crates/npc-engine/src/db/libmetadata.rs
+++ b/crates/npc-engine/src/db/libmetadata.rs
@@ -304,3 +304,27 @@ pub extern "C" fn engine_libmetadata_to_properties(
     let result = Box::new(meta.to_properties(propset));
     Box::into_raw(result)
 }
+
+use npc_fwk::toolkit::widgets::WrappedPropertyBag;
+
+fn into_u32(from: NiepcePropertyBag) -> PropertyBag<u32> {
+    PropertyBag {
+        bag: from.bag.iter().map(|v| (*v).into()).collect(),
+        map: from
+            .map
+            .iter()
+            .map(|(k, v)| ((*k).into(), v.clone()))
+            .collect(),
+    }
+}
+
+#[no_mangle]
+pub extern "C" fn engine_libmetadata_to_wrapped_properties(
+    meta: &LibMetadata,
+    propset: &NiepcePropertySet,
+) -> *mut WrappedPropertyBag {
+    let bag = meta.to_properties(propset);
+    let bag = into_u32(bag);
+    let result = Box::new(WrappedPropertyBag(bag));
+    Box::into_raw(result)
+}
diff --git a/crates/npc-engine/src/lib.rs b/crates/npc-engine/src/lib.rs
index 7cbad7ec..671f4971 100644
--- a/crates/npc-engine/src/lib.rs
+++ b/crates/npc-engine/src/lib.rs
@@ -49,11 +49,6 @@ pub extern "C" fn eng_property_set_add(set: &mut NiepcePropertySet, v: NiepcePro
     set.insert(NiepceProperties::Index(v));
 }
 
-#[no_mangle]
-pub extern "C" fn eng_property_bag_new() -> *mut NiepcePropertyBag {
-    Box::into_raw(Box::new(NiepcePropertyBag::new()))
-}
-
 /// Delete the %PropertyBag object
 ///
 /// # Safety
@@ -104,6 +99,58 @@ pub extern "C" fn eng_property_bag_set_value(
     b.set_value(key.into(), v.clone())
 }
 
+use npc_fwk::toolkit::widgets::WrappedPropertyBag;
+
+/// Delete the %WrappedPropertyBag object
+///
+/// # Safety
+/// Dereference the raw pointer.
+#[no_mangle]
+pub unsafe extern "C" fn fwk_wrapped_property_bag_delete(bag: *mut WrappedPropertyBag) {
+    drop(Box::from_raw(bag));
+}
+
+/// Clone the %WrappedPropertyBag object. Use this to take it out of the GValue.
+///
+/// # Safety
+/// Dereference the raw pointer.
+#[no_mangle]
+pub unsafe extern "C" fn fwk_wrapped_property_bag_clone(
+    bag: *const WrappedPropertyBag,
+) -> *mut WrappedPropertyBag {
+    Box::into_raw(Box::new((*bag).clone()))
+}
+
+/// # Safety
+/// Dereference the raw pointer.
+#[no_mangle]
+pub unsafe extern "C" fn fwk_property_bag_len(bag: &WrappedPropertyBag) -> usize {
+    (*bag).0.len()
+}
+
+/// # Safety
+/// Dereference the raw pointer.
+#[no_mangle]
+pub unsafe extern "C" fn fwk_property_bag_key_by_index(
+    bag: &WrappedPropertyBag,
+    idx: usize,
+) -> u32 {
+    (*bag).0.bag[idx]
+}
+
+#[no_mangle]
+pub extern "C" fn fwk_property_bag_value(
+    b: &WrappedPropertyBag,
+    key: PropertyIndex,
+) -> *mut PropertyValue {
+    if b.0.map.contains_key(&key) {
+        let value = Box::new(b.0.map[&key].clone());
+        Box::into_raw(value)
+    } else {
+        ptr::null_mut()
+    }
+}
+
 use crate::db::{Keyword, Label, LibFile, LibMetadata};
 
 #[cxx::bridge(namespace = "eng")]
diff --git a/crates/npc-fwk/Cargo.toml b/crates/npc-fwk/Cargo.toml
index 4766c377..471fcbc6 100644
--- a/crates/npc-fwk/Cargo.toml
+++ b/crates/npc-fwk/Cargo.toml
@@ -3,7 +3,6 @@ name = "npc-fwk"
 version = "0.1.0"
 authors = ["Hubert Figuière <hub figuiere net>"]
 edition = "2018"
-build = "build.rs"
 
 # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
 
@@ -30,7 +29,3 @@ multimap = "0.4.0"
 once_cell = "^1.12.0"
 rexiv2 = "^0.9.1"
 tempfile = "3.3.0"
-
-
-[build-dependencies]
-cbindgen = { version = "0.20.0" }
diff --git a/crates/npc-fwk/src/base/fractions.rs b/crates/npc-fwk/src/base/fractions.rs
index a6c692b8..a7d08c58 100644
--- a/crates/npc-fwk/src/base/fractions.rs
+++ b/crates/npc-fwk/src/base/fractions.rs
@@ -1,7 +1,7 @@
 /*
  * niepce - fwk/base/fractions.rs
  *
- * 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
@@ -19,7 +19,7 @@
 
 use std::f64;
 
-pub fn fraction_to_decimal(value: &str) -> Option<f64> {
+pub fn parse_fraction(value: &str) -> Option<(i64, i64)> {
     let numbers: Vec<i64> = value
         .split('/')
         .map(|s| s.parse::<i64>().unwrap_or(0))
@@ -30,13 +30,33 @@ pub fn fraction_to_decimal(value: &str) -> Option<f64> {
     if numbers[1] == 0 {
         return None;
     }
-    Some(numbers[0] as f64 / numbers[1] as f64)
+    Some((numbers[0], numbers[1]))
+}
+
+pub fn fraction_to_decimal(value: &str) -> Option<f64> {
+    parse_fraction(value).map(|(n, d)| n as f64 / d as f64)
 }
 
 #[cfg(test)]
 mod tests {
     #[test]
-    fn faction_to_decimal_works() {
+    fn parse_fraction_works() {
+        let f = super::parse_fraction("1/4");
+        assert!(f.is_some());
+        assert_eq!(f.unwrap(), (1, 4));
+
+        let f = super::parse_fraction("foobar");
+        assert!(f.is_none());
+
+        let f = super::parse_fraction("1/0");
+        assert!(f.is_none());
+
+        let f = super::parse_fraction("1/0/1");
+        assert!(f.is_none());
+    }
+
+    #[test]
+    fn fraction_to_decimal_works() {
         use super::fraction_to_decimal;
 
         let f = fraction_to_decimal("1/4");
diff --git a/crates/npc-fwk/src/base/propertybag.rs b/crates/npc-fwk/src/base/propertybag.rs
index e5ccf925..d71e9acd 100644
--- a/crates/npc-fwk/src/base/propertybag.rs
+++ b/crates/npc-fwk/src/base/propertybag.rs
@@ -21,6 +21,7 @@ use std::collections::BTreeMap;
 
 use crate::base::propertyvalue::PropertyValue;
 
+#[derive(Clone)]
 pub struct PropertyBag<Index> {
     pub bag: Vec<Index>,
     pub map: BTreeMap<Index, PropertyValue>,
@@ -48,6 +49,10 @@ impl<Index: Ord + Copy> PropertyBag<Index> {
         self.bag.len()
     }
 
+    pub fn value(&self, key: &Index) -> Option<&PropertyValue> {
+        self.map.get(key)
+    }
+
     pub fn set_value(&mut self, key: Index, value: PropertyValue) -> bool {
         let ret = self.map.insert(key, value);
         if ret.is_some() {
diff --git a/crates/npc-fwk/src/base/propertyvalue.rs b/crates/npc-fwk/src/base/propertyvalue.rs
index ef8151b9..c10584f3 100644
--- a/crates/npc-fwk/src/base/propertyvalue.rs
+++ b/crates/npc-fwk/src/base/propertyvalue.rs
@@ -19,7 +19,8 @@
 
 use super::date::Date;
 
-#[derive(Clone, Debug)]
+#[derive(Clone, Debug, glib::Boxed)]
+#[boxed_type(name = "PropertyValue")]
 pub enum PropertyValue {
     Empty,
     Int(i32),
@@ -47,6 +48,13 @@ impl PropertyValue {
         matches!(*self, PropertyValue::String(_))
     }
 
+    pub fn integer(&self) -> Option<i32> {
+        match *self {
+            PropertyValue::Int(i) => Some(i),
+            _ => None,
+        }
+    }
+
     pub fn integer_unchecked(&self) -> i32 {
         match *self {
             PropertyValue::Int(i) => i,
@@ -54,6 +62,13 @@ impl PropertyValue {
         }
     }
 
+    pub fn date(&self) -> Option<&Date> {
+        match *self {
+            PropertyValue::Date(ref d) => Some(d),
+            _ => None,
+        }
+    }
+
     pub fn date_unchecked(&self) -> Box<Date> {
         match *self {
             PropertyValue::Date(ref d) => Box::new(*d),
@@ -61,6 +76,13 @@ impl PropertyValue {
         }
     }
 
+    pub fn string(&self) -> Option<&str> {
+        match *self {
+            PropertyValue::String(ref s) => Some(s),
+            _ => None,
+        }
+    }
+
     pub fn string_unchecked(&self) -> &str {
         match *self {
             PropertyValue::String(ref s) => s,
@@ -80,6 +102,13 @@ impl PropertyValue {
         }
     }
 
+    pub fn string_array(&self) -> Option<&[String]> {
+        match *self {
+            PropertyValue::StringArray(ref sa) => Some(sa),
+            _ => None,
+        }
+    }
+
     pub fn string_array_unchecked(&self) -> &[String] {
         match *self {
             PropertyValue::StringArray(ref sa) => sa,
@@ -88,19 +117,10 @@ impl PropertyValue {
     }
 }
 
-/// Create a new String %PropertyValue from a string
-pub fn property_value_new_str(v: &str) -> Box<PropertyValue> {
-    Box::new(PropertyValue::String(v.to_string()))
-}
-
 pub fn property_value_new_int(v: i32) -> Box<PropertyValue> {
     Box::new(PropertyValue::Int(v))
 }
 
-pub fn property_value_new_date(v: &Date) -> Box<PropertyValue> {
-    Box::new(PropertyValue::Date(*v))
-}
-
 pub fn property_value_new_string_array() -> Box<PropertyValue> {
     Box::new(PropertyValue::StringArray(vec![]))
 }
diff --git a/crates/npc-fwk/src/lib.rs b/crates/npc-fwk/src/lib.rs
index 179de4ef..1fd94520 100644
--- a/crates/npc-fwk/src/lib.rs
+++ b/crates/npc-fwk/src/lib.rs
@@ -22,7 +22,7 @@ pub mod base;
 pub mod toolkit;
 pub mod utils;
 
-pub use self::base::fractions::fraction_to_decimal;
+pub use self::base::fractions::{fraction_to_decimal, parse_fraction};
 pub use self::base::propertybag::PropertyBag;
 pub use self::base::propertyvalue::PropertyValue;
 pub use self::base::PropertySet;
@@ -52,11 +52,9 @@ use glib::translate::*;
 
 use self::base::rgbcolour::RgbColour;
 use crate::base::date::Date;
-use crate::base::propertyvalue::{
-    property_value_new_date, property_value_new_int, property_value_new_str,
-    property_value_new_string_array,
-};
+use crate::base::propertyvalue::{property_value_new_int, property_value_new_string_array};
 use crate::toolkit::thumbnail::Thumbnail;
+use crate::toolkit::widgets::MetadataWidget;
 use crate::toolkit::Configuration;
 use crate::utils::files::FileList;
 
@@ -89,10 +87,6 @@ pub fn gps_coord_from_xmp_(value: &str) -> f64 {
     gps_coord_from_xmp(value).unwrap_or(f64::NAN)
 }
 
-pub fn fraction_to_decimal_(value: &str) -> f64 {
-    fraction_to_decimal(value).unwrap_or(f64::NAN)
-}
-
 pub fn thumbnail_for_file(path: &str, w: i32, h: i32, orientation: i32) -> Box<Thumbnail> {
     Box::new(Thumbnail::thumbnail_file(path, w, h, orientation))
 }
@@ -143,6 +137,10 @@ pub fn file_list_new() -> Box<FileList> {
     Box::new(FileList::default())
 }
 
+pub fn metadata_widget_new(title: &str) -> Box<MetadataWidget> {
+    Box::new(MetadataWidget::new(title))
+}
+
 #[cxx::bridge(namespace = "fwk")]
 mod ffi {
     struct SharedConfiguration {
@@ -188,8 +186,6 @@ mod ffi {
     extern "Rust" {
         #[cxx_name = "gps_coord_from_xmp"]
         fn gps_coord_from_xmp_(value: &str) -> f64;
-        #[cxx_name = "fraction_to_decimal"]
-        fn fraction_to_decimal_(value: &str) -> f64;
     }
 
     extern "Rust" {
@@ -230,9 +226,7 @@ mod ffi {
     extern "Rust" {
         type PropertyValue;
 
-        fn property_value_new_str(v: &str) -> Box<PropertyValue>;
         fn property_value_new_int(v: i32) -> Box<PropertyValue>;
-        fn property_value_new_date(v: &Date) -> Box<PropertyValue>;
         fn property_value_new_string_array() -> Box<PropertyValue>;
 
         fn is_empty(&self) -> bool;
@@ -250,4 +244,24 @@ mod ffi {
         #[cxx_name = "get_string_array"]
         fn string_array_unchecked(&self) -> &[String];
     }
+
+    extern "C++" {
+        include!("fwk/cxx_widgets_bindings.hpp");
+
+        type WrappedPropertyBag = crate::toolkit::widgets::WrappedPropertyBag;
+        type MetadataSectionFormat = crate::toolkit::widgets::MetadataSectionFormat;
+    }
+
+    extern "Rust" {
+        type MetadataWidget;
+
+        fn gobj(&self) -> *mut c_char;
+        #[cxx_name = "MetadataWidget_new"]
+        fn metadata_widget_new(title: &str) -> Box<MetadataWidget>;
+        #[cxx_name = "set_data_format"]
+        fn set_data_format_(&self, fmt: &MetadataSectionFormat);
+        #[cxx_name = "set_data_source"]
+        fn set_data_source_wrapped(&self, properties: &WrappedPropertyBag);
+        fn set_data_source_none(&self);
+    }
 }
diff --git a/crates/npc-fwk/src/toolkit/widgets.rs b/crates/npc-fwk/src/toolkit/widgets.rs
index c20ec9f7..bbaf466e 100644
--- a/crates/npc-fwk/src/toolkit/widgets.rs
+++ b/crates/npc-fwk/src/toolkit/widgets.rs
@@ -17,15 +17,21 @@
  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
  */
 
+mod metadata_widget;
 pub mod rating_label;
 mod token_text_view;
 mod toolbox_item;
 
 // Re-exports
+pub use metadata_widget::{
+    MetaDT, MetadataFormat, MetadataSectionFormat, MetadataWidget, WrappedPropertyBag,
+};
+pub use rating_label::RatingLabel;
 pub use token_text_view::TokenTextView;
 pub use toolbox_item::ToolboxItem;
 
 pub mod prelude {
+    pub use super::rating_label::RatingLabelExt;
     pub use super::toolbox_item::ToolboxItemExt;
     pub use super::toolbox_item::ToolboxItemImpl;
 }
diff --git a/crates/npc-fwk/src/toolkit/widgets/metadata_widget.rs 
b/crates/npc-fwk/src/toolkit/widgets/metadata_widget.rs
new file mode 100644
index 00000000..5fe059ab
--- /dev/null
+++ b/crates/npc-fwk/src/toolkit/widgets/metadata_widget.rs
@@ -0,0 +1,539 @@
+/*
+ * niepce - fwk/toolkit/widgets/metadata_widget.rs
+ *
+ * Copyright (C) 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
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+use std::ffi::c_char;
+
+use glib::translate::*;
+use glib::Cast;
+use gtk4::subclass::prelude::*;
+
+use super::ToolboxItem;
+use crate::PropertyBag;
+
+/// A wrapped PropertyBag to use in the cxx bridge.
+#[derive(Clone, Default, glib::Boxed)]
+#[boxed_type(name = "PropertyBag")]
+pub struct WrappedPropertyBag(pub PropertyBag<u32>);
+
+impl std::ops::Deref for WrappedPropertyBag {
+    type Target = PropertyBag<u32>;
+
+    fn deref(&self) -> &Self::Target {
+        &self.0
+    }
+}
+
+impl std::ops::DerefMut for WrappedPropertyBag {
+    fn deref_mut(&mut self) -> &mut Self::Target {
+        &mut self.0
+    }
+}
+
+use cxx::{type_id, ExternType};
+
+unsafe impl ExternType for WrappedPropertyBag {
+    type Id = type_id!("fwk::WrappedPropertyBag");
+    type Kind = cxx::kind::Opaque;
+}
+
+#[cxx::bridge(namespace = "fwk")]
+mod ffi {
+    extern "C++" {
+        include!("fwk/cxx_prelude.hpp");
+    }
+
+    // This bridge content should be moved when the bridge is removed.
+    #[repr(u32)]
+    #[derive(Clone)]
+    enum MetaDT {
+        #[allow(dead_code)]
+        NONE = 0,
+        #[allow(dead_code)]
+        STRING,
+        STRING_ARRAY,
+        TEXT,
+        DATE,
+        FRAC,
+        FRAC_DEC, // Fraction as decimal
+        STAR_RATING,
+        #[allow(dead_code)]
+        SIZE, // Size in bytes
+    }
+
+    #[derive(Clone)]
+    struct MetadataFormat {
+        label: String,
+        id: u32, // NiepcePropertyIdx
+        type_: MetaDT,
+        readonly: bool,
+    }
+
+    #[derive(Clone)]
+    struct MetadataSectionFormat {
+        section: String,
+        formats: Vec<MetadataFormat>,
+    }
+}
+
+pub use ffi::{MetaDT, MetadataFormat, MetadataSectionFormat};
+
+glib::wrapper! {
+    pub struct MetadataWidget(
+    ObjectSubclass<imp::MetadataWidget>)
+    @extends ToolboxItem, gtk4::Box, gtk4::Widget;
+}
+
+impl MetadataWidget {
+    pub fn new(title: &str) -> MetadataWidget {
+        let obj: MetadataWidget = glib::Object::new(&[]).expect("Failed to create MetadataWidget");
+        obj.upcast_ref::<ToolboxItem>().set_title(title);
+
+        obj
+    }
+
+    // cxx
+    pub fn set_data_source_wrapped(&self, properties: &WrappedPropertyBag) {
+        self.set_data_source(Some(properties.0.clone()))
+    }
+
+    // cxx
+    pub fn set_data_source_none(&self) {
+        self.set_data_source(None)
+    }
+
+    // cxx
+    pub fn gobj(&self) -> *mut c_char {
+        let gobj: *mut gtk4_sys::GtkWidget = self.upcast_ref::<gtk4::Widget>().to_glib_none().0;
+        gobj as *mut c_char
+    }
+
+    // cxx
+    pub fn set_data_format_(&self, fmt: &MetadataSectionFormat) {
+        self.set_data_format(Some(fmt.clone()));
+    }
+}
+
+trait MetadataWidgetExt {
+    fn set_data_source(&self, properties: Option<PropertyBag<u32>>);
+    fn set_data_format(&self, fmt: Option<MetadataSectionFormat>);
+}
+
+impl MetadataWidgetExt for MetadataWidget {
+    /// Set the data source of the metadata.
+    fn set_data_source(&self, properties: Option<PropertyBag<u32>>) {
+        self.imp().set_data_source(properties);
+    }
+
+    fn set_data_format(&self, fmt: Option<MetadataSectionFormat>) {
+        self.imp().set_data_format(fmt);
+    }
+}
+
+mod imp {
+    use std::cell::RefCell;
+    use std::collections::HashMap;
+
+    use glib::subclass::*;
+    use glib::Cast;
+    use glib::StaticType;
+    use gtk4::prelude::*;
+    use gtk4::subclass::prelude::*;
+
+    use crate::{PropertyBag, PropertyValue};
+
+    use super::super::prelude::*;
+    use super::super::{RatingLabel, TokenTextView};
+    use super::{MetaDT, MetadataFormat, MetadataSectionFormat, WrappedPropertyBag};
+
+    fn clear_widget(widget: &gtk4::Widget) {
+        if let Some(label) = widget.downcast_ref::<gtk4::Label>() {
+            label.set_text("");
+        } else if let Some(entry) = widget.downcast_ref::<gtk4::Entry>() {
+            entry.set_text("");
+        } else if let Some(ttv) = widget.downcast_ref::<TokenTextView>() {
+            ttv.set_tokens(&[]);
+        } else if let Some(tv) = widget.downcast_ref::<gtk4::TextView>() {
+            tv.buffer().set_text("");
+        } else if let Some(rating) = widget.downcast_ref::<RatingLabel>() {
+            rating.set_rating(0);
+        } else {
+            err_out!("Unknow widget type {}", widget.type_().name());
+        }
+    }
+
+    pub struct MetadataWidget {
+        widget: gtk4::Grid,
+        data_map: RefCell<HashMap<u32, gtk4::Widget>>,
+        current_data: RefCell<Option<PropertyBag<u32>>>,
+        fmt: RefCell<Option<MetadataSectionFormat>>,
+    }
+
+    impl MetadataWidget {
+        fn create_star_rating_widget(&self, readonly: bool, id: u32) -> gtk4::Widget {
+            let rating = RatingLabel::new(0, !readonly);
+            if !readonly {
+                let obj = self.instance();
+                rating.connect_rating_changed(glib::clone!(@weak obj => move |_, rating| {
+                    obj.imp().emit_metadata_changed(id, &PropertyValue::Int(rating));
+                }));
+            }
+
+            rating.upcast()
+        }
+
+        fn create_text_widget(&self, readonly: bool, id: u32) -> gtk4::Widget {
+            if readonly {
+                self.create_string_widget(readonly, id)
+            } else {
+                let entry = gtk4::TextView::new();
+                entry.set_accepts_tab(false);
+                entry.set_editable(true);
+                entry.set_wrap_mode(gtk4::WrapMode::Word);
+                let ctrl = gtk4::EventControllerFocus::new();
+                entry.add_controller(&ctrl);
+
+                let obj = self.instance();
+                ctrl.connect_leave(glib::clone!(@weak entry, @weak obj => move |_| {
+                    let buffer = entry.buffer();
+                    let start = buffer.start_iter();
+                    let end = buffer.end_iter();
+                    obj.imp().emit_metadata_changed(id, &PropertyValue::String(buffer.text(&start, &end, 
true).to_string()));
+                }));
+
+                entry.upcast()
+            }
+        }
+        fn create_string_widget(&self, readonly: bool, id: u32) -> gtk4::Widget {
+            if readonly {
+                let label = gtk4::Label::new(None);
+                label.set_xalign(0.0);
+                label.set_yalign(0.5);
+                label.set_ellipsize(gtk4::pango::EllipsizeMode::Middle);
+
+                label.upcast()
+            } else {
+                let entry = gtk4::Entry::new();
+                entry.set_has_frame(false);
+                let ctrl = gtk4::EventControllerFocus::new();
+                entry.add_controller(&ctrl);
+
+                let obj = self.instance();
+                ctrl.connect_leave(glib::clone!(@weak entry, @weak obj => move |_| {
+                    obj.imp().emit_metadata_changed(id, &PropertyValue::String(entry.text().to_string()));
+                }));
+
+                entry.upcast()
+            }
+        }
+
+        fn create_string_array_widget(&self, readonly: bool, id: u32) -> gtk4::Widget {
+            let ttv = TokenTextView::new();
+            if !readonly {
+                let ctrl = gtk4::EventControllerFocus::new();
+                ttv.add_controller(&ctrl);
+
+                let obj = self.instance();
+                ctrl.connect_leave(glib::clone!(@weak ttv, @weak obj => move |_| {
+                    obj.imp().emit_metadata_changed(id, &PropertyValue::StringArray(ttv.tokens()));
+                }));
+            }
+
+            ttv.upcast()
+        }
+
+        fn create_date_widget(&self, readonly: bool, id: u32) -> gtk4::Widget {
+            self.create_string_widget(readonly, id)
+        }
+
+        fn create_widgets_for_format(&self, fmt: &MetadataSectionFormat) {
+            for (i, f) in fmt.formats.iter().enumerate() {
+                let label = gtk4::Label::new(Some(&format!("<b>{}</b>", &f.label)));
+                label.set_use_markup(true);
+                label.set_xalign(0.0);
+                if f.type_ != MetaDT::STRING_ARRAY {
+                    label.set_yalign(0.5);
+                } else {
+                    label.set_yalign(0.0);
+                }
+                let w = match f.type_ {
+                    MetaDT::STAR_RATING => self.create_star_rating_widget(f.readonly, f.id),
+                    MetaDT::STRING_ARRAY => self.create_string_array_widget(f.readonly, f.id),
+                    MetaDT::TEXT => self.create_text_widget(f.readonly, f.id),
+                    MetaDT::DATE => self.create_date_widget(f.readonly, f.id),
+                    _ => self.create_string_widget(f.readonly, f.id),
+                };
+                let row = i as i32;
+                self.widget.insert_row(row + 1);
+                self.widget.attach(&label, 0, row, 1, 1);
+                self.widget
+                    .attach_next_to(&w, Some(&label), gtk4::PositionType::Right, 1, 1);
+                self.data_map.borrow_mut().insert(f.id, w);
+            }
+        }
+
+        fn add_data(&self, fmt: &MetadataFormat, value: &PropertyValue) {
+            let data_map = self.data_map.borrow();
+            let w = data_map.get(&fmt.id);
+
+            if w.is_none() {
+                err_out!("No widget for property {}", fmt.id);
+                return;
+            }
+
+            let w = w.as_ref().unwrap();
+            match fmt.type_ {
+                MetaDT::FRAC_DEC => self.set_fraction_dec_data(w, value),
+                MetaDT::FRAC => self.set_fraction_data(w, value),
+                MetaDT::STAR_RATING => self.set_star_rating_data(w, value),
+                MetaDT::STRING_ARRAY => self.set_string_array_data(w, fmt.readonly, value),
+                MetaDT::TEXT => self.set_text_data(w, fmt.readonly, value),
+                MetaDT::DATE => self.set_date_data(w, value),
+                _ => {
+                    if !self.set_text_data(w, fmt.readonly, value) {
+                        err_out!("failed to set value for {}", fmt.id);
+                        false
+                    } else {
+                        true
+                    }
+                }
+            };
+        }
+
+        pub(super) fn set_data_format(&self, fmt: Option<MetadataSectionFormat>) {
+            self.fmt.replace(fmt);
+            if let Some(fmt) = self.fmt.borrow().as_ref() {
+                self.create_widgets_for_format(fmt);
+            }
+            // XXX what if None? Should we delete the widgets?
+        }
+
+        pub(super) fn set_data_source(&self, properties: Option<PropertyBag<u32>>) {
+            self.current_data.replace(properties);
+            self.data_map.borrow().values().for_each(clear_widget);
+
+            let is_empty = self
+                .current_data
+                .borrow()
+                .as_ref()
+                .map(|v| v.is_empty())
+                .unwrap_or(true);
+            self.instance().set_sensitive(!is_empty);
+            if is_empty {
+                return;
+            }
+            let properties = self.current_data.borrow();
+            if let Some(fmt) = self.fmt.borrow().as_ref() {
+                fmt.formats.iter().for_each(|f| {
+                    if let Some(value) = properties.as_ref().and_then(|v| v.value(&f.id)) {
+                        self.add_data(f, value)
+                    } else {
+                        // XXX value is empty
+                    }
+                });
+            }
+        }
+
+        fn set_fraction_dec_data(&self, w: &gtk4::Widget, value: &PropertyValue) -> bool {
+            if let Some(s) = value.string() {
+                dbg_out!("set faction dec {}", s);
+                return if let Some(w) = w.downcast_ref::<gtk4::Label>() {
+                    let dec_str = crate::fraction_to_decimal(s)
+                        .map(|dec| dec.to_string())
+                        .unwrap_or_else(|| "NaN".to_string());
+
+                    w.set_text(&dec_str);
+                    true
+                } else {
+                    err_out!("Incorrect widget type {}", w.type_().name());
+                    false
+                };
+            }
+
+            err_out!("Data not a string");
+            false
+        }
+
+        fn set_fraction_data(&self, w: &gtk4::Widget, value: &PropertyValue) -> bool {
+            if let Some(s) = value.string() {
+                dbg_out!("set fraction {}", s);
+                return if let Some(w) = w.downcast_ref::<gtk4::Label>() {
+                    if let Some((n, d)) = crate::parse_fraction(s) {
+                        let frac_str = format!("{}/{}", n, d);
+                        w.set_text(&frac_str);
+                        true
+                    } else {
+                        err_out!("Invalid fraction {}", s);
+                        false
+                    }
+                } else {
+                    err_out!("Incorrect widget type {}", w.type_().name());
+                    false
+                };
+            }
+
+            err_out!("Data not a string");
+            false
+        }
+
+        fn set_star_rating_data(&self, w: &gtk4::Widget, value: &PropertyValue) -> bool {
+            if let Some(i) = value.integer() {
+                return if let Some(w) = w.downcast_ref::<RatingLabel>() {
+                    w.set_rating(i);
+                    true
+                } else {
+                    err_out!("Incorrect widget type {}", w.type_().name());
+                    false
+                };
+            }
+
+            err_out!("Data not integer");
+            false
+        }
+
+        fn set_string_array_data(
+            &self,
+            w: &gtk4::Widget,
+            readonly: bool,
+            value: &PropertyValue,
+        ) -> bool {
+            if let Some(tokens) = value.string_array() {
+                return if let Some(w) = w.downcast_ref::<TokenTextView>() {
+                    w.set_tokens(tokens);
+                    w.set_editable(!readonly);
+                    true
+                } else {
+                    err_out!("Incorrect widget type {}", w.type_().name());
+                    false
+                };
+            }
+            err_out!("Data not string array");
+            false
+        }
+
+        fn set_text_data(&self, w: &gtk4::Widget, readonly: bool, value: &PropertyValue) -> bool {
+            if let Some(s) = value.string() {
+                if readonly {
+                    return if let Some(w) = w.downcast_ref::<gtk4::Label>() {
+                        w.set_text(s);
+                        true
+                    } else {
+                        err_out!("Incorrect widget type {}", w.type_().name());
+                        false
+                    };
+                } else {
+                    return if let Some(w) = w.downcast_ref::<gtk4::Entry>() {
+                        w.set_text(s);
+                        true
+                    } else if let Some(w) = w.downcast_ref::<gtk4::TextView>() {
+                        w.buffer().set_text(s);
+                        true
+                    } else {
+                        err_out!("Incorrect widget type {}", w.type_().name());
+                        false
+                    };
+                }
+            }
+
+            err_out!("Data not a string");
+            false
+        }
+
+        fn set_date_data(&self, w: &gtk4::Widget, value: &PropertyValue) -> bool {
+            if let Some(d) = value.date() {
+                return if let Some(w) = w.downcast_ref::<gtk4::Label>() {
+                    w.set_text(&d.to_string());
+                    true
+                } else {
+                    err_out!("Incorrect widget type {}", w.type_().name());
+                    false
+                };
+            }
+
+            err_out!("Data not a date");
+            false
+        }
+
+        fn emit_metadata_changed(&self, prop: u32, value: &PropertyValue) {
+            let mut props = WrappedPropertyBag::default();
+            let mut old_props = WrappedPropertyBag::default();
+            props.set_value(prop, value.clone());
+            if let Some(old_val) = self
+                .current_data
+                .borrow()
+                .as_ref()
+                .and_then(|props| props.value(&prop))
+            {
+                old_props.set_value(prop, old_val.clone());
+            }
+            self.instance()
+                .emit_by_name::<()>("metadata-changed", &[&props, &old_props]);
+        }
+    }
+
+    impl ObjectImpl for MetadataWidget {
+        fn constructed(&self, obj: &Self::Type) {
+            self.parent_constructed(obj);
+
+            self.widget.set_column_homogeneous(true);
+            self.widget.set_row_homogeneous(false);
+            self.widget.insert_column(0);
+            self.widget.insert_column(0);
+            self.widget.set_margin_start(8);
+            obj.upcast_ref::<super::ToolboxItem>()
+                .set_child(Some(&self.widget));
+        }
+
+        fn signals() -> &'static [Signal] {
+            use once_cell::sync::Lazy;
+            static SIGNALS: Lazy<Vec<Signal>> = Lazy::new(|| {
+                vec![Signal::builder(
+                    "metadata-changed",
+                    &[
+                        <WrappedPropertyBag>::static_type().into(),
+                        <WrappedPropertyBag>::static_type().into(),
+                    ],
+                    <()>::static_type().into(),
+                )
+                .run_last()
+                .build()]
+            });
+            SIGNALS.as_ref()
+        }
+    }
+
+    #[glib::object_subclass]
+    impl ObjectSubclass for MetadataWidget {
+        const NAME: &'static str = "NpcMetadataWidget";
+        type Type = super::MetadataWidget;
+        type ParentType = super::ToolboxItem;
+
+        fn new() -> Self {
+            Self {
+                widget: gtk4::Grid::new(),
+                data_map: RefCell::new(HashMap::default()),
+                current_data: RefCell::new(None),
+                fmt: RefCell::new(None),
+            }
+        }
+    }
+
+    impl ToolboxItemImpl for MetadataWidget {}
+    impl BoxImpl for MetadataWidget {}
+    impl WidgetImpl for MetadataWidget {}
+}
diff --git a/crates/npc-fwk/src/toolkit/widgets/rating_label.rs 
b/crates/npc-fwk/src/toolkit/widgets/rating_label.rs
index 56a2c6d0..ef114566 100644
--- a/crates/npc-fwk/src/toolkit/widgets/rating_label.rs
+++ b/crates/npc-fwk/src/toolkit/widgets/rating_label.rs
@@ -17,13 +17,11 @@
  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
  */
 
-use libc::c_int;
 use std::cell::Cell;
 
 use gdk4::prelude::*;
 use glib::subclass::prelude::*;
 use glib::subclass::Signal;
-use glib::translate::*;
 use gtk4::prelude::*;
 use gtk4::subclass::prelude::*;
 
@@ -55,6 +53,10 @@ impl RatingLabelPriv {
         self.editable.set(editable);
     }
 
+    fn rating(&self) -> i32 {
+        self.rating.get()
+    }
+
     fn set_rating(&self, rating: i32) {
         self.rating.set(rating);
         let w = self.instance();
@@ -154,15 +156,37 @@ impl ObjectImpl for RatingLabelPriv {
 
 pub trait RatingLabelExt {
     fn set_rating(&self, rating: i32);
+    fn rating(&self) -> i32;
 }
 
 impl RatingLabelExt for RatingLabel {
     fn set_rating(&self, rating: i32) {
         self.imp().set_rating(rating);
     }
+
+    fn rating(&self) -> i32 {
+        self.imp().rating()
+    }
 }
 
 impl RatingLabel {
+    /// Connect to the signal `rating-changed`
+    pub fn connect_rating_changed<F>(&self, f: F) -> glib::SignalHandlerId
+    where
+        F: Fn(&Self, i32) + 'static,
+    {
+        self.connect_local(
+            "rating-changed",
+            true,
+            glib::clone!(@weak self as w => @default-return None, move |values| {
+                if let Ok(rating) = values[0].get::<i32>() {
+                    f(&w, rating);
+                }
+                None
+            }),
+        )
+    }
+
     pub fn star() -> gdk4::Texture {
         PIXBUFS.star.clone()
     }
@@ -246,25 +270,3 @@ impl WidgetImpl for RatingLabelPriv {
         RatingLabel::draw_rating(snapshot, rating, &star, &RatingLabel::unstar(), x, y);
     }
 }
-
-#[no_mangle]
-pub extern "C" fn fwk_rating_label_new(rating: c_int, editable: bool) -> *mut gtk4_sys::GtkWidget {
-    RatingLabel::new(rating, editable)
-        .upcast::<gtk4::Widget>()
-        .to_glib_full()
-}
-
-/// Set the rating for the %RatingLabel widget
-///
-/// # Safety
-/// Dereference the widget pointer.
-#[no_mangle]
-pub unsafe extern "C" fn fwk_rating_label_set_rating(
-    widget: *mut gtk4_sys::GtkWidget,
-    rating: i32,
-) {
-    let rating_label = gtk4::Widget::from_glib_none(widget)
-        .downcast::<RatingLabel>()
-        .expect("Not a RatingLabel widget");
-    rating_label.set_rating(rating);
-}
diff --git a/crates/npc-fwk/src/toolkit/widgets/token_text_view.rs 
b/crates/npc-fwk/src/toolkit/widgets/token_text_view.rs
index 36c25b36..33452806 100644
--- a/crates/npc-fwk/src/toolkit/widgets/token_text_view.rs
+++ b/crates/npc-fwk/src/toolkit/widgets/token_text_view.rs
@@ -30,8 +30,11 @@ glib::wrapper! {
 
 impl TokenTextView {
     pub fn new() -> TokenTextView {
-        glib::Object::new(&[("wrap-mode", &gtk4::WrapMode::Word)])
-            .expect("Failed to create TokenTextView Widget")
+        glib::Object::new(&[
+            ("wrap-mode", &gtk4::WrapMode::Word),
+            ("accepts-tab", &false),
+        ])
+        .expect("Failed to create TokenTextView Widget")
     }
 
     /// Get the tokens from the text.
diff --git a/crates/npc-fwk/src/toolkit/widgets/toolbox_item.rs 
b/crates/npc-fwk/src/toolkit/widgets/toolbox_item.rs
index 21ddec50..2f219e44 100644
--- a/crates/npc-fwk/src/toolkit/widgets/toolbox_item.rs
+++ b/crates/npc-fwk/src/toolkit/widgets/toolbox_item.rs
@@ -40,6 +40,10 @@ impl ToolboxItem {
         obj.imp().expander.set_label(Some(label));
         obj
     }
+
+    pub fn set_title(&self, title: &str) {
+        self.imp().expander.set_label(Some(title));
+    }
 }
 
 pub trait ToolboxItemExt {
diff --git a/niepce-main/Cargo.toml b/niepce-main/Cargo.toml
index 8caae616..639ac530 100644
--- a/niepce-main/Cargo.toml
+++ b/niepce-main/Cargo.toml
@@ -7,21 +7,22 @@ edition = "2018"
 
 [dependencies]
 async-channel = "1.6.1"
-once_cell = "^1.12.0"
-gettext-rs = "0.3.0"
-glib = "*"
-gio-sys = "*"
-gio = "*"
 cairo-rs = "*"
+cxx = { version = "1.0", features = [ "c++17" ] }
 gdk4 = "*"
 gdk-pixbuf = "*"
 gdk-pixbuf-sys = "*"
+gettext-rs = "0.3.0"
+gio-sys = "*"
+gio = "*"
+glib = "*"
 graphene-rs = "0.15.1"
 gtk4-sys = "*"
 gtk4 = "*"
 lazy_static = "^1.4.0"
 libc = "0.2.39"
 #gphoto = "0.1.1"
+once_cell = "^1.12.0"
 
 npc-fwk = { path = "../crates/npc-fwk" }
 npc-engine = { path = "../crates/npc-engine" }
diff --git a/niepce-main/src/lib.rs b/niepce-main/src/lib.rs
index 1b0d43a8..830a2e28 100644
--- a/niepce-main/src/lib.rs
+++ b/niepce-main/src/lib.rs
@@ -22,9 +22,7 @@ pub mod niepce;
 
 use std::sync::Once;
 
-/// Call this to initialize npc-fwk the gtk-rs bindings
-#[no_mangle]
-pub extern "C" fn niepce_init() {
+fn niepce_init() {
     static START: Once = Once::new();
 
     START.call_once(|| {
@@ -32,3 +30,24 @@ pub extern "C" fn niepce_init() {
         npc_fwk::init();
     });
 }
+
+use crate::niepce::ui::metadata_pane_controller::get_format;
+use npc_fwk::toolkit;
+
+#[cxx::bridge(namespace = "npc")]
+mod ffi {
+    extern "Rust" {
+        fn niepce_init();
+    }
+
+    #[namespace = "fwk"]
+    extern "C++" {
+        include!("fwk/cxx_widgets_bindings.hpp");
+
+        type MetadataSectionFormat = crate::toolkit::widgets::MetadataSectionFormat;
+    }
+
+    extern "Rust" {
+        fn get_format() -> &'static [MetadataSectionFormat];
+    }
+}
diff --git a/niepce-main/src/niepce/ui.rs b/niepce-main/src/niepce/ui.rs
index 47771336..805768e5 100644
--- a/niepce-main/src/niepce/ui.rs
+++ b/niepce-main/src/niepce/ui.rs
@@ -1,7 +1,7 @@
 /*
  * niepce - niepce/ui/mod.rs
  *
- * Copyright (C) 2020 Hubert Figuière
+ * Copyright (C) 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
@@ -22,5 +22,6 @@ pub mod image_grid_view;
 pub mod image_list_store;
 pub mod imagetoolbar;
 pub mod library_cell_renderer;
+pub mod metadata_pane_controller;
 pub mod thumb_nav;
 pub mod thumb_strip_view;
diff --git a/niepce-main/src/niepce/ui/metadata_pane_controller.rs 
b/niepce-main/src/niepce/ui/metadata_pane_controller.rs
new file mode 100644
index 00000000..083d1183
--- /dev/null
+++ b/niepce-main/src/niepce/ui/metadata_pane_controller.rs
@@ -0,0 +1,61 @@
+use gettextrs::gettext as i18n;
+
+use npc_engine::db::NiepcePropertyIdx;
+use npc_fwk::toolkit::widgets::{MetaDT, MetadataFormat, MetadataSectionFormat};
+
+lazy_static::lazy_static! {
+    static ref FORMATS: Vec<MetadataSectionFormat> = vec![
+        MetadataSectionFormat{
+            section: i18n("File Information"),
+            formats: vec![
+                MetadataFormat{ label: i18n("File Name:"), id: NiepcePropertyIdx::NpFileNameProp as u32, 
type_: MetaDT::STRING, readonly: true },
+                MetadataFormat{ label: i18n("Folder:"), id: NiepcePropertyIdx::NpFolderProp as u32, 
type_:MetaDT::STRING, readonly: true },
+                MetadataFormat{ label: i18n("File Type:"), id: NiepcePropertyIdx::NpFileTypeProp as u32, 
type_:MetaDT::STRING, readonly: true },
+                MetadataFormat{ label: i18n("File Size:"), id: NiepcePropertyIdx::NpFileSizeProp as u32, 
type_:MetaDT::SIZE, readonly: true },
+                MetadataFormat{ label: i18n("Sidecar Files:"), id: NiepcePropertyIdx::NpSidecarsProp as u32, 
type_:MetaDT::STRING_ARRAY, readonly: true },
+            ]
+        },
+        MetadataSectionFormat{
+            section: i18n("Camera Information"),
+            formats: vec![
+                MetadataFormat{ label: i18n("Make:"), id: NiepcePropertyIdx::NpTiffMakeProp as u32, 
type_:MetaDT::STRING, readonly: true },
+                MetadataFormat{ label: i18n("Model:"), id: NiepcePropertyIdx::NpTiffModelProp as u32, 
type_:MetaDT::STRING, readonly: true },
+                MetadataFormat{ label: i18n("Lens:"), id: NiepcePropertyIdx::NpExifAuxLensProp as u32, 
type_:MetaDT::STRING, readonly: true },
+            ]
+        },
+        MetadataSectionFormat{
+            section: i18n("Shooting Information"),
+            formats: vec![
+                MetadataFormat{ label: i18n("Exposure Program:"), id: 
NiepcePropertyIdx::NpExifExposureProgramProp as u32, type_:MetaDT::STRING, readonly: true },
+                MetadataFormat{ label: i18n("Speed:"), id: NiepcePropertyIdx::NpExifExposureTimeProp as u32, 
type_:MetaDT::FRAC, readonly: true },
+                MetadataFormat{ label: i18n("Aperture:"), id: NiepcePropertyIdx::NpExifFNumberPropProp as 
u32, type_:MetaDT::FRAC_DEC, readonly: true },
+                MetadataFormat{ label: i18n("ISO:"), id: NiepcePropertyIdx::NpExifIsoSpeedRatingsProp as 
u32, type_:MetaDT::STRING, readonly: true },
+                MetadataFormat{ label: i18n("Exposure Bias:"), id: NiepcePropertyIdx::NpExifExposureBiasProp 
as u32, type_:MetaDT::FRAC_DEC, readonly: true },
+                MetadataFormat{ label: i18n("Flash:"), id: NiepcePropertyIdx::NpExifFlashFiredProp as u32, 
type_:MetaDT::STRING, readonly: true },
+                MetadataFormat{ label: i18n("Flash compensation:"), id: 
NiepcePropertyIdx::NpExifAuxFlashCompensationProp as u32, type_:MetaDT::STRING, readonly: true },
+                MetadataFormat{ label: i18n("Focal length:"), id: NiepcePropertyIdx::NpExifFocalLengthProp 
as u32, type_:MetaDT::FRAC_DEC, readonly: true },
+                MetadataFormat{ label: i18n("White balance:"), id: NiepcePropertyIdx::NpExifWbProp as u32, 
type_:MetaDT::STRING, readonly: true },
+                MetadataFormat{ label: i18n("Date:"), id: NiepcePropertyIdx::NpExifDateTimeOriginalProp as 
u32, type_:MetaDT::DATE, readonly: false },
+            ]
+        },
+        MetadataSectionFormat{
+            section: i18n("IPTC"),
+            formats: vec![
+                MetadataFormat{ label: i18n("Headline:"), id: NiepcePropertyIdx::NpIptcHeadlineProp as u32, 
type_:MetaDT::STRING, readonly: false },
+                MetadataFormat{ label: i18n("Caption:"), id: NiepcePropertyIdx::NpIptcDescriptionProp as 
u32, type_:MetaDT::TEXT, readonly: false },
+                MetadataFormat{ label: i18n("Rating:"), id: NiepcePropertyIdx::NpXmpRatingProp as u32, 
type_:MetaDT::STAR_RATING, readonly: false },
+                // FIXME change this type to the right one when there is a widget
+                MetadataFormat{ label: i18n("Label:"), id: NiepcePropertyIdx::NpXmpLabelProp as u32, 
type_:MetaDT::STRING, readonly: true },
+                MetadataFormat{ label: i18n("Keywords:"), id: NiepcePropertyIdx::NpIptcKeywordsProp as u32, 
type_:MetaDT::STRING_ARRAY, readonly: false },
+            ]
+        },
+        MetadataSectionFormat{
+            section: i18n("Rights"),
+            formats: vec![]
+        },
+    ];
+}
+
+pub fn get_format() -> &'static [MetadataSectionFormat] {
+    &FORMATS
+}
diff --git a/src/Makefile.am b/src/Makefile.am
index cbfb8686..22fea51c 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -27,7 +27,6 @@ RUST_SOURCES = \
        @top_srcdir@/crates/npc-engine/src/library/thumbnail_cache.rs \
        @top_srcdir@/crates/npc-engine/src/lib.rs \
        @top_srcdir@/crates/npc-fwk/Cargo.toml \
-       @top_srcdir@/crates/npc-fwk/build.rs \
        @top_srcdir@/crates/npc-fwk/src/base.rs \
        @top_srcdir@/crates/npc-fwk/src/base/date.rs \
        @top_srcdir@/crates/npc-fwk/src/base/debug.rs \
@@ -44,6 +43,7 @@ RUST_SOURCES = \
        @top_srcdir@/crates/npc-fwk/src/toolkit/movieutils.rs \
        @top_srcdir@/crates/npc-fwk/src/toolkit/thumbnail.rs \
        @top_srcdir@/crates/npc-fwk/src/toolkit/widgets.rs \
+       @top_srcdir@/crates/npc-fwk/src/toolkit/widgets/metadata_widget.rs \
        @top_srcdir@/crates/npc-fwk/src/toolkit/widgets/rating_label.rs \
        @top_srcdir@/crates/npc-fwk/src/toolkit/widgets/token_text_view.rs \
        @top_srcdir@/crates/npc-fwk/src/toolkit/widgets/toolbox_item.rs \
diff --git a/src/engine/db/libmetadata.cpp b/src/engine/db/libmetadata.cpp
index 324506c5..0537cc3f 100644
--- a/src/engine/db/libmetadata.cpp
+++ b/src/engine/db/libmetadata.cpp
@@ -1,7 +1,7 @@
 /*
  * niepce - db/libmetadata.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
@@ -27,6 +27,12 @@ fwk::PropertyBagPtr libmetadata_to_properties(const LibMetadata* meta,
     return fwk::property_bag_wrap(ffi::engine_libmetadata_to_properties(meta, &propset));
 }
 
+fwk::WrappedPropertyBagPtr libmetadata_to_wrapped_properties(const LibMetadata* meta,
+                                              const fwk::PropertySet& propset)
+{
+    return fwk::wrapped_property_bag_wrap(ffi::engine_libmetadata_to_wrapped_properties(meta, &propset));
+}
+
 }
 
 /*
diff --git a/src/engine/db/libmetadata.hpp b/src/engine/db/libmetadata.hpp
index 5b92fbfa..22e347b9 100644
--- a/src/engine/db/libmetadata.hpp
+++ b/src/engine/db/libmetadata.hpp
@@ -1,7 +1,7 @@
 /*
  * niepce - eng/db/libmetadata.hpp
  *
- * 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
@@ -28,6 +28,8 @@ namespace eng {
 
 fwk::PropertyBagPtr libmetadata_to_properties(const LibMetadata *meta,
                                               const fwk::PropertySet &propset);
+fwk::WrappedPropertyBagPtr libmetadata_to_wrapped_properties(const LibMetadata* meta,
+                                                             const fwk::PropertySet& propset);
 
 }
 
diff --git a/src/fwk/Makefile.am b/src/fwk/Makefile.am
index 48f729b8..1e1f426e 100644
--- a/src/fwk/Makefile.am
+++ b/src/fwk/Makefile.am
@@ -21,6 +21,14 @@ cxx_fwk_bindings.hpp: $(top_srcdir)/crates/npc-fwk/src/lib.rs
        @echo "Generating bindings header $@..."
        @$(RUST_CXXBRIDGE) --header $< > $@
 
+cxx_widgets_bindings.cpp: $(top_srcdir)/crates/npc-fwk/src/toolkit/widgets/metadata_widget.rs
+       @echo "Generating bindings $@..."
+       @$(RUST_CXXBRIDGE) $< > $@
+
+cxx_widgets_bindings.hpp: $(top_srcdir)/crates/npc-fwk/src/toolkit/widgets/metadata_widget.rs
+       @echo "Generating bindings header $@..."
+       @$(RUST_CXXBRIDGE) --header $< > $@
+
 cxx_eng_bindings.cpp: $(top_srcdir)/crates/npc-engine/src/lib.rs
        @echo "Generating bindings $@..."
        @$(RUST_CXXBRIDGE) $< > $@
@@ -29,12 +37,25 @@ cxx_eng_bindings.hpp: $(top_srcdir)/crates/npc-engine/src/lib.rs
        @echo "Generating bindings header $@..."
        @$(RUST_CXXBRIDGE) --header $< > $@
 
+cxx_npc_bindings.cpp: $(top_srcdir)/niepce-main/src/lib.rs
+       @echo "Generating bindings $@..."
+       @$(RUST_CXXBRIDGE) $< > $@
+
+cxx_npc_bindings.hpp: $(top_srcdir)/niepce-main/src/lib.rs
+       @echo "Generating bindings header $@..."
+       @$(RUST_CXXBRIDGE) --header $< > $@
+
 BUILT_SOURCES = \
         cxx_colour_bindings.cpp \
         cxx_colour_bindings.hpp \
         cxx_fwk_bindings.cpp \
         cxx_fwk_bindings.hpp \
         cxx_eng_bindings.cpp \
-        cxx_eng_bindings.hpp
+        cxx_eng_bindings.hpp \
+        cxx_widgets_bindings.cpp \
+        cxx_widgets_bindings.hpp \
+        cxx_npc_bindings.cpp \
+        cxx_npc_bindings.hpp \
+        $(NULL)
 
 CLEANFILES = $(BUILD_SOURCES)
diff --git a/src/fwk/base/propertybag.cpp b/src/fwk/base/propertybag.cpp
index aa6ec429..d7bf910a 100644
--- a/src/fwk/base/propertybag.cpp
+++ b/src/fwk/base/propertybag.cpp
@@ -45,9 +45,9 @@ PropertyBagPtr property_bag_wrap(PropertyBag* bag)
     return PropertyBagPtr(bag, &ffi::eng_property_bag_delete);
 }
 
-PropertyBagPtr property_bag_new()
+WrappedPropertyBagPtr wrapped_property_bag_wrap(WrappedPropertyBag* bag)
 {
-    return property_bag_wrap(ffi::eng_property_bag_new());
+    return WrappedPropertyBagPtr(bag, &ffi::fwk_wrapped_property_bag_delete);
 }
 
 PropertyValuePtr property_bag_value(const PropertyBagPtr& bag, PropertyIndex idx)
@@ -55,6 +55,11 @@ PropertyValuePtr property_bag_value(const PropertyBagPtr& bag, PropertyIndex idx
     return PropertyValuePtr::from_raw(ffi::eng_property_bag_value(bag.get(), idx));
 }
 
+PropertyValuePtr wrapped_property_bag_value(const WrappedPropertyBagPtr& bag, PropertyIndex idx)
+{
+    return PropertyValuePtr::from_raw(ffi::fwk_property_bag_value(bag.get(), idx));
+}
+
 bool set_value_for_property(PropertyBag& bag, ffi::NiepcePropertyIdx idx,
                             const PropertyValue& value)
 {
diff --git a/src/fwk/base/propertybag.hpp b/src/fwk/base/propertybag.hpp
index 30918037..7d3897d8 100644
--- a/src/fwk/base/propertybag.hpp
+++ b/src/fwk/base/propertybag.hpp
@@ -44,11 +44,14 @@ PropertySetPtr property_set_new();
  */
 typedef std::shared_ptr<PropertyBag> PropertyBagPtr;
 
-PropertyBagPtr property_bag_new();
 PropertyBagPtr property_bag_wrap(PropertyBag*);
-
 PropertyValuePtr property_bag_value(const PropertyBagPtr& bag, PropertyIndex key);
 
+typedef std::shared_ptr<WrappedPropertyBag> WrappedPropertyBagPtr;
+
+WrappedPropertyBagPtr wrapped_property_bag_wrap(WrappedPropertyBag* bag);
+PropertyValuePtr wrapped_property_bag_value(const WrappedPropertyBagPtr& bag, PropertyIndex key);
+
 /** return true if a property was removed prior to insertion */
 bool set_value_for_property(PropertyBag&, ffi::NiepcePropertyIdx idx, const PropertyValue& value);
 /** return property or an empty option */
diff --git a/src/fwk/cxx_prelude.hpp b/src/fwk/cxx_prelude.hpp
new file mode 100644
index 00000000..65283892
--- /dev/null
+++ b/src/fwk/cxx_prelude.hpp
@@ -0,0 +1,12 @@
+/*
+ * niepce - fwk/cxx_prelude.hpp
+ */
+
+#pragma once
+
+// things that need to be declared before anything.
+// early "extern C++"
+// And that the implementation needs too.
+namespace fwk {
+class WrappedPropertyBag;
+}
diff --git a/src/fwk/toolkit/Makefile.am b/src/fwk/toolkit/Makefile.am
index 41ac621a..94c00af4 100644
--- a/src/fwk/toolkit/Makefile.am
+++ b/src/fwk/toolkit/Makefile.am
@@ -37,6 +37,8 @@ libniepceframework_a_SOURCES = \
        ../cxx_colour_bindings.cpp \
        ../cxx_fwk_bindings.cpp \
        ../cxx_eng_bindings.cpp \
+       ../cxx_widgets_bindings.cpp \
+       ../cxx_npc_bindings.cpp \
        application.hpp application.cpp \
        appframe.hpp appframe.cpp \
        dialog.hpp dialog.cpp \
@@ -57,10 +59,7 @@ libniepceframework_a_SOURCES = \
        widgets/toolboxitemwidget.hpp widgets/toolboxitemwidget.cpp \
        widgets/editablehscale.hpp widgets/editablehscale.cpp \
        widgets/dock.cpp widgets/dock.hpp \
-       widgets/notabtextview.hpp widgets/notabtextview.cpp \
-       widgets/tokentextview.hpp widgets/tokentextview.cpp \
        dockable.hpp dockable.cpp \
-       metadatawidget.hpp metadatawidget.cpp \
        undo.hpp undo.cpp \
        command.hpp \
        $(NULL)
diff --git a/src/fwk/utils/init.cpp b/src/fwk/utils/init.cpp
index f19f1d54..c6525b78 100644
--- a/src/fwk/utils/init.cpp
+++ b/src/fwk/utils/init.cpp
@@ -33,7 +33,7 @@ void init()
 {
   Gio::init();
 
-  ffi::niepce_init();
+  npc::niepce_init();
 }
 
 
diff --git a/src/niepce/ui/gridviewmodule.cpp b/src/niepce/ui/gridviewmodule.cpp
index 1746291b..3f99a707 100644
--- a/src/niepce/ui/gridviewmodule.cpp
+++ b/src/niepce/ui/gridviewmodule.cpp
@@ -236,8 +236,8 @@ void GridViewModule::select_image(eng::library_id_t id)
     }
 }
 
-void GridViewModule::on_metadata_changed(const fwk::PropertyBagPtr & props,
-                                         const fwk::PropertyBagPtr & old)
+void GridViewModule::on_metadata_changed(const fwk::WrappedPropertyBagPtr& props,
+                                         const fwk::WrappedPropertyBagPtr& old)
 {
     // TODO this MUST be more generic
     DBG_OUT("on_metadata_changed()");
diff --git a/src/niepce/ui/gridviewmodule.hpp b/src/niepce/ui/gridviewmodule.hpp
index 15203958..262ffdda 100644
--- a/src/niepce/ui/gridviewmodule.hpp
+++ b/src/niepce/ui/gridviewmodule.hpp
@@ -77,11 +77,10 @@ public:
 protected:
   virtual Gtk::Widget * buildWidget() override;
 
-
 private:
   static bool get_colour_callback_c(int32_t label, ffi::RgbColour* out, const void* user_data);
   std::optional<fwk::RgbColourPtr> get_colour_callback(int32_t label) const;
-  void on_metadata_changed(const fwk::PropertyBagPtr&, const fwk::PropertyBagPtr& old);
+  void on_metadata_changed(const fwk::WrappedPropertyBagPtr&, const fwk::WrappedPropertyBagPtr& old);
   static void on_rating_changed(GtkCellRenderer*, eng::library_id_t id, int rating,
                                 gpointer user_data);
   void on_librarylistview_click(const Glib::RefPtr<Gtk::GestureClick>& gesture, double, double);
diff --git a/src/niepce/ui/metadatapanecontroller.cpp b/src/niepce/ui/metadatapanecontroller.cpp
index 8c336e73..e8e56554 100644
--- a/src/niepce/ui/metadatapanecontroller.cpp
+++ b/src/niepce/ui/metadatapanecontroller.cpp
@@ -24,78 +24,28 @@
 #include <gtkmm/entry.h>
 
 #include "fwk/base/debug.hpp"
-#include "fwk/toolkit/metadatawidget.hpp"
 #include "engine/db/properties.hpp"
 #include "engine/db/libmetadata.hpp"
 #include "metadatapanecontroller.hpp"
 
+#include "rust_bindings.hpp"
+
 namespace ui {
 
 using ffi::NiepcePropertyIdx;
 
-const std::vector<fwk::MetaDataSectionFormat>&
-MetaDataPaneController::get_format()
-{
-    static const std::vector<fwk::MetaDataSectionFormat> s_format = {
-        { _("File Information"),
-          {
-              { _("File Name:"), NiepcePropertyIdx::NpFileNameProp, fwk::MetaDT::STRING, true },
-              { _("Folder:"), NiepcePropertyIdx::NpFolderProp, fwk::MetaDT::STRING, true },
-              { _("File Type:"), NiepcePropertyIdx::NpFileTypeProp, fwk::MetaDT::STRING, true },
-              { _("File Size:"), NiepcePropertyIdx::NpFileSizeProp, fwk::MetaDT::SIZE, true },
-              { _("Sidecar Files:"), NiepcePropertyIdx::NpSidecarsProp, fwk::MetaDT::STRING_ARRAY, true },
-          }
-        },
-        { _("Camera Information"),
-          {
-              { _("Make:"), NiepcePropertyIdx::NpTiffMakeProp, fwk::MetaDT::STRING, true },
-              { _("Model:"), NiepcePropertyIdx::NpTiffModelProp, fwk::MetaDT::STRING, true },
-              { _("Lens:"), NiepcePropertyIdx::NpExifAuxLensProp, fwk::MetaDT::STRING, true },
-          }
-        },
-        { _("Shooting Information"),
-          {
-              { _("Exposure Program:"), NiepcePropertyIdx::NpExifExposureProgramProp, fwk::MetaDT::STRING, 
true },
-              { _("Speed:"), NiepcePropertyIdx::NpExifExposureTimeProp, fwk::MetaDT::FRAC, true },
-              { _("Aperture:"), NiepcePropertyIdx::NpExifFNumberPropProp, fwk::MetaDT::FRAC_DEC, true },
-              { _("ISO:"), NiepcePropertyIdx::NpExifIsoSpeedRatingsProp, fwk::MetaDT::STRING, true },
-              { _("Exposure Bias:"), NiepcePropertyIdx::NpExifExposureBiasProp, fwk::MetaDT::FRAC_DEC, true 
},
-              { _("Flash:"), NiepcePropertyIdx::NpExifFlashFiredProp, fwk::MetaDT::STRING, true },
-              { _("Flash compensation:"), NiepcePropertyIdx::NpExifAuxFlashCompensationProp, 
fwk::MetaDT::STRING, true },
-              { _("Focal length:"), NiepcePropertyIdx::NpExifFocalLengthProp, fwk::MetaDT::FRAC_DEC, true },
-              { _("White balance:"), NiepcePropertyIdx::NpExifWbProp, fwk::MetaDT::STRING, true },
-              { _("Date:"), NiepcePropertyIdx::NpExifDateTimeOriginalProp, fwk::MetaDT::DATE, false },
-          }
-        },
-        { _("IPTC"),
-          {
-              { _("Headline:"), NiepcePropertyIdx::NpIptcHeadlineProp, fwk::MetaDT::STRING, false },
-              { _("Caption:"), NiepcePropertyIdx::NpIptcDescriptionProp, fwk::MetaDT::TEXT, false },
-              { _("Rating:"), NiepcePropertyIdx::NpXmpRatingProp, fwk::MetaDT::STAR_RATING, false },
-              // FIXME change this type to the right one when there is a widget
-              { _("Label:"), NiepcePropertyIdx::NpXmpLabelProp, fwk::MetaDT::STRING, true },
-              { _("Keywords:"), NiepcePropertyIdx::NpIptcKeywordsProp, fwk::MetaDT::STRING_ARRAY, false },
-          }
-        },
-        { _("Rights"),
-          std::vector<fwk::MetaDataFormat>()
-        },
-    };
-    return s_format;
-}
-
 const fwk::PropertySet* MetaDataPaneController::get_property_set()
 {
     static fwk::PropertySet* propset = nullptr;
     if(!propset) {
         propset = ffi::eng_property_set_new();
-        const std::vector<fwk::MetaDataSectionFormat>& formats = get_format();
+        rust::Slice<const fwk::MetadataSectionFormat> formats = npc::get_format();
 
         auto current = formats.begin();
         while (current != formats.end()) {
             auto format = current->formats.begin();
             while (format != current->formats.end()) {
-                ffi::eng_property_set_add(propset, format->id);
+                ffi::eng_property_set_add(propset, (NiepcePropertyIdx)format->id);
                 format++;
             }
             current++;
@@ -113,9 +63,25 @@ MetaDataPaneController::MetaDataPaneController()
 
 MetaDataPaneController::~MetaDataPaneController()
 {
+    for (const auto& w : m_widgets) {
+        auto w_ptr = reinterpret_cast<GtkWidget*>(w.first->gobj());
+        g_signal_handler_disconnect(w_ptr, w.second);
+    }
 }
 
-Gtk::Widget * 
+void
+MetaDataPaneController::metadata_changed_cb(GtkWidget*, const fwk::WrappedPropertyBag* props,
+                                            const fwk::WrappedPropertyBag* old_props,
+                                            MetaDataPaneController* self)
+{
+    self->on_metadata_changed(
+        fwk::wrapped_property_bag_wrap(
+            ffi::fwk_wrapped_property_bag_clone(props)),
+        fwk::wrapped_property_bag_wrap(
+            ffi::fwk_wrapped_property_bag_clone(old_props)));
+}
+
+Gtk::Widget *
 MetaDataPaneController::buildWidget()
 {
     if(m_widget) {
@@ -125,25 +91,28 @@ MetaDataPaneController::buildWidget()
     m_widget = box;
     DBG_ASSERT(box, "dockable vbox not found");
 
-    const auto& formats = get_format();
+    const auto& formats = npc::get_format();
 
     auto current = formats.begin();
     while (current != formats.end()) {
-        auto w = Gtk::manage(new fwk::MetaDataWidget(current->section));
-        box->append(*w);
-        w->set_data_format(&*current);
-        m_widgets.push_back(w);
-        w->signal_metadata_changed.connect(
-            sigc::mem_fun(*this,
-                          &MetaDataPaneController::on_metadata_changed));
+        auto w = fwk::MetadataWidget_new(current->section);
+        auto w_ptr = reinterpret_cast<GtkWidget*>(w->gobj());
+        DBG_ASSERT(w_ptr, "MetadataWidget is null");
+        gtk_box_append(box->gobj(), w_ptr);
+        w->set_data_format(*current);
+        auto handler = g_signal_connect(w_ptr, "metadata-changed",
+                         G_CALLBACK(MetaDataPaneController::metadata_changed_cb),
+                         this);
+        m_widgets.push_back(std::make_pair(std::move(w), handler));
+
         current++;
     }
 
     return m_widget;
 }
 
-void MetaDataPaneController::on_metadata_changed(const fwk::PropertyBagPtr& props,
-                                                 const fwk::PropertyBagPtr& old)
+void MetaDataPaneController::on_metadata_changed(const fwk::WrappedPropertyBagPtr& props,
+                                                 const fwk::WrappedPropertyBagPtr& old)
 {
     signal_metadata_changed.emit(props, old);
 }
@@ -152,15 +121,18 @@ void MetaDataPaneController::display(eng::library_id_t file_id, const eng::LibMe
 {
     m_fileid = file_id;
     DBG_OUT("displaying metadata");
-    fwk::PropertyBagPtr properties;
-    if(meta) {
+    fwk::WrappedPropertyBagPtr properties;
+    if (meta) {
         const fwk::PropertySet* propset = get_property_set();
-        properties = eng::libmetadata_to_properties(meta, *propset);
+        properties = eng::libmetadata_to_wrapped_properties(meta, *propset);
+    }
+    for (const auto& w : m_widgets) {
+        if (properties) {
+            w.first->set_data_source(*properties);
+        } else {
+            w.first->set_data_source_none();
+        }
     }
-    std::for_each(m_widgets.begin(), m_widgets.end(),
-                  [properties] (decltype(m_widgets)::value_type w) {
-                      w->set_data_source(properties);
-                  });
 }
 
 }
diff --git a/src/niepce/ui/metadatapanecontroller.hpp b/src/niepce/ui/metadatapanecontroller.hpp
index 6c53065d..f7eb9643 100644
--- a/src/niepce/ui/metadatapanecontroller.hpp
+++ b/src/niepce/ui/metadatapanecontroller.hpp
@@ -19,16 +19,14 @@
 
 #pragma once
 
-#include <gtkmm/box.h>
-
 #include "engine/db/libmetadata.hpp"
 #include "fwk/base/propertybag.hpp"
 #include "fwk/utils/exempi.hpp"
 #include "fwk/toolkit/dockable.hpp"
 
+#include "rust_bindings.hpp"
+
 namespace fwk {
-struct MetaDataSectionFormat;
-class MetaDataWidget;
 class Dock;
 }
 
@@ -46,14 +44,16 @@ 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::WrappedPropertyBagPtr&, const fwk::WrappedPropertyBagPtr&)> 
signal_metadata_changed;
 private:
-    void on_metadata_changed(const fwk::PropertyBagPtr &,
-                             const fwk::PropertyBagPtr & old);
+    void on_metadata_changed(const fwk::WrappedPropertyBagPtr&,
+                             const fwk::WrappedPropertyBagPtr& old);
+    static void metadata_changed_cb(GtkWidget*, const fwk::WrappedPropertyBag* props,
+                                    const fwk::WrappedPropertyBag* old_props,
+                                    MetaDataPaneController* self);
 
-    std::vector<fwk::MetaDataWidget *> m_widgets;
+    std::vector<std::pair<::rust::Box<fwk::MetadataWidget>, guint>> m_widgets;
 
-    static const std::vector<fwk::MetaDataSectionFormat>& get_format();
     static const fwk::PropertySet* get_property_set();
 
     eng::library_id_t m_fileid;
diff --git a/src/niepce/ui/selectioncontroller.cpp b/src/niepce/ui/selectioncontroller.cpp
index 0684a420..18ea1f5f 100644
--- a/src/niepce/ui/selectioncontroller.cpp
+++ b/src/niepce/ui/selectioncontroller.cpp
@@ -1,7 +1,7 @@
 /*
  * niepce - niepce/ui/selectioncontroller.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
@@ -206,16 +206,16 @@ bool SelectionController::_set_metadata(const std::string & undo_label,
 
 bool SelectionController::_set_metadata(const std::string & undo_label,
                                         eng::library_id_t file_id,
-                                        const fwk::PropertyBagPtr & props,
-                                        const fwk::PropertyBagPtr & old)
+                                        const fwk::WrappedPropertyBagPtr& props,
+                                        const fwk::WrappedPropertyBagPtr& old)
 {
     auto undo = fwk::Application::app()->begin_undo(undo_label);
-    auto len = eng_property_bag_len(props.get());
+    auto len = ffi::fwk_property_bag_len(props.get());
     for (size_t i = 0; i < len; i++) {
-        auto key = eng_property_bag_key_by_index(props.get(), i);
+        auto key = ffi::fwk_property_bag_key_by_index(props.get(), i);
         // This is a shared_ptr because it's the only way to pass it to the lambda
         // as Box<> isn't copyable
-        auto value = std::make_shared<fwk::PropertyValuePtr>(fwk::property_bag_value(old, key));
+        auto value = std::make_shared<fwk::PropertyValuePtr>(fwk::wrapped_property_bag_value(old, key));
         /*
         if (!result.empty()) {
             value = result.unwrap();
@@ -228,7 +228,7 @@ bool SelectionController::_set_metadata(const std::string & undo_label,
         */
 
         auto libclient = getLibraryClient();
-        auto new_value = std::make_shared<fwk::PropertyValuePtr>(fwk::property_bag_value(props, key));
+        auto new_value = std::make_shared<fwk::PropertyValuePtr>(fwk::wrapped_property_bag_value(props, 
key));
         undo->new_command<void>(
             [libclient, file_id, key, new_value] () {
                 ffi::libraryclient_set_metadata(
@@ -294,8 +294,8 @@ void SelectionController::set_property(ffi::NiepcePropertyIdx idx, int value)
     }
 }
 
-void SelectionController::set_properties(const fwk::PropertyBagPtr & props,
-                                         const fwk::PropertyBagPtr & old)
+void SelectionController::set_properties(const fwk::WrappedPropertyBagPtr& props,
+                                         const fwk::WrappedPropertyBagPtr& old)
 {
     eng::library_id_t selection = get_selection();
     if (selection >= 0) {
diff --git a/src/niepce/ui/selectioncontroller.hpp b/src/niepce/ui/selectioncontroller.hpp
index fbe268cf..4d6503d8 100644
--- a/src/niepce/ui/selectioncontroller.hpp
+++ b/src/niepce/ui/selectioncontroller.hpp
@@ -94,8 +94,8 @@ public:
 
     void set_property(ffi::NiepcePropertyIdx idx, int value);
 
-    void set_properties(const fwk::PropertyBagPtr & props,
-                        const fwk::PropertyBagPtr & old);
+    void set_properties(const fwk::WrappedPropertyBagPtr& props,
+                        const fwk::WrappedPropertyBagPtr& old);
 
     /** the content will change */
     void content_will_change();
@@ -119,8 +119,8 @@ private:
                        int old_value, int new_value);
     bool _set_metadata(const std::string & undo_label,
                        eng::library_id_t file_id,
-                       const fwk::PropertyBagPtr & props,
-                       const fwk::PropertyBagPtr & old);
+                       const fwk::WrappedPropertyBagPtr& props,
+                       const fwk::WrappedPropertyBagPtr& old);
     /** move the selection and emit the signal
      * @param backwards true if the move is backwards.
      */
diff --git a/src/rust_bindings.hpp b/src/rust_bindings.hpp
index 9539c612..a653285c 100644
--- a/src/rust_bindings.hpp
+++ b/src/rust_bindings.hpp
@@ -25,6 +25,7 @@
 
 #include "fwk/cxx_fwk_bindings.hpp"
 #include "fwk/cxx_eng_bindings.hpp"
+#include "fwk/cxx_npc_bindings.hpp"
 
 namespace ffi {
 class rust_str;
@@ -36,6 +37,7 @@ typedef rust_str str;
 typedef fwk::FileList FileList;
 typedef fwk::PropertyValue PropertyValue;
 typedef fwk::RgbColour RgbColour;
+typedef fwk::WrappedPropertyBag WrappedPropertyBag;
 typedef eng::Label Label;
 typedef eng::LibFile LibFile;
 typedef eng::LibMetadata LibMetadata;
@@ -44,7 +46,6 @@ struct NiepcePropertyBag;
 struct NiepcePropertySet;
 }
 
-#include "target/fwk_bindings.h"
 #include "target/eng_bindings.h"
 #include "target/bindings.h"
 


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