[niepce] rust+ui: port the library cell renderer to Rust



commit 9c6853619786ed35a4fb1b3ca957793cae4ffedb
Author: Hubert Figuière <hub figuiere net>
Date:   Mon Jan 27 00:42:44 2020 -0500

    rust+ui: port the library cell renderer to Rust
    
    - still not used
    - tweak the model a bit to match
    - Change the prototype of the get_colour callback

 Cargo.lock                                         |   3 +
 Cargo.toml                                         |   5 +-
 build.rs                                           |   1 +
 crates/npc-engine/src/db/libfile.rs                |  12 +
 .../npc-fwk/src/toolkit/clickable_cell_renderer.rs |  28 +
 crates/npc-fwk/src/toolkit/mod.rs                  |   2 +
 src/Makefile.am                                    |   1 +
 src/lib.rs                                         |   3 +
 src/libraryclient/uidataprovider.cpp               |   8 +-
 src/libraryclient/uidataprovider.hpp               |   2 +-
 src/niepce/ui/gridviewmodule.cpp                   |  12 +-
 src/niepce/ui/imageliststore.cpp                   |   4 +-
 src/niepce/ui/imageliststore.hpp                   |   2 +-
 src/niepce/ui/library_cell_renderer.rs             | 567 +++++++++++++++++++++
 src/niepce/ui/librarycellrenderer.cpp              |  11 +-
 src/niepce/ui/librarycellrenderer.hpp              |   4 +-
 src/niepce/ui/mod.rs                               |   1 +
 src/niepce/ui/thumbstripview.cpp                   |  12 +-
 18 files changed, 654 insertions(+), 24 deletions(-)
