image_view: Expose file as a property instead of filename
The GFile is more useful for other data. The window can use expressions to calculate an appropriate title, so we no longer need to call `set_title()` or connect to a signal manually. This commit also adjusts the `uri()` and `print()` functions to use `active_file()`
This commit is contained in:
parent
fa513596b2
commit
1e95dd4cbb
3 changed files with 46 additions and 36 deletions
|
|
@ -4,7 +4,12 @@
|
||||||
<property name="default-width">600</property>
|
<property name="default-width">600</property>
|
||||||
<property name="default-height">498</property>
|
<property name="default-height">498</property>
|
||||||
<property name="width-request">360</property>
|
<property name="width-request">360</property>
|
||||||
<property name="title">Image Viewer</property>
|
<property name="title" translatable="yes">Image Viewer</property>
|
||||||
|
<binding name="title">
|
||||||
|
<closure type="gchararray" function="window_title">
|
||||||
|
<lookup type="LpImageView" name="active-file">image_view</lookup>
|
||||||
|
</closure>
|
||||||
|
</binding>
|
||||||
<property name="content">
|
<property name="content">
|
||||||
<object class="AdwFlap" id="flap">
|
<object class="AdwFlap" id="flap">
|
||||||
<property name="orientation">vertical</property>
|
<property name="orientation">vertical</property>
|
||||||
|
|
|
||||||
|
|
@ -33,7 +33,6 @@ use std::cell::{Cell, RefCell};
|
||||||
|
|
||||||
use crate::file_model::LpFileModel;
|
use crate::file_model::LpFileModel;
|
||||||
use crate::thumbnail::Thumbnail;
|
use crate::thumbnail::Thumbnail;
|
||||||
use crate::util;
|
|
||||||
use crate::widgets::LpImagePage;
|
use crate::widgets::LpImagePage;
|
||||||
|
|
||||||
// The number of pages we want to buffer
|
// The number of pages we want to buffer
|
||||||
|
|
@ -50,7 +49,6 @@ mod imp {
|
||||||
pub carousel: TemplateChild<adw::Carousel>,
|
pub carousel: TemplateChild<adw::Carousel>,
|
||||||
|
|
||||||
pub model: RefCell<Option<LpFileModel>>,
|
pub model: RefCell<Option<LpFileModel>>,
|
||||||
pub filename: RefCell<Option<String>>,
|
|
||||||
pub current_model_index: Cell<u32>,
|
pub current_model_index: Cell<u32>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -81,11 +79,11 @@ mod imp {
|
||||||
impl ObjectImpl for LpImageView {
|
impl ObjectImpl for LpImageView {
|
||||||
fn properties() -> &'static [glib::ParamSpec] {
|
fn properties() -> &'static [glib::ParamSpec] {
|
||||||
static PROPERTIES: Lazy<Vec<glib::ParamSpec>> = Lazy::new(|| {
|
static PROPERTIES: Lazy<Vec<glib::ParamSpec>> = Lazy::new(|| {
|
||||||
vec![glib::ParamSpecString::new(
|
vec![glib::ParamSpecObject::new(
|
||||||
"filename",
|
"active-file",
|
||||||
"Filename",
|
"",
|
||||||
"The filename of the current file",
|
"",
|
||||||
None,
|
gio::File::static_type(),
|
||||||
glib::ParamFlags::READABLE,
|
glib::ParamFlags::READABLE,
|
||||||
)]
|
)]
|
||||||
});
|
});
|
||||||
|
|
@ -95,7 +93,7 @@ mod imp {
|
||||||
|
|
||||||
fn property(&self, obj: &Self::Type, _id: usize, psec: &glib::ParamSpec) -> glib::Value {
|
fn property(&self, obj: &Self::Type, _id: usize, psec: &glib::ParamSpec) -> glib::Value {
|
||||||
match psec.name() {
|
match psec.name() {
|
||||||
"filename" => obj.filename().to_value(),
|
"active-file" => obj.active_file().to_value(),
|
||||||
_ => unimplemented!(),
|
_ => unimplemented!(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -120,6 +118,11 @@ mod imp {
|
||||||
}));
|
}));
|
||||||
|
|
||||||
obj.add_controller(&source);
|
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]
|
#[gtk::template_callbacks]
|
||||||
impl LpImageView {
|
impl LpImageView {
|
||||||
pub fn set_image_from_file(&self, file: &gio::File) -> anyhow::Result<()> {
|
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) {
|
if current_file.equal(file) {
|
||||||
bail!("Image is the same as the previous image; Doing nothing.");
|
bail!("Image is the same as the previous image; Doing nothing.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
self.build_model_from_file(file);
|
self.build_model_from_file(file);
|
||||||
self.notify("filename");
|
self.notify("active-file");
|
||||||
|
|
||||||
// TODO: rework width stuff
|
// TODO: rework width stuff
|
||||||
// let width = imp.picture.image_width();
|
// let width = imp.picture.image_width();
|
||||||
|
|
@ -188,7 +191,6 @@ impl LpImageView {
|
||||||
if let Some(model) = imp.model.borrow().as_ref() {
|
if let Some(model) = imp.model.borrow().as_ref() {
|
||||||
let index = model.index_of(file).unwrap();
|
let index = model.index_of(file).unwrap();
|
||||||
log::debug!("Currently at file {index} in the directory");
|
log::debug!("Currently at file {index} in the directory");
|
||||||
imp.filename.replace(util::get_file_display_name(file));
|
|
||||||
carousel.append(&LpImagePage::from_file(file));
|
carousel.append(&LpImagePage::from_file(file));
|
||||||
self.fill_carousel(model, index);
|
self.fill_carousel(model, index);
|
||||||
self.update_action_state(model, index);
|
self.update_action_state(model, index);
|
||||||
|
|
@ -297,14 +299,12 @@ impl LpImageView {
|
||||||
let imp = self.imp();
|
let imp = self.imp();
|
||||||
let b = imp.model.borrow();
|
let b = imp.model.borrow();
|
||||||
let model = b.as_ref().unwrap();
|
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 model_index = model.index_of(¤t).unwrap();
|
||||||
let prev_index = imp.current_model_index.get();
|
let prev_index = imp.current_model_index.get();
|
||||||
|
|
||||||
if model_index != prev_index {
|
if model_index != prev_index {
|
||||||
imp.filename.replace(util::get_file_display_name(¤t));
|
|
||||||
self.notify("filename");
|
|
||||||
self.update_action_state(model, model_index);
|
self.update_action_state(model, model_index);
|
||||||
|
|
||||||
// We've moved forward
|
// We've moved forward
|
||||||
|
|
@ -394,8 +394,7 @@ impl LpImageView {
|
||||||
pub fn print(&self) -> anyhow::Result<()> {
|
pub fn print(&self) -> anyhow::Result<()> {
|
||||||
let operation = gtk::PrintOperation::new();
|
let operation = gtk::PrintOperation::new();
|
||||||
let path = self
|
let path = self
|
||||||
.current_page()
|
.active_file()
|
||||||
.and_then(|p| p.file())
|
|
||||||
.context("No file")?
|
.context("No file")?
|
||||||
.peek_path()
|
.peek_path()
|
||||||
.context("No path")?;
|
.context("No path")?;
|
||||||
|
|
@ -454,12 +453,11 @@ impl LpImageView {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn uri(&self) -> Option<String> {
|
pub fn uri(&self) -> Option<String> {
|
||||||
let page = self.current_page().expect("No page");
|
self.active_file().map(|f| f.uri().to_string())
|
||||||
let file = page.file().expect("No file");
|
|
||||||
Some(file.uri().to_string())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn filename(&self) -> Option<String> {
|
pub fn active_file(&self) -> Option<gio::File> {
|
||||||
self.imp().filename.borrow().clone()
|
let page = self.current_page()?;
|
||||||
|
page.file()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -83,6 +83,7 @@ mod imp {
|
||||||
// bind_template() is a function generated by the
|
// bind_template() is a function generated by the
|
||||||
// CompositeTemplate macro to bind all children at once.
|
// CompositeTemplate macro to bind all children at once.
|
||||||
Self::bind_template(klass);
|
Self::bind_template(klass);
|
||||||
|
Self::Type::bind_template_callbacks(klass);
|
||||||
|
|
||||||
// Set up actions
|
// Set up actions
|
||||||
klass.install_action("win.toggle-fullscreen", None, move |win, _, _| {
|
klass.install_action("win.toggle-fullscreen", None, move |win, _, _| {
|
||||||
|
|
@ -139,20 +140,12 @@ mod imp {
|
||||||
let help_overlay = builder.object("help_overlay").unwrap();
|
let help_overlay = builder.object("help_overlay").unwrap();
|
||||||
obj.set_help_overlay(Some(&help_overlay));
|
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
|
// For callbacks, you will want to reference the GTK docs on
|
||||||
// the relevant signal to see which parameters you need.
|
// the relevant signal to see which parameters you need.
|
||||||
// In this case, we need one to react to the signal,
|
// In this case, we need only need the GValue,
|
||||||
// so we name it `iv` then use `_` for the other spots.
|
// so we name it `value` 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()]);
|
|
||||||
self.drop_target.connect_drop(
|
self.drop_target.connect_drop(
|
||||||
clone!(@weak obj => @default-return false, move |_, value, _, _| {
|
clone!(@weak obj => @default-return false, move |_, value, _, _| {
|
||||||
// Here we use a GValue, which is a dynamic object that can hold different types,
|
// 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;
|
@implements gio::ActionMap, gio::ActionGroup, gtk::Native;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[gtk::template_callbacks]
|
||||||
impl LpWindow {
|
impl LpWindow {
|
||||||
pub fn new<A: IsA<gtk::Application>>(app: &A) -> Self {
|
pub fn new<A: IsA<gtk::Application>>(app: &A) -> Self {
|
||||||
glib::Object::new(&[("application", app)]).expect("Failed to create LpWindow")
|
glib::Object::new(&[("application", app)]).expect("Failed to create LpWindow")
|
||||||
|
|
@ -261,8 +255,7 @@ impl LpWindow {
|
||||||
|
|
||||||
if let Some(file) = imp
|
if let Some(file) = imp
|
||||||
.image_view
|
.image_view
|
||||||
.current_page()
|
.active_file()
|
||||||
.and_then(|p| p.file())
|
|
||||||
.and_then(|f| f.peek_path())
|
.and_then(|f| f.peek_path())
|
||||||
.and_then(|p| std::fs::File::open(p).ok())
|
.and_then(|p| std::fs::File::open(p).ok())
|
||||||
{
|
{
|
||||||
|
|
@ -375,4 +368,18 @@ impl LpWindow {
|
||||||
self.set_default_size(final_width, final_height);
|
self.set_default_size(final_width, final_height);
|
||||||
log::debug!("Window resized to {} x {}", 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<gio::File>) -> 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
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue