diff --git a/data/gtk/window.ui b/data/gtk/window.ui index 2ceed00..b7ba5de 100644 --- a/data/gtk/window.ui +++ b/data/gtk/window.ui @@ -4,7 +4,12 @@ 600 498 360 - Image Viewer + Image Viewer + + + image_view + + vertical diff --git a/src/widgets/image_view.rs b/src/widgets/image_view.rs index d7fbfb3..67eb106 100644 --- a/src/widgets/image_view.rs +++ b/src/widgets/image_view.rs @@ -33,7 +33,6 @@ use std::cell::{Cell, RefCell}; use crate::file_model::LpFileModel; use crate::thumbnail::Thumbnail; -use crate::util; use crate::widgets::LpImagePage; // The number of pages we want to buffer @@ -50,7 +49,6 @@ mod imp { pub carousel: TemplateChild, pub model: RefCell>, - pub filename: RefCell>, pub current_model_index: Cell, } @@ -81,11 +79,11 @@ mod imp { impl ObjectImpl for LpImageView { fn properties() -> &'static [glib::ParamSpec] { static PROPERTIES: Lazy> = Lazy::new(|| { - vec![glib::ParamSpecString::new( - "filename", - "Filename", - "The filename of the current file", - None, + vec![glib::ParamSpecObject::new( + "active-file", + "", + "", + gio::File::static_type(), glib::ParamFlags::READABLE, )] }); @@ -95,7 +93,7 @@ mod imp { fn property(&self, obj: &Self::Type, _id: usize, psec: &glib::ParamSpec) -> glib::Value { match psec.name() { - "filename" => obj.filename().to_value(), + "active-file" => obj.active_file().to_value(), _ => unimplemented!(), } } @@ -120,6 +118,11 @@ mod imp { })); obj.add_controller(&source); + + self.carousel + .connect_position_notify(clone!(@weak obj => move |_| { + obj.notify("active-file"); + })); } } @@ -136,14 +139,14 @@ glib::wrapper! { #[gtk::template_callbacks] impl LpImageView { pub fn set_image_from_file(&self, file: &gio::File) -> anyhow::Result<()> { - if let Some(current_file) = self.current_page().and_then(|p| p.file()) { + if let Some(current_file) = self.active_file() { if current_file.equal(file) { bail!("Image is the same as the previous image; Doing nothing."); } } self.build_model_from_file(file); - self.notify("filename"); + self.notify("active-file"); // TODO: rework width stuff // let width = imp.picture.image_width(); @@ -188,7 +191,6 @@ impl LpImageView { if let Some(model) = imp.model.borrow().as_ref() { let index = model.index_of(file).unwrap(); log::debug!("Currently at file {index} in the directory"); - imp.filename.replace(util::get_file_display_name(file)); carousel.append(&LpImagePage::from_file(file)); self.fill_carousel(model, index); self.update_action_state(model, index); @@ -297,14 +299,12 @@ impl LpImageView { let imp = self.imp(); let b = imp.model.borrow(); let model = b.as_ref().unwrap(); - let current = self.current_page().and_then(|p| p.file()).unwrap(); + let current = self.active_file().unwrap(); let model_index = model.index_of(¤t).unwrap(); let prev_index = imp.current_model_index.get(); if model_index != prev_index { - imp.filename.replace(util::get_file_display_name(¤t)); - self.notify("filename"); self.update_action_state(model, model_index); // We've moved forward @@ -394,8 +394,7 @@ impl LpImageView { pub fn print(&self) -> anyhow::Result<()> { let operation = gtk::PrintOperation::new(); let path = self - .current_page() - .and_then(|p| p.file()) + .active_file() .context("No file")? .peek_path() .context("No path")?; @@ -454,12 +453,11 @@ impl LpImageView { } pub fn uri(&self) -> Option { - let page = self.current_page().expect("No page"); - let file = page.file().expect("No file"); - Some(file.uri().to_string()) + self.active_file().map(|f| f.uri().to_string()) } - pub fn filename(&self) -> Option { - self.imp().filename.borrow().clone() + pub fn active_file(&self) -> Option { + let page = self.current_page()?; + page.file() } } diff --git a/src/window.rs b/src/window.rs index 663ff79..30555f3 100644 --- a/src/window.rs +++ b/src/window.rs @@ -83,6 +83,7 @@ mod imp { // bind_template() is a function generated by the // CompositeTemplate macro to bind all children at once. Self::bind_template(klass); + Self::Type::bind_template_callbacks(klass); // Set up actions klass.install_action("win.toggle-fullscreen", None, move |win, _, _| { @@ -139,20 +140,12 @@ mod imp { let help_overlay = builder.object("help_overlay").unwrap(); obj.set_help_overlay(Some(&help_overlay)); + self.drop_target.set_types(&[gdk::FileList::static_type()]); + // For callbacks, you will want to reference the GTK docs on // the relevant signal to see which parameters you need. - // In this case, we need one to react to the signal, - // so we name it `iv` then use `_` for the other spots. - self.image_view.connect_notify_local( - Some("filename"), - clone!(@weak obj => move |iv, _| { - if let Some(filename) = iv.filename() { - obj.set_title(Some(&filename)); - } - }), - ); - - self.drop_target.set_types(&[gdk::FileList::static_type()]); + // In this case, we need only need the GValue, + // so we name it `value` then use `_` for the other spots. self.drop_target.connect_drop( clone!(@weak obj => @default-return false, move |_, value, _, _| { // Here we use a GValue, which is a dynamic object that can hold different types, @@ -206,6 +199,7 @@ glib::wrapper! { @implements gio::ActionMap, gio::ActionGroup, gtk::Native; } +#[gtk::template_callbacks] impl LpWindow { pub fn new>(app: &A) -> Self { glib::Object::new(&[("application", app)]).expect("Failed to create LpWindow") @@ -261,8 +255,7 @@ impl LpWindow { if let Some(file) = imp .image_view - .current_page() - .and_then(|p| p.file()) + .active_file() .and_then(|f| f.peek_path()) .and_then(|p| std::fs::File::open(p).ok()) { @@ -375,4 +368,18 @@ impl LpWindow { self.set_default_size(final_width, final_height); log::debug!("Window resized to {} x {}", final_width, final_height); } + + // In the LpWindow UI file we define a `gtk::Expression`s + // that is a closure. This closure takes the current `gio::File` + // and processes it to return a window title. + // + // In this function we chain `Option`s with `and_then()` in order + // to handle optional results with a fallback, without needing to + // have multiple `match` or `if let` branches, and without needing + // to unwrap. + #[template_callback] + fn window_title(&self, file: Option) -> String { + file.and_then(|f| util::get_file_display_name(&f)) // If the file exists, get display name + .unwrap_or_else(|| String::from("Image Viewer")) // Return that or the default if there's nothing + } }