---
diff --git a/Cargo.lock b/Cargo.lock
index 8bdeb08..9131555 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -572,7 +572,10 @@ dependencies = [
 name = "niepce_rust"
 version = "0.1.0"
 dependencies = [
+ "cairo-rs 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "cbindgen 0.8.4 (registry+https://github.com/rust-lang/crates.io-index)",
+ "gdk 0.12.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "gdk-pixbuf 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "gettext-rs 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "gio 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "gio-sys 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)",
diff --git a/Cargo.toml b/Cargo.toml
index 251e8ce..f5d7ced 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -10,6 +10,9 @@ gettext-rs = "0.3.0"
 glib = { version = "^0.9.0" }
 gio-sys = "*"
 gio = "^0.8.0"
+cairo-rs = "*"
+gdk = "*"
+gdk-pixbuf = "*"
 gtk-sys = { version = "*", features = ["v3_22"] }
 gtk = { version = "^0.8.0", git = "https://github.com/hfiguiere/gtk.git";, branch = "0.8.0-p1" }
 libc = "0.2.39"
@@ -26,4 +29,4 @@ name = "niepce_rust"
 crate-type = ["staticlib", "lib"]
 
 [[example]]
-name = "widget-test"
\ No newline at end of file
+name = "widget-test"
diff --git a/build.rs b/build.rs
index 65cf8b0..5b08a40 100644
--- a/build.rs
+++ b/build.rs
@@ -19,6 +19,7 @@ fn main() {
             .exclude_item("GtkWindow")
             .exclude_item("GtkToolbar")
             .exclude_item("GtkIconView")
+            .exclude_item("GtkCellRenderer")
             .exclude_item("GtkWidget")
             .exclude_item("GFileInfo")
             .exclude_item("RgbColour")
diff --git a/crates/npc-engine/src/db/libfile.rs b/crates/npc-engine/src/db/libfile.rs
index 2776421..33fa62a 100644
--- a/crates/npc-engine/src/db/libfile.rs
+++ b/crates/npc-engine/src/db/libfile.rs
@@ -51,6 +51,18 @@ pub enum FileStatus {
     Ok = 0,
     /// File is missing
     Missing = 1,
+    /// Invalid
+    Invalid = -1,
+}
+
+impl From<i32> for FileStatus {
+    fn from(t: i32) -> Self {
+        match t {
+            0 => FileStatus::Ok,
+            1 => FileStatus::Missing,
+            _ => FileStatus::Invalid,
+        }
+    }
 }
 
 impl From<i32> for FileType {
diff --git a/crates/npc-fwk/src/toolkit/clickable_cell_renderer.rs 
b/crates/npc-fwk/src/toolkit/clickable_cell_renderer.rs
new file mode 100644
index 0000000..296ef87
--- /dev/null
+++ b/crates/npc-fwk/src/toolkit/clickable_cell_renderer.rs
@@ -0,0 +1,28 @@
+/*
+ * niepce - npc-fwk/toolkit/clickable_cell_renderer.rs
+ *
+ * Copyright (C) 2020 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/>.
+ */
+
+/// Trait for get clicks from cell renderer.
+/// This is used to work around some bug in Gtk.
+pub trait ClickableCellRenderer {
+    fn hit(&mut self, x: i32, y: i32);
+    fn x(&self) -> i32;
+    fn y(&self) -> i32;
+    fn is_hit(&self) -> bool;
+    fn reset_hit(&mut self);
+}
diff --git a/crates/npc-fwk/src/toolkit/mod.rs b/crates/npc-fwk/src/toolkit/mod.rs
index 5adcdc8..659d60b 100644
--- a/crates/npc-fwk/src/toolkit/mod.rs
+++ b/crates/npc-fwk/src/toolkit/mod.rs
@@ -16,6 +16,8 @@
  * You should have received a copy of the GNU General Public License
  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
  */
+
+pub mod clickable_cell_renderer;
 pub mod mimetype;
 pub mod widgets;
 
diff --git a/src/Makefile.am b/src/Makefile.am
index 7c04234..c0d780a 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -56,6 +56,7 @@ RUST_SOURCES = \
        @top_srcdir@/src/niepce/ui/dialogs/confirm.rs \
        @top_srcdir@/src/niepce/ui/dialogs/requestnewfolder.rs \
        @top_srcdir@/src/niepce/ui/thumb_nav.rs \
+       @top_srcdir@/src/niepce/ui/library_cell_renderer.rs \
        $(NULL)
 
 EXTRA_DIST = \
diff --git a/src/lib.rs b/src/lib.rs
index 2d38e64..dcaf462 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -17,6 +17,9 @@
  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
  */
 
+extern crate cairo;
+extern crate gdk;
+extern crate gdk_pixbuf;
 extern crate gettextrs;
 extern crate gio;
 extern crate gio_sys;
diff --git a/src/libraryclient/uidataprovider.cpp b/src/libraryclient/uidataprovider.cpp
index c7d571b..f481bb0 100644
--- a/src/libraryclient/uidataprovider.cpp
+++ b/src/libraryclient/uidataprovider.cpp
@@ -58,15 +58,15 @@ void UIDataProvider::deleteLabel(int id)
     }
 }
 
-fwk::Option<fwk::RgbColourPtr> UIDataProvider::colourForLabel(int id) const
+fwk::Option<fwk::RgbColour> UIDataProvider::colourForLabel(int32_t id) const
 {
     for(auto label : m_labels) {
         if (engine_db_label_id(label.get()) == id) {
-            return fwk::Option<fwk::RgbColourPtr>(
-                fwk::rgbcolour_clone(engine_db_label_colour(label.get())));
+            return fwk::Option<fwk::RgbColour>(
+                *engine_db_label_colour(label.get()));
         }
     }
-    return fwk::Option<fwk::RgbColourPtr>();
+    return fwk::Option<fwk::RgbColour>();
 }
 
 
diff --git a/src/libraryclient/uidataprovider.hpp b/src/libraryclient/uidataprovider.hpp
index f57981d..ce3c9a3 100644
--- a/src/libraryclient/uidataprovider.hpp
+++ b/src/libraryclient/uidataprovider.hpp
@@ -36,7 +36,7 @@ public:
     void updateLabel(const eng::Label &);
     void addLabel(const eng::Label & l);
     void deleteLabel(int id);
-    fwk::Option<fwk::RgbColourPtr> colourForLabel(int id) const;
+    fwk::Option<fwk::RgbColour> colourForLabel(int32_t id) const;
     const eng::LabelList & getLabels() const
         { return m_labels; }
 private:
diff --git a/src/niepce/ui/gridviewmodule.cpp b/src/niepce/ui/gridviewmodule.cpp
index 9ce98de..bc3a200 100644
--- a/src/niepce/ui/gridviewmodule.cpp
+++ b/src/niepce/ui/gridviewmodule.cpp
@@ -114,13 +114,17 @@ Gtk::Widget * GridViewModule::buildWidget()
   libraryclient::UIDataProviderWeakPtr ui_data_provider(m_shell.get_ui_data_provider());
   LibraryCellRenderer* libcell = Gtk::manage(
       new LibraryCellRenderer(
-          [ui_data_provider] (int label) {
+          [ui_data_provider] (int32_t label, ffi::RgbColour* out) {
               auto provider = ui_data_provider.lock();
+              DBG_ASSERT(static_cast<bool>(provider), "couldn't lock UI provider");
               if (provider) {
-                  return provider->colourForLabel(label);
+                  auto c = provider->colourForLabel(label);
+                  if (c.ok() && out) {
+                      *out = c.unwrap();
+                      return true;
+                  }
               }
-              ERR_OUT("couldn't lock UI provider");
-              return fwk::Option<fwk::RgbColourPtr>();
+              return false;
           })
       );
   libcell->signal_rating_changed.connect(
diff --git a/src/niepce/ui/imageliststore.cpp b/src/niepce/ui/imageliststore.cpp
index b9ae872..f854f37 100644
--- a/src/niepce/ui/imageliststore.cpp
+++ b/src/niepce/ui/imageliststore.cpp
@@ -117,7 +117,7 @@ void ImageListStore::add_libfile(const eng::LibFilePtr & f)
     row[m_columns.m_libfile] = f;
     row[m_columns.m_strip_thumb]
         = fwk::gdkpixbuf_scale_to_fit(icon, 100);
-    row[m_columns.m_file_status] = eng::FileStatus::Ok;
+    row[m_columns.m_file_status] = static_cast<gint>(eng::FileStatus::Ok);
     m_idmap[engine_db_libfile_id(f.get())] = riter;
 }
 
@@ -181,7 +181,7 @@ void ImageListStore::on_lib_notification(const eng::LibNotification &ln)
         auto iter = m_idmap.find(id);
         if (iter != m_idmap.end()) {
             Gtk::TreeRow row = *(iter->second);
-            row[m_columns.m_file_status] = status;
+            row[m_columns.m_file_status] = static_cast<gint>(status);
         }
         break;
     }
diff --git a/src/niepce/ui/imageliststore.hpp b/src/niepce/ui/imageliststore.hpp
index a5bb3bd..a0ceebb 100644
--- a/src/niepce/ui/imageliststore.hpp
+++ b/src/niepce/ui/imageliststore.hpp
@@ -59,7 +59,7 @@ public:
         Gtk::TreeModelColumn<Glib::RefPtr<Gdk::Pixbuf> > m_pix;
         Gtk::TreeModelColumn<eng::LibFilePtr> m_libfile;
         Gtk::TreeModelColumn<Glib::RefPtr<Gdk::Pixbuf> > m_strip_thumb;
-        Gtk::TreeModelColumn<eng::FileStatus> m_file_status;
+        Gtk::TreeModelColumn<gint> m_file_status;
     };
 
     Gtk::TreePath get_path_from_id(eng::library_id_t id) const;
diff --git a/src/niepce/ui/library_cell_renderer.rs b/src/niepce/ui/library_cell_renderer.rs
new file mode 100644
index 0000000..b4d5f7d
--- /dev/null
+++ b/src/niepce/ui/library_cell_renderer.rs
@@ -0,0 +1,567 @@
+/*
+ * niepce - niepce/ui/library_cell_renderer.rs
+ *
+ * Copyright (C) 2020 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 once_cell::unsync::Lazy;
+use std::cell::{Cell, RefCell};
+
+use cairo;
+use gdk;
+use gdk::prelude::*;
+use gdk_pixbuf::Pixbuf;
+use glib::subclass;
+use glib::subclass::prelude::*;
+use glib::translate::*;
+use glib::Type;
+use gtk;
+use gtk::prelude::*;
+use gtk::subclass::prelude::*;
+use gtk::CellRendererPixbufClass;
+
+use npc_engine::db::libfile::{FileStatus, FileType, LibFile};
+use npc_fwk::base::rgbcolour::RgbColour;
+use npc_fwk::toolkit::clickable_cell_renderer::ClickableCellRenderer;
+use npc_fwk::toolkit::widgets::rating_label::RatingLabel;
+
+const CELL_PADDING: i32 = 4;
+
+struct Emblems {
+    raw: Pixbuf,
+    raw_jpeg: Pixbuf,
+    img: Pixbuf,
+    video: Pixbuf,
+    unknown: Pixbuf,
+    status_missing: Pixbuf,
+    flag_reject: Pixbuf,
+    flag_pick: Pixbuf,
+}
+
+const EMBLEMS: Lazy<Emblems> = Lazy::new(|| Emblems {
+    raw: Pixbuf::new_from_resource("/org/gnome/Niepce/pixmaps/niepce-raw-fmt.png").unwrap(),
+    raw_jpeg: Pixbuf::new_from_resource("/org/gnome/Niepce/pixmaps/niepce-rawjpeg-fmt.png")
+        .unwrap(),
+    img: Pixbuf::new_from_resource("/org/gnome/Niepce/pixmaps/niepce-img-fmt.png").unwrap(),
+    video: Pixbuf::new_from_resource("/org/gnome/Niepce/pixmaps/niepce-video-fmt.png").unwrap(),
+    unknown: Pixbuf::new_from_resource("/org/gnome/Niepce/pixmaps/niepce-unknown-fmt.png").unwrap(),
+    status_missing: Pixbuf::new_from_resource("/org/gnome/Niepce/pixmaps/niepce-missing.png")
+        .unwrap(),
+    flag_reject: Pixbuf::new_from_resource("/org/gnome/Niepce/pixmaps/niepce-flag-reject.png")
+        .unwrap(),
+    flag_pick: Pixbuf::new_from_resource("/org/gnome/Niepce/pixmaps/niepce-flag-pick.png").unwrap(),
+});
+
+glib_wrapper! {
+    pub struct LibraryCellRenderer(
+        Object<subclass::simple::InstanceStruct<LibraryCellRendererPriv>,
+        subclass::simple::ClassStruct<LibraryCellRendererPriv>,
+        LibraryCellRendererClass>)
+        @extends gtk::CellRendererPixbuf, gtk::CellRenderer;
+
+    match fn {
+        get_type => || LibraryCellRendererPriv::get_type().to_glib(),
+    }
+}
+
+impl LibraryCellRenderer {
+    pub fn new(callback: Option<GetColourCallback>) -> Self {
+        let obj: Self = glib::Object::new(
+            Self::static_type(),
+            &[("mode", &gtk::CellRendererMode::Activatable)],
+        )
+        .expect("Failed to create Library Cell Renderer")
+        .downcast()
+        .expect("Created Library Cell Renderer is of wrong type");
+
+        let priv_ = LibraryCellRendererPriv::from_instance(&obj);
+        priv_.get_colour_callback.replace(callback);
+
+        obj
+    }
+}
+
+#[derive(Default)]
+struct ClickableCell {
+    x: i32,
+    y: i32,
+    hit: bool,
+}
+
+impl ClickableCellRenderer for LibraryCellRenderer {
+    fn hit(&mut self, x: i32, y: i32) {
+        let priv_ = LibraryCellRendererPriv::from_instance(self);
+        priv_
+            .clickable_cell
+            .replace(ClickableCell { x, y, hit: true });
+    }
+
+    fn x(&self) -> i32 {
+        let priv_ = LibraryCellRendererPriv::from_instance(self);
+        priv_.clickable_cell.borrow().x
+    }
+
+    fn y(&self) -> i32 {
+        let priv_ = LibraryCellRendererPriv::from_instance(self);
+        priv_.clickable_cell.borrow().y
+    }
+
+    fn is_hit(&self) -> bool {
+        let priv_ = LibraryCellRendererPriv::from_instance(self);
+        priv_.clickable_cell.borrow().hit
+    }
+
+    fn reset_hit(&mut self) {
+        let priv_ = LibraryCellRendererPriv::from_instance(self);
+        priv_.clickable_cell.borrow_mut().hit = false;
+    }
+}
+
+/// Wrap a libfile into something that can be in a glib::Value
+#[derive(Clone)]
+struct CellLibFile(LibFile);
+
+impl glib::subclass::boxed::BoxedType for CellLibFile {
+    const NAME: &'static str = "CellLibFile";
+
+    glib_boxed_type!();
+}
+glib_boxed_derive_traits!(CellLibFile);
+
+type GetColourCallback = unsafe extern "C" fn(i32, *mut RgbColour) -> bool;
+
+pub struct LibraryCellRendererPriv {
+    libfile: RefCell<Option<CellLibFile>>,
+    status: Cell<FileStatus>,
+    size: Cell<i32>,
+    pad: Cell<i32>,
+    drawborder: Cell<bool>,
+    draw_emblem: Cell<bool>,
+    draw_rating: Cell<bool>,
+    draw_label: Cell<bool>,
+    draw_flag: Cell<bool>,
+    draw_status: Cell<bool>,
+    clickable_cell: RefCell<ClickableCell>,
+    get_colour_callback: RefCell<Option<GetColourCallback>>,
+}
+
+impl LibraryCellRendererPriv {
+    fn set_status(&self, status: FileStatus) {
+        self.status.set(status);
+    }
+
+    fn set_libfile(&self, libfile: Option<CellLibFile>) {
+        self.libfile.replace(libfile);
+    }
+
+    fn do_draw_thumbnail(&self, cr: &cairo::Context, pixbuf: &Pixbuf, r: &gdk::Rectangle) {
+        let w = pixbuf.get_width();
+        let h = pixbuf.get_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();
+
+        cr.set_source_rgb(1.0, 1.0, 1.0);
+        cr.rectangle(x, y, w.into(), h.into());
+        cr.stroke();
+
+        cr.set_source_pixbuf(&pixbuf, x, y);
+        cr.paint();
+    }
+
+    fn do_draw_flag(cr: &cairo::Context, flag: i32, r: &gdk::Rectangle) {
+        if flag == 0 {
+            return;
+        }
+        let pixbuf = match flag {
+            -1 => EMBLEMS.flag_reject.clone(),
+            1 => EMBLEMS.flag_pick.clone(),
+            _ => return,
+        };
+
+        let w = pixbuf.get_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);
+        cr.paint();
+    }
+
+    fn do_draw_status(cr: &cairo::Context, status: FileStatus, r: &gdk::Rectangle) {
+        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);
+        cr.paint();
+    }
+
+    fn do_draw_format_emblem(cr: &cairo::Context, emblem: &Pixbuf, r: &gdk::Rectangle) -> i32 {
+        let w = emblem.get_width();
+        let h = emblem.get_height();
+        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);
+        cr.paint();
+        left
+    }
+
+    fn do_draw_label(cr: &cairo::Context, right: i32, colour: RgbColour, r: &gdk::Rectangle) {
+        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();
+
+        cr.rectangle(x, y, LABEL_SIZE.into(), LABEL_SIZE.into());
+        cr.set_source_rgb(1.0, 1.0, 1.0);
+        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.fill();
+    }
+
+    fn get_colour(&self, label_id: i32) -> Option<RgbColour> {
+        if let Some(f) = *self.get_colour_callback.borrow() {
+            unsafe {
+                let mut c = RgbColour::default();
+                if f(label_id, &mut c) {
+                    return Some(c);
+                }
+            }
+        }
+        None
+    }
+}
+
+static PROPERTIES: [subclass::Property; 2] = [
+    subclass::Property("libfile", |libfile| {
+        glib::ParamSpec::boxed(
+            libfile,
+            "Library File",
+            "File from the library in the cell",
+            CellLibFile::get_type(),
+            glib::ParamFlags::READWRITE,
+        )
+    }),
+    subclass::Property("status", |status| {
+        glib::ParamSpec::int(
+            status,
+            "File Status",
+            "Status of the file in the cell",
+            FileStatus::Ok as i32,
+            FileStatus::Missing as i32,
+            FileStatus::Ok as i32,
+            glib::ParamFlags::READWRITE,
+        )
+    }),
+];
+
+impl ObjectSubclass for LibraryCellRendererPriv {
+    const NAME: &'static str = "LibraryCellRenderer";
+    type ParentType = gtk::CellRendererPixbuf;
+    type Instance = subclass::simple::InstanceStruct<Self>;
+    type Class = subclass::simple::ClassStruct<Self>;
+
+    glib_object_subclass!();
+
+    fn class_init(klass: &mut Self::Class) {
+        klass.install_properties(&PROPERTIES);
+        klass.add_signal(
+            "rating-changed",
+            glib::SignalFlags::RUN_LAST,
+            &[Type::U64, Type::I32],
+            Type::Unit,
+        );
+    }
+
+    fn new() -> Self {
+        Self {
+            libfile: RefCell::new(None),
+            status: Cell::new(FileStatus::Ok),
+            size: Cell::new(160),
+            pad: Cell::new(16),
+            drawborder: Cell::new(true),
+            draw_emblem: Cell::new(true),
+            draw_rating: Cell::new(true),
+            draw_label: Cell::new(true),
+            draw_flag: Cell::new(true),
+            draw_status: Cell::new(true),
+            clickable_cell: RefCell::new(ClickableCell::default()),
+            get_colour_callback: RefCell::new(None),
+        }
+    }
+}
+
+impl ObjectImpl for LibraryCellRendererPriv {
+    glib_object_impl!();
+
+    fn constructed(&self, obj: &glib::Object) {
+        self.parent_constructed(obj);
+    }
+
+    fn set_property(&self, _obj: &glib::Object, id: usize, value: &glib::Value) {
+        let prop = &PROPERTIES[id];
+        match *prop {
+            subclass::Property("libfile", ..) => {
+                let libfile = value
+                    .get::<&CellLibFile>()
+                    .expect("type conformity checked by `Object::set_property`")
+                    .map(|f| f.clone());
+                self.set_libfile(libfile);
+            }
+            subclass::Property("status", ..) => {
+                let status: i32 = value
+                    .get_some()
+                    .expect("type conformity checked by `Object::set_property`");
+                self.set_status(FileStatus::from(status));
+            }
+            _ => unimplemented!(),
+        }
+    }
+
+    fn get_property(&self, _obj: &glib::Object, id: usize) -> Result<glib::Value, ()> {
+        let prop = &PROPERTIES[id];
+
+        match *prop {
+            subclass::Property("libfile", ..) => Ok(self.libfile.borrow().to_value()),
+            subclass::Property("status", ..) => Ok((self.status.get() as i32).to_value()),
+            _ => unimplemented!(),
+        }
+    }
+}
+
+impl CellRendererPixbufImpl for LibraryCellRendererPriv {}
+
+impl CellRendererImpl for LibraryCellRendererPriv {
+    fn get_preferred_width<P: IsA<gtk::Widget>>(
+        &self,
+        _renderer: &gtk::CellRenderer,
+        _widget: &P,
+    ) -> (i32, i32) {
+        let maxdim: i32 = self.size.get() + self.pad.get() * 2;
+        (maxdim, maxdim)
+    }
+
+    fn get_preferred_height<P: IsA<gtk::Widget>>(
+        &self,
+        _renderer: &gtk::CellRenderer,
+        _widget: &P,
+    ) -> (i32, i32) {
+        let maxdim: i32 = self.size.get() + self.pad.get() * 2;
+        (maxdim, maxdim)
+    }
+
+    fn render<P: IsA<gtk::Widget>>(
+        &self,
+        _renderer: &gtk::CellRenderer,
+        cr: &cairo::Context,
+        widget: &P,
+        _background_area: &gdk::Rectangle,
+        cell_area: &gdk::Rectangle,
+        flags: gtk::CellRendererState,
+    ) {
+        let self_ = self.get_instance();
+        let xpad = self_.get_property_xpad();
+        let ypad = self_.get_property_ypad();
+
+        let mut r = cell_area.clone();
+        r.x += xpad as i32;
+        r.y += ypad as i32;
+
+        let file = self.libfile.borrow();
+
+        let style_context = widget.get_style_context();
+
+        style_context.save();
+        style_context.set_state(if flags.contains(gtk::CellRendererState::SELECTED) {
+            gtk::StateFlags::SELECTED
+        } else {
+            gtk::StateFlags::NORMAL
+        });
+        gtk::render_background(
+            &style_context,
+            cr,
+            (r.x).into(),
+            (r.y).into(),
+            (r.width).into(),
+            (r.height).into(),
+        );
+
+        if self.drawborder.get() {
+            gtk::render_frame(
+                &style_context,
+                cr,
+                (r.x).into(),
+                (r.y).into(),
+                (r.width).into(),
+                (r.height).into(),
+            );
+        }
+        style_context.restore();
+
+        if let Some(pixbuf) = self_.get_property_pixbuf() {
+            self.do_draw_thumbnail(cr, &pixbuf, &r);
+        }
+        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();
+            RatingLabel::draw_rating(
+                cr,
+                rating,
+                &RatingLabel::get_star(),
+                &RatingLabel::get_unstar(),
+                x,
+                y,
+            );
+        }
+        if self.draw_flag.get() {
+            match &*file {
+                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);
+        }
+
+        if self.draw_emblem.get() {
+            let file_type = match &*file {
+                Some(f) => f.0.file_type(),
+                None => FileType::UNKNOWN,
+            };
+            let emblem: Pixbuf = match file_type {
+                FileType::RAW => EMBLEMS.raw.clone(),
+                FileType::RAW_JPEG => EMBLEMS.raw_jpeg.clone(),
+                FileType::IMAGE => EMBLEMS.img.clone(),
+                FileType::VIDEO => EMBLEMS.video.clone(),
+                FileType::UNKNOWN => EMBLEMS.unknown.clone(),
+            };
+            let left = Self::do_draw_format_emblem(cr, &emblem, &r);
+
+            if self.draw_label.get() {
+                let label_id = match &*file {
+                    Some(f) => f.0.label(),
+                    None => 0,
+                };
+                if label_id != 0 {
+                    if let Some(colour) = self.get_colour(label_id) {
+                        Self::do_draw_label(cr, left, colour, &r);
+                    }
+                }
+            }
+        }
+    }
+
+    fn activate<P: IsA<gtk::Widget>>(
+        &self,
+        _renderer: &gtk::CellRenderer,
+        _event: Option<&gdk::Event>,
+        _widget: &P,
+        _path: &str,
+        _background_area: &gdk::Rectangle,
+        cell_area: &gdk::Rectangle,
+        _flags: gtk::CellRendererState,
+    ) -> bool {
+        let mut instance = self
+            .get_instance()
+            .downcast::<LibraryCellRenderer>()
+            .unwrap();
+
+        if instance.is_hit() {
+            instance.reset_hit();
+
+            // hit test with the rating region
+            let xpad = instance.get_property_xpad();
+            let ypad = instance.get_property_ypad();
+            let mut r = cell_area.clone();
+            r.x += xpad as i32;
+            r.y += ypad as i32;
+
+            let (rw, rh) = RatingLabel::get_geometry();
+            let rect = gdk::Rectangle {
+                x: r.x + CELL_PADDING,
+                y: r.y + r.height - rh - CELL_PADDING,
+                width: rw,
+                height: rh,
+            };
+            let x = instance.x();
+            let y = instance.y();
+            dbg_out!(
+                "r({}, {}, {}, {}) p({}, {})",
+                rect.x,
+                rect.y,
+                rect.width,
+                rect.height,
+                x,
+                y
+            );
+            let hit = (rect.x <= x)
+                && (rect.x + rect.width >= x)
+                && (rect.y <= y)
+                && (rect.y + rect.height >= y);
+            if !hit {
+                dbg_out!("not a hit");
+                return false;
+            }
+
+            // hit test for the rating value
+            let new_rating = RatingLabel::rating_value_from_hit_x((x - rect.x).into());
+            dbg_out!("new_rating {}", new_rating);
+
+            let file = self.libfile.borrow();
+            if let Some(f) = &*file {
+                if f.0.rating() != new_rating {
+                    // emit signal if changed
+                    if let Err(err) = instance.emit("rating-changed", &[&f.0.id(), &new_rating]) {
+                        err_out!("Can't emit rating-changed signal: {}", err);
+                    }
+                }
+            }
+            true
+        } else {
+            false
+        }
+    }
+}
+
+// allow subclassing this
+pub trait LibraryCellRendererImpl: CellRendererPixbufImpl + 'static {}
+
+unsafe impl<T: ObjectSubclass + LibraryCellRendererImpl> IsSubclassable<T>
+    for LibraryCellRendererClass
+{
+    fn override_vfuncs(&mut self) {
+        <CellRendererPixbufClass as IsSubclassable<T>>::override_vfuncs(self);
+    }
+}
+
+// XXX we must pass the get_colour callback
+#[no_mangle]
+pub unsafe extern "C" fn npc_library_cell_renderer_new(
+    get_colour: Option<unsafe extern "C" fn(i32, *mut RgbColour) -> bool>,
+) -> *mut gtk_sys::GtkCellRenderer {
+    LibraryCellRenderer::new(get_colour)
+        .upcast::<gtk::CellRenderer>()
+        .to_glib_full()
+}
diff --git a/src/niepce/ui/librarycellrenderer.cpp b/src/niepce/ui/librarycellrenderer.cpp
index 7c4aec3..105adc9 100644
--- a/src/niepce/ui/librarycellrenderer.cpp
+++ b/src/niepce/ui/librarycellrenderer.cpp
@@ -255,7 +255,7 @@ LibraryCellRenderer::render_vfunc(const Cairo::RefPtr<Cairo::Context>& cr,
         draw_flag(cr, engine_db_libfile_flag(file.get()), r);
     }
 
-    auto status = m_statusproperty.get_value();
+    auto status = static_cast<eng::FileStatus>(m_statusproperty.get_value());
     if (m_drawstatus && status != eng::FileStatus::Ok) {
         draw_status(cr, status, r);
     }
@@ -285,10 +285,11 @@ LibraryCellRenderer::render_vfunc(const Cairo::RefPtr<Cairo::Context>& cr,
         if (m_drawlabel) {
             uint32_t label_id = engine_db_libfile_label(file.get());
             if (label_id != 0) {
-                auto result = m_get_colour(label_id);
-                DBG_ASSERT(!result.empty(), "colour not found");
-                if (!result.empty()) {
-                    drawLabel(cr, left, *result.unwrap(), r);
+                ffi::RgbColour colour;
+                if (m_get_colour(label_id, &colour)) {
+                    drawLabel(cr, left, colour, r);
+                } else {
+                    DBG_ASSERT(false, "colour not found");
                 }
             }
         }
diff --git a/src/niepce/ui/librarycellrenderer.hpp b/src/niepce/ui/librarycellrenderer.hpp
index 8e83d05..091f6dd 100644
--- a/src/niepce/ui/librarycellrenderer.hpp
+++ b/src/niepce/ui/librarycellrenderer.hpp
@@ -34,7 +34,7 @@ class LibraryCellRenderer
     , public fwk::ClickableCellRenderer
 {
 public:
-    typedef std::function<fwk::Option<fwk::RgbColourPtr>(int)> GetColourFunc;
+    typedef std::function<bool (int32_t, fwk::RgbColour*)> GetColourFunc;
     LibraryCellRenderer(const GetColourFunc& get_colour);
 
     virtual void get_preferred_width_vfunc(Gtk::Widget& widget, int& minimum_width, int& natural_width) 
const override;
@@ -93,7 +93,7 @@ private:
     bool                                m_drawflag;
     bool m_drawstatus;
     Glib::Property<eng::LibFilePtr>   m_libfileproperty;
-    Glib::Property<eng::FileStatus> m_statusproperty;
+    Glib::Property<gint> m_statusproperty;
 
     Glib::RefPtr<Gdk::Pixbuf>  m_raw_format_emblem;
     Glib::RefPtr<Gdk::Pixbuf>  m_rawjpeg_format_emblem;
diff --git a/src/niepce/ui/mod.rs b/src/niepce/ui/mod.rs
index 0d3e07e..ec86fe3 100644
--- a/src/niepce/ui/mod.rs
+++ b/src/niepce/ui/mod.rs
@@ -19,4 +19,5 @@
 
 pub mod dialogs;
 pub mod imagetoolbar;
+pub mod library_cell_renderer;
 pub mod thumb_nav;
diff --git a/src/niepce/ui/thumbstripview.cpp b/src/niepce/ui/thumbstripview.cpp
index 35bea95..3129d87 100644
--- a/src/niepce/ui/thumbstripview.cpp
+++ b/src/niepce/ui/thumbstripview.cpp
@@ -85,13 +85,17 @@ ThumbStripView::ThumbStripView(const Glib::RefPtr<ui::ImageListStore> & store,
 {
     m_renderer = manage(
         new ThumbStripCell(
-            [ui_data_provider] (int label) {
+            [ui_data_provider] (int32_t label, ffi::RgbColour* out) {
                 auto provider = ui_data_provider.lock();
+                DBG_ASSERT(static_cast<bool>(provider), "couldn't lock UI provider");
                 if (provider) {
-                    return provider->colourForLabel(label);
+                    auto c = provider->colourForLabel(label);
+                    if (c.ok() && out) {
+                        *out = c.unwrap();
+                        return true;
+                    }
                 }
-                ERR_OUT("couldn't lock UI provider");
-                return fwk::Option<fwk::RgbColourPtr>();
+                return false;
             })
         );
 


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