[niepce] rust+ui: port rating label to Rust



commit f99b48117234468d11e6f654a75e86c4018d2b3c
Author: Hubert Figuière <hub figuiere net>
Date:   Tue Jan 28 23:20:19 2020 -0500

    rust+ui: port rating label to Rust
    
    - Also remove the corresponding C++ code.
    - Compile resources for pure Rust (examples)

 Cargo.lock                                         |   2 +
 build.rs                                           |  48 ++++-
 crates/npc-fwk/Cargo.toml                          |   2 +
 crates/npc-fwk/build.rs                            |   2 +
 crates/npc-fwk/src/lib.rs                          |   3 +
 crates/npc-fwk/src/toolkit/widgets/rating_label.rs | 211 ++++++++++++++++++++-
 examples/widget-test.rs                            |  30 +++
 src/fwk/toolkit/Makefile.am                        |   1 -
 src/fwk/toolkit/metadatawidget.cpp                 |  34 ++--
 src/fwk/toolkit/metadatawidget.hpp                 |   2 +
 src/fwk/toolkit/widgets/ratinglabel.cpp            | 174 -----------------
 src/fwk/toolkit/widgets/ratinglabel.hpp            |  67 -------
 12 files changed, 318 insertions(+), 258 deletions(-)
---
diff --git a/Cargo.lock b/Cargo.lock
index 7fc03a0..00f69fc 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -627,6 +627,8 @@ dependencies = [
  "gio-sys 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)",
  "glib 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "glib-sys 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "gtk 0.8.0 (git+https://github.com/hfiguiere/gtk.git?branch=0.8.0-p1)",
+ "gtk-sys 0.9.2 (registry+https://github.com/rust-lang/crates.io-index)",
  "libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)",
  "multimap 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "once_cell 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)",
