[niepce] rust+ui: re-implement Thumb navigator in Rust



commit 4ca20bdd33e7604f1ca173288cfdbf7503b060f1
Author: Hubert Figuière <hub figuiere net>
Date:   Sat Jan 25 22:27:39 2020 -0500

    rust+ui: re-implement Thumb navigator in Rust
    
    - rename ThumbNav
    - added a widget-test example

 Cargo.lock                                 |   2 +
 Cargo.toml                                 |   7 +-
 build.rs                                   |   2 +
 examples/widget-test.rs                    |  48 +++
 src/Makefile.am                            |   2 +
 src/lib.rs                                 |   5 +-
 src/niepce/ui/Makefile.am                  |   1 -
 src/niepce/ui/filmstripcontroller.cpp      |   7 +-
 src/niepce/ui/mod.rs                       |  20 ++
 src/niepce/ui/thumb_nav.rs                 | 487 +++++++++++++++++++++++++++++
 src/niepce/ui/thumb_view/eog-thumb-nav.cpp | 487 -----------------------------
 src/niepce/ui/thumb_view/eog-thumb-nav.hpp |  79 -----
 12 files changed, 574 insertions(+), 573 deletions(-)
---
diff --git a/Cargo.lock b/Cargo.lock
index 47ce926..8bdeb08 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -575,12 +575,14 @@ dependencies = [
  "cbindgen 0.8.4 (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)",
  "glib 0.9.0 (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)",
  "npc-engine 0.1.0",
  "npc-fwk 0.1.0",
+ "once_cell 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
diff --git a/Cargo.toml b/Cargo.toml
index 0bfd43e..251e8ce 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -5,8 +5,10 @@ authors = ["Hubert Figuière <hub figuiere net>"]
 build = "build.rs"
 
 [dependencies]
+once_cell = "^0"
 gettext-rs = "0.3.0"
 glib = { version = "^0.9.0" }
+gio-sys = "*"
 gio = "^0.8.0"
 gtk-sys = { version = "*", features = ["v3_22"] }
 gtk = { version = "^0.8.0", git = "https://github.com/hfiguiere/gtk.git";, branch = "0.8.0-p1" }
@@ -21,4 +23,7 @@ cbindgen = { version = "=0.8.4" }
 
 [lib]
 name = "niepce_rust"
-crate-type = ["staticlib"]
+crate-type = ["staticlib", "lib"]
+
+[[example]]
+name = "widget-test"
\ No newline at end of file
diff --git a/build.rs b/build.rs
index 32c3d8c..65cf8b0 100644
--- a/build.rs
+++ b/build.rs
@@ -18,6 +18,8 @@ fn main() {
             .with_parse_exclude(&["exempi", "chrono", "multimap"])
             .exclude_item("GtkWindow")
             .exclude_item("GtkToolbar")
+            .exclude_item("GtkIconView")
+            .exclude_item("GtkWidget")
             .exclude_item("GFileInfo")
             .exclude_item("RgbColour")
             .with_crate(&crate_dir)
diff --git a/examples/widget-test.rs b/examples/widget-test.rs
new file mode 100644
index 0000000..86aba4f
--- /dev/null
+++ b/examples/widget-test.rs
@@ -0,0 +1,48 @@
+/*
+ * niepce - examples/widget-test.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/>.
+ */
+
+extern crate gtk;
+extern crate niepce_rust;
+
+use gtk::prelude::*;
+use niepce_rust::niepce::ui::thumb_nav::{ThumbNav, ThumbNavMode};
+
+pub fn main() {
+    if let Err(err) = gtk::init() {
+        println!("main: gtk::init failed: {}", err);
+        panic!();
+    }
+
+    let thumbview = gtk::IconView::new();
+    let thn = ThumbNav::new(&thumbview, ThumbNavMode::OneRow, true);
+    thn.set_size_request(-1, 134);
+
+    let box_ = gtk::Box::new(gtk::Orientation::Vertical, 0);
+    box_.pack_start(&thn, false, false, 0);
+
+    let window = gtk::Window::new(gtk::WindowType::Toplevel);
+    window.add(&box_);
+    window.show_all();
+    window.connect_delete_event(|_, _| {
+        gtk::main_quit();
+        gtk::Inhibit(false)
+    });
+
+    gtk::main();
+}
diff --git a/src/Makefile.am b/src/Makefile.am
index 26b3880..7c04234 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -44,6 +44,7 @@ RUST_SOURCES = \
        @top_srcdir@/crates/npc-fwk/src/utils/exiv2.rs \
        @top_srcdir@/crates/npc-fwk/src/utils/files.rs \
        @top_srcdir@/crates/npc-fwk/src/utils/mod.rs \
+       @top_srcdir@/examples/widget-test.rs \
        @top_srcdir@/src/lib.rs \
        @top_srcdir@/src/libraryclient/clientimpl.rs \
        @top_srcdir@/src/libraryclient/clientinterface.rs \
@@ -54,6 +55,7 @@ RUST_SOURCES = \
        @top_srcdir@/src/niepce/ui/dialogs/mod.rs \
        @top_srcdir@/src/niepce/ui/dialogs/confirm.rs \
        @top_srcdir@/src/niepce/ui/dialogs/requestnewfolder.rs \
+       @top_srcdir@/src/niepce/ui/thumb_nav.rs \
        $(NULL)
 
 EXTRA_DIST = \
diff --git a/src/lib.rs b/src/lib.rs
index 55a5c68..2d38e64 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -1,7 +1,7 @@
 /*
  * niepce - lib.rs
  *
- * Copyright (C) 2017-2019 Hubert Figuière
+ * Copyright (C) 2017-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
@@ -19,10 +19,13 @@
 
 extern crate gettextrs;
 extern crate gio;
+extern crate gio_sys;
+#[macro_use]
 extern crate glib;
 extern crate gtk;
 extern crate gtk_sys;
 extern crate libc;
+extern crate once_cell;
 
 // internal crates
 #[macro_use]
diff --git a/src/niepce/ui/Makefile.am b/src/niepce/ui/Makefile.am
index e4f8361..52063a5 100644
--- a/src/niepce/ui/Makefile.am
+++ b/src/niepce/ui/Makefile.am
@@ -41,7 +41,6 @@ libniepceui_a_SOURCES = \
        dialogs/importers/cameraimporterui.cpp \
        selectioncontroller.hpp selectioncontroller.cpp \
        filmstripcontroller.hpp filmstripcontroller.cpp \
-       thumb_view/eog-thumb-nav.cpp thumb_view/eog-thumb-nav.hpp \
        thumbstripview.cpp thumbstripview.hpp \
        $(PUBLICHEADERS) \
        $(NULL)
diff --git a/src/niepce/ui/filmstripcontroller.cpp b/src/niepce/ui/filmstripcontroller.cpp
index 6bca0c2..116883e 100644
--- a/src/niepce/ui/filmstripcontroller.cpp
+++ b/src/niepce/ui/filmstripcontroller.cpp
@@ -1,7 +1,7 @@
 /*
  * niepce - niepce/ui/filmstripcontroller.cpp
  *
- * Copyright (C) 2008-2020 Hubert 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
@@ -24,7 +24,6 @@
 #include "engine/library/thumbnailnotification.hpp"
 #include "fwk/base/debug.hpp"
 
-#include "thumb_view/eog-thumb-nav.hpp"
 #include "thumbstripview.hpp"
 #include "filmstripcontroller.hpp"
 
@@ -44,8 +43,8 @@ Gtk::Widget * FilmStripController::buildWidget()
     }
     DBG_ASSERT(static_cast<bool>(m_store), "m_store NULL");
     m_thumbview = manage(new ThumbStripView(m_store, m_ui_data_provider));
-    GtkWidget *thn = eog_thumb_nav_new(m_thumbview,
-                                       EogThumbNavMode::ONE_ROW, true);
+    GtkWidget *thn = ffi::npc_thumb_nav_new(m_thumbview->gobj(),
+                                            ffi::ThumbNavMode::OneRow, true);
     m_thumbview->set_selection_mode(Gtk::SELECTION_SINGLE);
     m_widget = Glib::wrap(thn);
     m_widget->set_size_request(-1, 134);
diff --git a/src/niepce/ui/mod.rs b/src/niepce/ui/mod.rs
index fdbc266..0d3e07e 100644
--- a/src/niepce/ui/mod.rs
+++ b/src/niepce/ui/mod.rs
@@ -1,2 +1,22 @@
+/*
+ * niepce - niepce/ui/mod.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/>.
+ */
+
 pub mod dialogs;
 pub mod imagetoolbar;
+pub mod thumb_nav;
diff --git a/src/niepce/ui/thumb_nav.rs b/src/niepce/ui/thumb_nav.rs
new file mode 100644
index 0000000..52714ed
--- /dev/null
+++ b/src/niepce/ui/thumb_nav.rs
@@ -0,0 +1,487 @@
+/*
+ * niepce - niepce/ui/thumb_nav.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 std::cell::{Cell, RefCell};
+use std::rc::Rc;
+
+use once_cell::unsync::OnceCell;
+
+use glib::subclass;
+use glib::subclass::prelude::*;
+use glib::translate::*;
+use gtk;
+use gtk::prelude::*;
+use gtk::subclass::prelude::*;
+
+const SCROLL_INC: f64 = 1.;
+const SCROLL_MOVE: f64 = 20.;
+const SCROLL_TIMEOUT: u32 = 20;
+
+#[repr(i32)]
+#[derive(Clone, Copy, PartialEq)]
+pub enum ThumbNavMode {
+    OneRow,
+    OneColumn,
+    MultipleRows,
+    MultipleColumns,
+    Invalid,
+}
+
+impl Into<i32> for ThumbNavMode {
+    fn into(self) -> i32 {
+        match self {
+            ThumbNavMode::OneRow => 0,
+            ThumbNavMode::OneColumn => 1,
+            ThumbNavMode::MultipleRows => 2,
+            ThumbNavMode::MultipleColumns => 3,
+            ThumbNavMode::Invalid => 4,
+        }
+    }
+}
+
+impl From<i32> for ThumbNavMode {
+    fn from(value: i32) -> Self {
+        match value {
+            0 => ThumbNavMode::OneRow,
+            1 => ThumbNavMode::OneColumn,
+            2 => ThumbNavMode::MultipleRows,
+            3 => ThumbNavMode::MultipleColumns,
+            _ => ThumbNavMode::Invalid,
+        }
+    }
+}
+
+glib_wrapper! {
+    pub struct ThumbNav(
+        Object<subclass::simple::InstanceStruct<ThumbNavPriv>,
+        subclass::simple::ClassStruct<ThumbNavPriv>,
+        ThumbNavClass>)
+        @extends gtk::Box, gtk::Container, gtk::Widget;
+
+    match fn {
+        get_type => || ThumbNavPriv::get_type().to_glib(),
+    }
+}
+
+impl ThumbNav {
+    pub fn new(thumbview: &gtk::IconView, mode: ThumbNavMode, show_buttons: bool) -> Self {
+        let mode_n: i32 = mode.into();
+        glib::Object::new(
+            Self::static_type(),
+            &[
+                ("mode", &mode_n),
+                ("show-buttons", &show_buttons),
+                ("thumbview", thumbview),
+                ("homogeneous", &false),
+                ("spacing", &0),
+            ],
+        )
+        .expect("Failed to create Thumbnail Navigator")
+        .downcast()
+        .expect("Created Thumbnail Navigator is of wrong type")
+    }
+}
+
+struct ThumbNavWidgets {
+    button_left: gtk::Button,
+    button_right: gtk::Button,
+    sw: gtk::ScrolledWindow,
+}
+
+pub struct ThumbNavPriv {
+    mode: Cell<ThumbNavMode>,
+    show_buttons: Cell<bool>,
+
+    left_i: Cell<f64>,
+    right_i: Cell<f64>,
+    widgets: OnceCell<ThumbNavWidgets>,
+    thumbview: RefCell<Option<gtk::IconView>>,
+}
+
+pub trait ThumbNavExt {
+    /// Get whether we show the left and right scroll buttons.
+    fn get_show_buttons(&self) -> bool;
+    /// Set whether we show the left and right scroll buttons.
+    fn set_show_buttons(&self, show_buttons: bool);
+    /// Get the navigation mode.
+    fn get_mode(&self) -> ThumbNavMode;
+    /// Set the navigation mode.
+    fn set_mode(&self, mode: ThumbNavMode);
+}
+
+impl ThumbNavExt for ThumbNav {
+    fn get_show_buttons(&self) -> bool {
+        let priv_ = ThumbNavPriv::from_instance(self);
+        priv_.show_buttons.get()
+    }
+
+    fn set_show_buttons(&self, show_buttons: bool) {
+        let priv_ = ThumbNavPriv::from_instance(self);
+        priv_.set_show_buttons(show_buttons);
+    }
+
+    fn get_mode(&self) -> ThumbNavMode {
+        let priv_ = ThumbNavPriv::from_instance(self);
+        priv_.mode.get()
+    }
+
+    fn set_mode(&self, mode: ThumbNavMode) {
+        let priv_ = ThumbNavPriv::from_instance(self);
+        priv_.set_mode(mode);
+    }
+}
+
+impl ThumbNavPriv {
+    fn left_button_clicked(&self) {
+        if let Some(adj) = self.widgets.get().unwrap().sw.get_hadjustment() {
+            let adj = Rc::new(adj);
+            let i = self.left_i.clone();
+            gtk::timeout_add(SCROLL_TIMEOUT, move || ThumbNavPriv::scroll_left(&i, &adj));
+        }
+    }
+
+    fn right_button_clicked(&self) {
+        if let Some(adj) = self.widgets.get().unwrap().sw.get_hadjustment() {
+            let adj = Rc::new(adj);
+            let i = self.right_i.clone();
+            gtk::timeout_add(SCROLL_TIMEOUT, move || ThumbNavPriv::scroll_right(&i, &adj));
+        }
+    }
+
+    fn adj_changed(&self, adj: &gtk::Adjustment) {
+        if let Some(widgets) = self.widgets.get() {
+            let upper = adj.get_upper();
+            let page_size = adj.get_page_size();
+            widgets.button_right.set_sensitive(upper > page_size)
+        }
+    }
+
+    fn adj_value_changed(&self, adj: &gtk::Adjustment) {
+        let upper = adj.get_upper();
+        let page_size = adj.get_page_size();
+        let value = adj.get_value();
+
+        if let Some(w) = self.widgets.get() {
+            w.button_left.set_sensitive(value > 0.0);
+            w.button_right.set_sensitive(value < upper - page_size);
+        }
+    }
+
+    fn scroll_left(ref_i: &Cell<f64>, adj: &gtk::Adjustment) -> glib::Continue {
+        let value = adj.get_value();
+        let i = ref_i.get();
+
+        if i == SCROLL_MOVE || value - SCROLL_INC < 0.0 {
+            ref_i.set(0.0);
+            if let Err(err) = adj.emit("value-changed", &[]) {
+                err_out!("signal emit value-changed {}", err);
+            }
+            return Continue(false);
+        }
+
+        ref_i.set(i + 1.0);
+
+        let move_ = f64::min(SCROLL_MOVE, value);
+        adj.set_value(value - move_);
+
+        Continue(true)
+    }
+
+    fn scroll_right(ref_i: &Cell<f64>, adj: &gtk::Adjustment) -> glib::Continue {
+        let upper = adj.get_upper();
+        let page_size = adj.get_page_size();
+        let value = adj.get_value();
+        let i = ref_i.get();
+
+        if i == SCROLL_MOVE || value + SCROLL_INC > upper - page_size {
+            ref_i.set(0.0);
+            return Continue(false);
+        }
+
+        ref_i.set(i + 1.0);
+
+        let move_ = f64::min(SCROLL_MOVE, upper - page_size - value);
+        adj.set_value(value + move_);
+
+        Continue(true)
+    }
+
+    fn set_show_buttons(&self, show_buttons: bool) {
+        self.show_buttons.set(show_buttons);
+
+        let widgets = &self.widgets.get().unwrap();
+        if show_buttons && self.mode.get() == ThumbNavMode::OneRow {
+            widgets.button_left.show_all();
+            widgets.button_right.show_all();
+        } else {
+            widgets.button_left.hide();
+            widgets.button_right.hide();
+        }
+    }
+
+    fn set_mode(&self, mode: ThumbNavMode) {
+        self.mode.set(mode);
+
+        match mode {
+            ThumbNavMode::OneRow => {
+                if let Some(thumbview) = &*self.thumbview.borrow() {
+                    thumbview.set_size_request(-1, -1);
+                    if let Err(err) = thumbview.set_property("item-height", &100) {
+                        err_out!(
+                            "ThumbNav::set_mode: set property 'item-height' failed: {}",
+                            err
+                        );
+                    }
+                }
+                self.widgets
+                    .get()
+                    .unwrap()
+                    .sw
+                    .set_policy(gtk::PolicyType::Always, gtk::PolicyType::Never);
+
+                self.set_show_buttons(self.show_buttons.get());
+            }
+            ThumbNavMode::OneColumn
+            | ThumbNavMode::MultipleRows
+            | ThumbNavMode::MultipleColumns => {
+                if let Some(thumbview) = &*self.thumbview.borrow() {
+                    thumbview.set_columns(1);
+
+                    thumbview.set_size_request(-1, -1);
+                    if let Err(err) = thumbview.set_property("item-height", &-1) {
+                        err_out!(
+                            "ThumbNav::set_mode: set property 'item-height' failed: {}",
+                            err
+                        );
+                    }
+                }
+                if let Some(widgets) = self.widgets.get() {
+                    widgets
+                        .sw
+                        .set_policy(gtk::PolicyType::Never, gtk::PolicyType::Always);
+
+                    widgets.button_left.hide();
+                    widgets.button_right.hide();
+                }
+            }
+            _ => {}
+        }
+    }
+
+    fn add_thumbview(&self) {
+        if let Some(ref thumbview) = &*self.thumbview.borrow() {
+            if let Some(widgets) = self.widgets.get() {
+                widgets.sw.add(thumbview);
+                widgets.sw.show_all();
+            }
+        } else {
+            err_out!("No thumbview to add");
+        }
+    }
+}
+
+static PROPERTIES: [subclass::Property; 3] = [
+    subclass::Property("show-buttons", |show_buttons| {
+        glib::ParamSpec::boolean(
+            show_buttons,
+            "Show Buttons",
+            "Whether to show navigation buttons or not",
+            true, // Default value
+            glib::ParamFlags::READWRITE,
+        )
+    }),
+    subclass::Property("thumbview", |thumbview| {
+        glib::ParamSpec::object(
+            thumbview,
+            "Thumbnail View",
+            "The internal thumbnail viewer widget",
+            gtk::IconView::static_type(),
+            glib::ParamFlags::READWRITE | glib::ParamFlags::CONSTRUCT_ONLY,
+        )
+    }),
+    subclass::Property("mode", |mode| {
+        glib::ParamSpec::int(
+            mode,
+            "Mode",
+            "Thumb navigator mode",
+            ThumbNavMode::OneRow.into(),
+            ThumbNavMode::MultipleRows.into(),
+            ThumbNavMode::OneRow.into(),
+            glib::ParamFlags::READWRITE,
+        )
+    }),
+];
+
+impl ObjectSubclass for ThumbNavPriv {
+    const NAME: &'static str = "NpcThumbNav";
+    type ParentType = gtk::Box;
+    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);
+    }
+
+    fn new() -> Self {
+        Self {
+            mode: Cell::new(ThumbNavMode::OneRow),
+            show_buttons: Cell::new(true),
+            left_i: Cell::new(0.0),
+            right_i: Cell::new(0.0),
+            widgets: OnceCell::new(),
+            thumbview: RefCell::new(None),
+        }
+    }
+}
+
+impl ObjectImpl for ThumbNavPriv {
+    glib_object_impl!();
+
+    fn constructed(&self, obj: &glib::Object) {
+        self.parent_constructed(obj);
+
+        let self_ = obj.downcast_ref::<ThumbNav>().unwrap();
+
+        let button_left = gtk::Button::new();
+        button_left.set_relief(gtk::ReliefStyle::None);
+        let arrow =
+            gtk::Image::new_from_icon_name(Some(&"pan-start-symbolic"), gtk::IconSize::Button);
+        button_left.add(&arrow);
+        button_left.set_size_request(20, 0);
+        self_.pack_start(&button_left, false, false, 0);
+        button_left.connect_clicked(clone!(@weak self_ => move |_| {
+            let priv_ = ThumbNavPriv::from_instance(&self_);
+            priv_.left_button_clicked();
+        }));
+
+        let sw = gtk::ScrolledWindow::new(gtk::NONE_ADJUSTMENT, gtk::NONE_ADJUSTMENT);
+        sw.set_shadow_type(gtk::ShadowType::In);
+        sw.set_policy(gtk::PolicyType::Always, gtk::PolicyType::Never);
+        if let Some(adj) = sw.get_hadjustment() {
+            adj.connect_changed(clone!(@weak self_ => move |adj| {
+                let priv_ = ThumbNavPriv::from_instance(&self_);
+                priv_.adj_changed(adj);
+            }));
+            adj.connect_value_changed(clone!(@weak self_ => move |adj| {
+                let priv_ = ThumbNavPriv::from_instance(&self_);
+                priv_.adj_value_changed(adj);
+            }));
+        }
+        self_.pack_start(&sw, true, true, 0);
+
+        let button_right = gtk::Button::new();
+        button_right.set_relief(gtk::ReliefStyle::None);
+        let arrow =
+            gtk::Image::new_from_icon_name(Some(&"pan-end-symbolic"), gtk::IconSize::Button);
+        button_right.add(&arrow);
+        button_right.set_size_request(20, 0);
+        self_.pack_start(&button_right, false, false, 0);
+        button_right.connect_clicked(clone!(@weak self_ => move |_| {
+            let priv_ = ThumbNavPriv::from_instance(&self_);
+            priv_.right_button_clicked();
+        }));
+        let adj = sw.get_hadjustment();
+
+        if self
+            .widgets
+            .set(ThumbNavWidgets {
+                button_left,
+                button_right,
+                sw,
+            })
+            .is_err()
+        {
+            err_out!("Widgets already set.");
+        }
+
+        if let Some(adj) = adj {
+            // The value-changed signal might not be emitted because the value is already 0.
+            // Ensure the state first.
+            self.adj_value_changed(&adj);
+            if let Err(err) = adj.emit("value-changed", &[]) {
+                err_out!("ThumbNav::constructed: signal emit failed: {}", err);
+            }
+        }
+
+        self.add_thumbview();
+    }
+
+    fn set_property(&self, _obj: &glib::Object, id: usize, value: &glib::Value) {
+        let prop = &PROPERTIES[id];
+        match *prop {
+            subclass::Property("show-buttons", ..) => {
+                let show_buttons = value
+                    .get_some()
+                    .expect("type conformity checked by `Object::set_property`");
+                self.set_show_buttons(show_buttons);
+            }
+            subclass::Property("thumbview", ..) => {
+                let thumbview: Option<gtk::IconView> = value
+                    .get()
+                    .expect("type conformity checked by `Object::set_property`");
+                self.thumbview.replace(thumbview);
+                self.add_thumbview();
+            }
+            subclass::Property("mode", ..) => {
+                let mode: i32 = value
+                    .get_some()
+                    .expect("type conformity checked by `Object::set_property`");
+                self.set_mode(ThumbNavMode::from(mode));
+            }
+            _ => unimplemented!(),
+        }
+    }
+
+    fn get_property(&self, _obj: &glib::Object, id: usize) -> Result<glib::Value, ()> {
+        let prop = &PROPERTIES[id];
+
+        match *prop {
+            subclass::Property("show-buttons", ..) => Ok(self.show_buttons.get().to_value()),
+            subclass::Property("thumbview", ..) => Ok(self.thumbview.borrow().to_value()),
+            subclass::Property("mode", ..) => {
+                let n: i32 = self.mode.get().into();
+                Ok(n.to_value())
+            }
+            _ => unimplemented!(),
+        }
+    }
+}
+
+impl WidgetImpl for ThumbNavPriv {}
+
+impl ContainerImpl for ThumbNavPriv {}
+
+impl BoxImpl for ThumbNavPriv {}
+
+#[no_mangle]
+pub unsafe extern "C" fn npc_thumb_nav_new(
+    thumbview: *mut gtk_sys::GtkIconView,
+    mode: ThumbNavMode,
+    show_buttons: bool,
+) -> *mut gtk_sys::GtkWidget {
+    ThumbNav::new(
+        &gtk::IconView::from_glib_full(thumbview),
+        mode,
+        show_buttons,
+    )
+    .upcast::<gtk::Widget>()
+    .to_glib_full()
+}


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