diff --git a/build.rs b/build.rs
index 046f2a4..0536573 100644
--- a/build.rs
+++ b/build.rs
@@ -4,9 +4,9 @@ use std::env;
 use std::path::PathBuf;
 
 fn main() {
+    let crate_dir = env::var("CARGO_MANIFEST_DIR").unwrap();
     if env::var("SKIP_CBINDINGS").is_err() {
         // Use cbindgen to generate C bindings.
-        let crate_dir = env::var("CARGO_MANIFEST_DIR").unwrap();
         let target_dir = env::var("CARGO_TARGET_DIR").unwrap_or(String::from("./target"));
         let mut target_file = PathBuf::from(target_dir);
         target_file.push("bindings.h");
@@ -34,4 +34,50 @@ fn main() {
             .expect("Couldn't generate bindings")
             .write_to_file(&target_file);
     }
+
+    #[cfg(examples)]
+    {
+        use std::fs::remove_file;
+        use std::path::Path;
+        use std::process::Command;
+        use std::str::from_utf8;
+
+        // Remove old versions of the gresource to make sure we're using the latest version
+        if Path::new("examples/gresource.gresource").exists() {
+            remove_file("examples/gresource.gresource").unwrap();
+        }
+
+        // Compile Gresource
+        let mut source_dir = String::from("--sourcedir=");
+        source_dir.push_str(&crate_dir);
+        let mut target_dir = String::from("--target=");
+        let mut target_path = PathBuf::from(crate_dir);
+        target_path.push("examples");
+        target_path.push("gresource.gresource");
+        target_dir.push_str(target_path.to_str().unwrap());
+        let output =
+            Command::new(option_env!("GRESOURCE_BINARY_PATH").unwrap_or("glib-compile-resources"))
+                .args(&[
+                    "--generate",
+                    "gresource.xml",
+                    source_dir.as_str(),
+                    target_dir.as_str(),
+                ])
+                .current_dir("src/niepce")
+                .output()
+                .unwrap();
+
+        if !output.status.success() {
+            println!("Failed to generate GResources!");
+            println!(
+                "glib-compile-resources stdout: {}",
+                from_utf8(&output.stdout).unwrap()
+            );
+            println!(
+                "glib-compile-resources stderr: {}",
+                from_utf8(&output.stderr).unwrap()
+            );
+            panic!("Can't continue build without GResources!");
+        }
+    }
 }
diff --git a/crates/npc-fwk/Cargo.toml b/crates/npc-fwk/Cargo.toml
index d9a296a..33df4f6 100644
--- a/crates/npc-fwk/Cargo.toml
+++ b/crates/npc-fwk/Cargo.toml
@@ -15,8 +15,10 @@ gio-sys = "*"
 gio = "^0.8.0"
 glib-sys = "*"
 glib = { version = "^0.9.0" }
+gtk-sys = "*"
 gdk = "^0.12.0"
 gdk-pixbuf = "0.8.0"
+gtk = { version = "^0.8.0", git = "https://github.com/hfiguiere/gtk.git";, branch = "0.8.0-p1" }
 libc = "0.2.39"
 multimap = "0.4.0"
 once_cell = "^0"
diff --git a/crates/npc-fwk/build.rs b/crates/npc-fwk/build.rs
index 0126961..1850b30 100644
--- a/crates/npc-fwk/build.rs
+++ b/crates/npc-fwk/build.rs
@@ -16,6 +16,8 @@ fn main() {
             .with_language(cbindgen::Language::Cxx)
             .with_parse_deps(true)
             .with_parse_exclude(&["exempi", "chrono", "multimap"])
+            .exclude_item("GtkDrawingArea")
+            .exclude_item("GtkWidget")
             .exclude_item("GtkWindow")
             .exclude_item("GtkToolbar")
             .exclude_item("GFileInfo")
diff --git a/crates/npc-fwk/src/lib.rs b/crates/npc-fwk/src/lib.rs
index b474b88..1c2899d 100644
--- a/crates/npc-fwk/src/lib.rs
+++ b/crates/npc-fwk/src/lib.rs
@@ -23,8 +23,11 @@ extern crate gdk;
 extern crate gdk_pixbuf;
 extern crate gio;
 extern crate gio_sys;
+#[macro_use]
 extern crate glib;
 extern crate glib_sys;
+extern crate gtk;
+extern crate gtk_sys;
 extern crate libc;
 extern crate multimap;
 extern crate once_cell;
diff --git a/crates/npc-fwk/src/toolkit/widgets/rating_label.rs 
b/crates/npc-fwk/src/toolkit/widgets/rating_label.rs
index 937dd21..d50d67b 100644
--- a/crates/npc-fwk/src/toolkit/widgets/rating_label.rs
+++ b/crates/npc-fwk/src/toolkit/widgets/rating_label.rs
@@ -17,9 +17,21 @@
  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
  */
 
+use libc::c_int;
+use std::cell::Cell;
+
 use cairo;
 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_sys;
+
 use once_cell::unsync::Lazy;
 
 struct Pixbufs {
@@ -32,7 +44,125 @@ const PIXBUFS: Lazy<Pixbufs> = Lazy::new(|| Pixbufs {
     unstar: Pixbuf::new_from_resource("/org/gnome/Niepce/pixmaps/niepce-unset-star.png").unwrap(),
 });
 
-pub struct RatingLabel {}
+glib_wrapper! {
+    pub struct RatingLabel(
+        Object<subclass::simple::InstanceStruct<RatingLabelPriv>,
+        subclass::simple::ClassStruct<RatingLabelPriv>, RatingLabelClass>)
+        @extends gtk::DrawingArea, gtk::Widget;
+
+    match fn {
+        get_type => || RatingLabelPriv::get_type().to_glib(),
+    }
+}
+
+pub struct RatingLabelPriv {
+    editable: Cell<bool>,
+    rating: Cell<i32>,
+}
+
+impl RatingLabelPriv {
+    fn set_editable(&self, editable: bool) {
+        self.editable.set(editable);
+    }
+
+    fn set_rating(&self, rating: i32) {
+        self.rating.set(rating);
+        let w = self.get_instance();
+        w.queue_draw();
+    }
+}
+
+static PROPERTIES: [subclass::Property; 1] = [subclass::Property("rating", |rating| {
+    glib::ParamSpec::int(
+        rating,
+        "Rating",
+        "The rating value",
+        0,
+        5,
+        0,
+        glib::ParamFlags::READWRITE,
+    )
+})];
+
+impl ObjectSubclass for RatingLabelPriv {
+    const NAME: &'static str = "RatingLabel";
+    type ParentType = gtk::DrawingArea;
+    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::I32],
+            Type::Unit,
+        );
+    }
+
+    fn new() -> Self {
+        Self {
+            editable: Cell::new(true),
+            rating: Cell::new(0),
+        }
+    }
+}
+
+impl ObjectImpl for RatingLabelPriv {
+    glib_object_impl!();
+
+    fn constructed(&self, obj: &glib::Object) {
+        self.parent_constructed(obj);
+
+        let widget = self.get_instance();
+        widget.connect_realize(|w| {
+            let priv_ = RatingLabelPriv::from_instance(w);
+            if priv_.editable.get() {
+                if let Some(win) = w.get_window() {
+                    let mut mask = win.get_events();
+                    mask |= gdk::EventMask::BUTTON_PRESS_MASK;
+                    win.set_events(mask);
+                }
+            }
+        });
+    }
+
+    fn set_property(&self, _obj: &glib::Object, id: usize, value: &glib::Value) {
+        let prop = &PROPERTIES[id];
+
+        match *prop {
+            subclass::Property("rating", ..) => {
+                let rating = value
+                    .get_some()
+                    .expect("type conformity checked by `Object::set_property`");
+                self.set_rating(rating);
+            }
+            _ => unimplemented!(),
+        }
+    }
+
+    fn get_property(&self, _obj: &glib::Object, id: usize) -> Result<glib::Value, ()> {
+        let prop = &PROPERTIES[id];
+
+        match *prop {
+            subclass::Property("rating", ..) => Ok(self.rating.get().to_value()),
+            _ => unimplemented!(),
+        }
+    }
+}
+
+pub trait RatingLabelExt {
+    fn set_rating(&self, rating: i32);
+}
+
+impl RatingLabelExt for RatingLabel {
+    fn set_rating(&self, rating: i32) {
+        let priv_ = RatingLabelPriv::from_instance(self);
+        priv_.set_rating(rating);
+    }
+}
 
 impl RatingLabel {
     pub fn get_star() -> Pixbuf {
@@ -78,4 +208,83 @@ impl RatingLabel {
         let width: f64 = Self::get_star().get_width().into();
         (x / width).round() as i32
     }
+
+    pub fn new(rating: i32, editable: bool) -> Self {
+        let obj: Self = glib::Object::new(Self::static_type(), &[])
+            .expect("Failed to create RatingLabel")
+            .downcast()
+            .expect("Created RatingLabel is of the wrong type");
+
+        let priv_ = RatingLabelPriv::from_instance(&obj);
+        priv_.set_editable(editable);
+        priv_.set_rating(rating);
+        obj
+    }
+}
+
+impl DrawingAreaImpl for RatingLabelPriv {}
+impl WidgetImpl for RatingLabelPriv {
+    fn button_press_event(&self, _widget: &gtk::Widget, event: &gdk::EventButton) -> Inhibit {
+        if event.get_button() != 1 {
+            Inhibit(false)
+        } else {
+            if let Some((x, _)) = event.get_coords() {
+                let new_rating = RatingLabel::rating_value_from_hit_x(x);
+                if new_rating != self.rating.get() {
+                    self.set_rating(new_rating);
+                    if let Err(err) = self.get_instance().emit("rating-changed", &[&new_rating]) {
+                        err_out!("Emit signal 'rating-changed' failed: {}", err);
+                    }
+                }
+            }
+            Inhibit(true)
+        }
+    }
+
+    fn draw(&self, _widget: &gtk::Widget, cr: &cairo::Context) -> Inhibit {
+        let star = RatingLabel::get_star();
+        let x = 0_f64;
+        let y = star.get_height() as f64;
+        RatingLabel::draw_rating(
+            cr,
+            self.rating.get(),
+            &star,
+            &RatingLabel::get_unstar(),
+            x,
+            y,
+        );
+
+        Inhibit(true)
+    }
+
+    fn get_preferred_width(&self, _widget: &gtk::Widget) -> (i32, i32) {
+        let w = RatingLabel::get_star().get_width() * 5;
+        (w, w)
+    }
+
+    fn get_preferred_height(&self, _widget: &gtk::Widget) -> (i32, i32) {
+        let h = RatingLabel::get_star().get_height();
+        (h, h)
+    }
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn fwk_rating_label_new(
+    rating: c_int,
+    editable: bool,
+) -> *mut gtk_sys::GtkWidget {
+    RatingLabel::new(rating, editable)
+        .upcast::<gtk::Widget>()
+        .to_glib_full()
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn fwk_rating_label_set_rating(
+    widget: *mut gtk_sys::GtkDrawingArea,
+    rating: i32,
+) {
+    let rating_label = gtk::DrawingArea::from_glib_borrow(widget)
+        .downcast::<RatingLabel>()
+        .expect("Not a RatingLabel widget");
+    rating_label.set_rating(rating);
 }
diff --git a/examples/widget-test.rs b/examples/widget-test.rs
index 289dfc6..1f17097 100644
--- a/examples/widget-test.rs
+++ b/examples/widget-test.rs
@@ -18,12 +18,35 @@
  */
 
 extern crate gdk_pixbuf;
+extern crate gio;
+extern crate glib;
 extern crate gtk;
 extern crate niepce_rust;
+extern crate npc_fwk;
 
+use gio::{resources_register, Resource};
+use glib::{Bytes, Error};
 use gtk::prelude::*;
+
 use niepce_rust::niepce::ui::thumb_nav::{ThumbNav, ThumbNavMode};
 use niepce_rust::niepce::ui::thumb_strip_view::ThumbStripView;
+use npc_fwk::toolkit::widgets::rating_label::RatingLabel;
+
+fn init() -> Result<(), Error> {
+    // load the gresource binary at build time and include/link it into the final
+    // binary.
+    let res_bytes = include_bytes!("gresource.gresource");
+
+    // Create Resource it will live as long the value lives.
+    let gbytes = Bytes::from_static(res_bytes.as_ref());
+    let resource = Resource::new_from_data(&gbytes)?;
+
+    // Register the resource so it won't be dropped and will continue to live in
+    // memory.
+    resources_register(&resource);
+
+    Ok(())
+}
 
 pub fn main() {
     if let Err(err) = gtk::init() {
@@ -31,6 +54,11 @@ pub fn main() {
         panic!();
     }
 
+    if let Err(err) = init() {
+        println!("main: init failed: {}", err);
+        panic!();
+    }
+
     let model = gtk::ListStore::new(&[gdk_pixbuf::Pixbuf::static_type()]);
     let thumbview = ThumbStripView::new(&model.upcast::<gtk::TreeModel>());
     let thn = ThumbNav::new(
@@ -41,6 +69,8 @@ pub fn main() {
     thn.set_size_request(-1, 134);
 
     let box_ = gtk::Box::new(gtk::Orientation::Vertical, 0);
+    let rating = RatingLabel::new(3, true);
+    box_.pack_start(&rating, false, false, 0);
     box_.pack_start(&thn, false, false, 0);
 
     let window = gtk::Window::new(gtk::WindowType::Toplevel);
diff --git a/src/fwk/toolkit/Makefile.am b/src/fwk/toolkit/Makefile.am
index c67834a..65f03d5 100644
--- a/src/fwk/toolkit/Makefile.am
+++ b/src/fwk/toolkit/Makefile.am
@@ -49,7 +49,6 @@ libniepceframework_a_SOURCES = configuration.hpp configuration.cpp \
        gtkutils.hpp gtkutils.cpp \
        gphoto.hpp gphoto.cpp \
        widgets/addinstreemodel.hpp widgets/addinstreemodel.cpp \
-       widgets/ratinglabel.hpp widgets/ratinglabel.cpp \
        widgets/toolboxitemwidget.hpp widgets/toolboxitemwidget.cpp \
        widgets/editablehscale.hpp widgets/editablehscale.cpp \
        widgets/dock.cpp widgets/dock.hpp \
diff --git a/src/fwk/toolkit/metadatawidget.cpp b/src/fwk/toolkit/metadatawidget.cpp
index 81383a1..c24eb12 100644
--- a/src/fwk/toolkit/metadatawidget.cpp
+++ b/src/fwk/toolkit/metadatawidget.cpp
@@ -1,7 +1,7 @@
 /*
  * niepce - fwk/toolkit/metadatawidget.cpp
  *
- * Copyright (C) 2008-2019 Hubert Figuiere
+ * Copyright (C) 2008-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
@@ -28,12 +28,12 @@
 #include <gtkmm/label.h>
 #include <gtkmm/entry.h>
 #include <gtkmm/textview.h>
+#include <gtkmm/drawingarea.h>
 
 #include "fwk/base/debug.hpp"
 #include "fwk/base/autoflag.hpp"
 #include "fwk/utils/exempi.hpp"
 #include "fwk/utils/stringutils.hpp"
-#include "fwk/toolkit/widgets/ratinglabel.hpp"
 #include "fwk/toolkit/widgets/notabtextview.hpp"
 #include "fwk/toolkit/widgets/tokentextview.hpp"
 
@@ -63,16 +63,22 @@ void MetaDataWidget::set_data_format(const MetaDataSectionFormat * fmt)
     create_widgets_for_format(fmt);
 }
 
-Gtk::Widget* 
+void MetaDataWidget::rating_callback(GtkWidget* w, gint rating, gpointer user_data)
+{
+    auto self = static_cast<MetaDataWidget*>(user_data);
+    auto id = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(w), "id"));
+    self->on_int_changed(rating, id);
+}
+
+Gtk::Widget*
 MetaDataWidget::create_star_rating_widget(bool readonly, uint32_t id)
 {
-    fwk::RatingLabel* r = Gtk::manage(new fwk::RatingLabel(0, !readonly));
-    if(!readonly) {
-        r->signal_changed.connect(
-            sigc::bind(
-                sigc::mem_fun(*this, 
-                              &MetaDataWidget::on_int_changed), 
-                id));
+    Gtk::DrawingArea* r =
+        Gtk::manage(Glib::wrap(
+                        GTK_DRAWING_AREA(ffi::fwk_rating_label_new(0, !readonly))));
+    if (!readonly) {
+        r->set_data("id", GINT_TO_POINTER(id));
+        g_signal_connect(r->gobj(), "rating-changed", G_CALLBACK(rating_callback), this);
     }
     return r;
 }
@@ -218,9 +224,9 @@ void MetaDataWidget::clear_widget(const std::pair<const PropertyIndex, Gtk::Widg
         tv->get_buffer()->set_text("");
         return;
     }
-    fwk::RatingLabel * rl = dynamic_cast<fwk::RatingLabel*>(p.second);
-    if(rl) {
-        rl->set_rating(0);
+    Gtk::DrawingArea* rl = dynamic_cast<Gtk::DrawingArea*>(p.second);
+    if (rl) {
+        ffi::fwk_rating_label_set_rating(rl->gobj(), 0);
         return;
     }
 }
@@ -314,7 +320,7 @@ bool MetaDataWidget::set_star_rating_data(Gtk::Widget* w,
     try {
         int rating = fwk_property_value_get_integer(value.get());
         AutoFlag flag(m_update);
-        static_cast<fwk::RatingLabel*>(w)->set_rating(rating);
+        ffi::fwk_rating_label_set_rating(static_cast<Gtk::DrawingArea*>(w)->gobj(), rating);
     }
     catch(...) {
         return false;
diff --git a/src/fwk/toolkit/metadatawidget.hpp b/src/fwk/toolkit/metadatawidget.hpp
index 24459bd..9222492 100644
--- a/src/fwk/toolkit/metadatawidget.hpp
+++ b/src/fwk/toolkit/metadatawidget.hpp
@@ -112,6 +112,8 @@ private:
     fwk::PropertyBagPtr m_current_data;
     const MetaDataSectionFormat * m_fmt;
     bool m_update;
+
+    static void rating_callback(GtkWidget* w, gint rating, gpointer user_data);
 };
 
 }


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