2020-12-26 02:05:34 -08:00
|
|
|
// image_view.rs
|
|
|
|
|
//
|
|
|
|
|
// Copyright 2020 Christopher Davis <christopherdavis@gnome.org>
|
|
|
|
|
//
|
|
|
|
|
// 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/>.
|
|
|
|
|
//
|
|
|
|
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
|
|
|
|
|
|
|
|
use gdk::prelude::*;
|
|
|
|
|
use glib::clone;
|
|
|
|
|
use glib::subclass::prelude::*;
|
|
|
|
|
use gtk::prelude::*;
|
|
|
|
|
use gtk::subclass::prelude::*;
|
|
|
|
|
use gtk::CompositeTemplate;
|
2021-01-23 23:36:28 -08:00
|
|
|
use libadwaita::subclass::prelude::*;
|
2020-12-26 02:05:34 -08:00
|
|
|
|
|
|
|
|
use anyhow::Context;
|
2021-08-17 20:37:04 -07:00
|
|
|
use ashpd::desktop::wallpaper;
|
|
|
|
|
use ashpd::WindowIdentifier;
|
2021-01-30 15:52:32 -08:00
|
|
|
use once_cell::sync::Lazy;
|
2020-12-26 02:05:34 -08:00
|
|
|
use std::cell::{Cell, RefCell};
|
|
|
|
|
|
2021-09-05 05:51:34 -07:00
|
|
|
use crate::util;
|
2021-12-13 18:19:23 -08:00
|
|
|
use crate::widgets::IvImage;
|
2021-09-05 05:51:34 -07:00
|
|
|
|
2020-12-26 02:05:34 -08:00
|
|
|
mod imp {
|
|
|
|
|
use super::*;
|
|
|
|
|
|
2021-09-05 05:51:34 -07:00
|
|
|
#[derive(Debug, Default, CompositeTemplate)]
|
2021-09-28 17:44:26 -07:00
|
|
|
#[template(resource = "/org/gnome/Loupe/gtk/image_view.ui")]
|
2020-12-26 02:05:34 -08:00
|
|
|
pub struct IvImageView {
|
|
|
|
|
#[template_child]
|
2021-12-13 18:19:23 -08:00
|
|
|
pub picture: TemplateChild<IvImage>,
|
2020-12-26 02:05:34 -08:00
|
|
|
#[template_child]
|
2021-09-24 05:58:55 -07:00
|
|
|
pub controls: TemplateChild<gtk::Box>,
|
|
|
|
|
#[template_child]
|
2020-12-26 02:05:34 -08:00
|
|
|
pub popover: TemplateChild<gtk::PopoverMenu>,
|
|
|
|
|
#[template_child]
|
|
|
|
|
pub click_gesture: TemplateChild<gtk::GestureClick>,
|
|
|
|
|
#[template_child]
|
|
|
|
|
pub press_gesture: TemplateChild<gtk::GestureLongPress>,
|
|
|
|
|
|
2021-09-24 01:14:56 -07:00
|
|
|
// RefCell<T> allows for interior mutability of non-primitive types.
|
2020-12-26 02:05:34 -08:00
|
|
|
pub menu_model: RefCell<Option<gio::MenuModel>>,
|
|
|
|
|
pub popover_menu_model: RefCell<Option<gio::MenuModel>>,
|
2021-09-05 05:51:34 -07:00
|
|
|
|
|
|
|
|
pub directory: RefCell<Option<String>>,
|
|
|
|
|
pub filename: RefCell<Option<String>>,
|
2021-09-28 22:08:57 -07:00
|
|
|
pub uri: RefCell<Option<String>>,
|
2021-09-05 05:51:34 -07:00
|
|
|
// Path of filenames
|
|
|
|
|
pub directory_pictures: RefCell<Vec<String>>,
|
2021-09-24 01:14:56 -07:00
|
|
|
// Cell<T> allows for interior mutability of primitive types
|
2021-09-05 05:51:34 -07:00
|
|
|
pub index: Cell<usize>,
|
2020-12-26 02:05:34 -08:00
|
|
|
}
|
|
|
|
|
|
2021-06-27 15:28:35 -07:00
|
|
|
#[glib::object_subclass]
|
2020-12-26 02:05:34 -08:00
|
|
|
impl ObjectSubclass for IvImageView {
|
|
|
|
|
const NAME: &'static str = "IvImageView";
|
|
|
|
|
type Type = super::IvImageView;
|
2021-01-23 23:36:28 -08:00
|
|
|
type ParentType = libadwaita::Bin;
|
2020-12-26 02:05:34 -08:00
|
|
|
|
|
|
|
|
fn class_init(klass: &mut Self::Class) {
|
2021-01-30 15:52:32 -08:00
|
|
|
Self::bind_template(klass);
|
2021-09-05 05:51:34 -07:00
|
|
|
|
|
|
|
|
klass.install_action("iv.next", None, move |image_view, _, _| {
|
|
|
|
|
image_view.next();
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
klass.install_action("iv.previous", None, move |image_view, _, _| {
|
|
|
|
|
image_view.previous();
|
|
|
|
|
});
|
2020-12-26 02:05:34 -08:00
|
|
|
}
|
2021-01-13 23:05:45 -08:00
|
|
|
|
2021-06-27 15:28:35 -07:00
|
|
|
fn instance_init(obj: &glib::subclass::InitializingObject<Self>) {
|
2021-01-13 23:05:45 -08:00
|
|
|
obj.init_template();
|
|
|
|
|
}
|
2020-12-26 02:05:34 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl ObjectImpl for IvImageView {
|
2021-01-30 15:52:32 -08:00
|
|
|
fn properties() -> &'static [glib::ParamSpec] {
|
|
|
|
|
static PROPERTIES: Lazy<Vec<glib::ParamSpec>> = Lazy::new(|| {
|
|
|
|
|
vec![
|
2021-09-05 05:51:34 -07:00
|
|
|
glib::ParamSpec::new_string(
|
|
|
|
|
"filename",
|
|
|
|
|
"Filename",
|
|
|
|
|
"The filename of the current file",
|
|
|
|
|
None,
|
|
|
|
|
glib::ParamFlags::READABLE,
|
|
|
|
|
),
|
2021-09-24 05:58:55 -07:00
|
|
|
glib::ParamSpec::new_object(
|
|
|
|
|
"controls",
|
|
|
|
|
"Controls",
|
|
|
|
|
"The controls for the image view",
|
|
|
|
|
gtk::Box::static_type(),
|
|
|
|
|
glib::ParamFlags::READABLE,
|
|
|
|
|
),
|
2021-01-30 15:52:32 -08:00
|
|
|
]
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
PROPERTIES.as_ref()
|
2020-12-26 02:05:34 -08:00
|
|
|
}
|
|
|
|
|
|
2021-06-27 15:28:35 -07:00
|
|
|
fn property(&self, _obj: &Self::Type, _id: usize, psec: &glib::ParamSpec) -> glib::Value {
|
|
|
|
|
match psec.name() {
|
2021-09-05 05:51:34 -07:00
|
|
|
"filename" => self.filename.borrow().to_value(),
|
2021-09-24 05:58:55 -07:00
|
|
|
"controls" => self.controls.to_value(),
|
2020-12-26 02:05:34 -08:00
|
|
|
_ => unimplemented!(),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2021-01-30 15:52:32 -08:00
|
|
|
fn constructed(&self, obj: &Self::Type) {
|
|
|
|
|
self.parent_constructed(obj);
|
|
|
|
|
|
|
|
|
|
self.click_gesture
|
|
|
|
|
.connect_pressed(clone!(@weak obj => move |_, _, x, y| {
|
|
|
|
|
obj.show_popover_at(x, y);
|
|
|
|
|
}));
|
|
|
|
|
|
|
|
|
|
self.press_gesture
|
|
|
|
|
.connect_pressed(clone!(@weak obj => move |_, x, y| {
|
|
|
|
|
obj.show_popover_at(x, y);
|
|
|
|
|
}));
|
|
|
|
|
}
|
2020-12-26 02:05:34 -08:00
|
|
|
}
|
|
|
|
|
|
2021-09-29 01:00:31 -07:00
|
|
|
impl WidgetImpl for IvImageView {}
|
2021-01-23 23:36:28 -08:00
|
|
|
impl BinImpl for IvImageView {}
|
2020-12-26 02:05:34 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
glib::wrapper! {
|
|
|
|
|
pub struct IvImageView(ObjectSubclass<imp::IvImageView>)
|
2021-01-23 23:36:28 -08:00
|
|
|
@extends gtk::Widget, libadwaita::Bin,
|
2020-12-26 02:05:34 -08:00
|
|
|
@implements gtk::Buildable, gtk::ConstraintTarget, gtk::Orientable;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl IvImageView {
|
2021-09-28 22:08:57 -07:00
|
|
|
pub fn set_image_from_file(&self, file: &gio::File) -> anyhow::Result<(i32, i32)> {
|
2020-12-26 02:05:34 -08:00
|
|
|
let imp = imp::IvImageView::from_instance(&self);
|
|
|
|
|
|
2021-09-05 05:51:34 -07:00
|
|
|
if let Some(current_file) = imp.picture.file() {
|
|
|
|
|
if current_file.path().as_deref() == file.path().as_deref() {
|
2021-09-28 22:08:57 -07:00
|
|
|
return Err(anyhow::Error::msg(
|
|
|
|
|
"Image is the same as the previous image; Doing nothing.",
|
|
|
|
|
));
|
2021-09-05 05:51:34 -07:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
self.set_parent_from_file(file);
|
2021-12-13 18:19:23 -08:00
|
|
|
imp.picture.set_file(file);
|
2021-09-28 22:08:57 -07:00
|
|
|
*imp.uri.borrow_mut() = Some(file.uri().to_string());
|
2021-09-05 05:51:34 -07:00
|
|
|
self.notify("filename");
|
|
|
|
|
self.update_action_state();
|
2021-03-02 16:39:03 -08:00
|
|
|
|
2021-12-13 18:19:23 -08:00
|
|
|
let width = imp.picture.image_width();
|
|
|
|
|
let height = imp.picture.image_height();
|
|
|
|
|
|
|
|
|
|
log::debug!("Image dimensions: {} x {}", width, height);
|
|
|
|
|
Ok((width, height))
|
2021-03-02 16:39:03 -08:00
|
|
|
}
|
|
|
|
|
|
2021-09-05 05:51:34 -07:00
|
|
|
fn set_parent_from_file(&self, file: &gio::File) {
|
|
|
|
|
let imp = imp::IvImageView::from_instance(&self);
|
|
|
|
|
|
|
|
|
|
if let Some(parent) = file.parent() {
|
|
|
|
|
let parent_path = parent.path().map(|p| p.to_str().unwrap().to_string());
|
|
|
|
|
let mut directory_vec = imp.directory_pictures.borrow_mut();
|
|
|
|
|
|
|
|
|
|
if parent_path.as_deref() != imp.directory.borrow().as_deref() {
|
|
|
|
|
*imp.directory.borrow_mut() = parent_path;
|
|
|
|
|
directory_vec.clear();
|
|
|
|
|
|
|
|
|
|
let enumerator = parent
|
|
|
|
|
.enumerate_children(
|
|
|
|
|
&format!(
|
|
|
|
|
"{},{}",
|
|
|
|
|
*gio::FILE_ATTRIBUTE_STANDARD_DISPLAY_NAME,
|
|
|
|
|
*gio::FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE
|
|
|
|
|
),
|
|
|
|
|
gio::FileQueryInfoFlags::NONE,
|
|
|
|
|
gio::NONE_CANCELLABLE,
|
|
|
|
|
)
|
|
|
|
|
.unwrap();
|
|
|
|
|
|
|
|
|
|
// Filter out non-images; For now we support "all" image types.
|
|
|
|
|
enumerator.for_each(|info| {
|
|
|
|
|
if let Ok(info) = info {
|
|
|
|
|
if let Some(content_type) = info.content_type().map(|t| t.to_string()) {
|
|
|
|
|
let filename = info.display_name().to_string();
|
|
|
|
|
log::debug!("Filename: {}", filename);
|
|
|
|
|
log::debug!("Mimetype: {}", content_type);
|
|
|
|
|
|
|
|
|
|
if content_type.starts_with("image/") {
|
|
|
|
|
log::debug!("{} is an image, adding to the list", filename);
|
|
|
|
|
directory_vec.push(filename);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// Then sort by name.
|
|
|
|
|
directory_vec.sort_by(|name_a, name_b| {
|
|
|
|
|
util::utf8_collate_key_for_filename(name_a)
|
|
|
|
|
.cmp(&util::utf8_collate_key_for_filename(name_b))
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
log::debug!("Sorted files: {:?}", directory_vec);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
*imp.filename.borrow_mut() = util::get_file_display_name(file);
|
|
|
|
|
|
|
|
|
|
imp.index.set(
|
|
|
|
|
directory_vec
|
|
|
|
|
.iter()
|
|
|
|
|
.position(|f| Some(f) == imp.filename.borrow().as_ref())
|
|
|
|
|
.unwrap(),
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
log::debug!("Current index is {}", imp.index.get());
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn update_image(&self) {
|
|
|
|
|
let imp = imp::IvImageView::from_instance(&self);
|
|
|
|
|
|
|
|
|
|
let path = &format!(
|
|
|
|
|
"{}/{}",
|
|
|
|
|
imp.directory.borrow().as_ref().unwrap(),
|
|
|
|
|
&imp.directory_pictures.borrow()[imp.index.get()]
|
|
|
|
|
);
|
2021-09-28 22:08:57 -07:00
|
|
|
|
2021-09-05 05:51:34 -07:00
|
|
|
let file = gio::File::for_path(path);
|
2021-12-13 18:19:23 -08:00
|
|
|
imp.picture.set_file(&file);
|
2021-09-28 22:08:57 -07:00
|
|
|
*imp.uri.borrow_mut() = Some(file.uri().to_string());
|
2021-09-05 05:51:34 -07:00
|
|
|
*imp.filename.borrow_mut() = util::get_file_display_name(&file);
|
|
|
|
|
self.notify("filename");
|
|
|
|
|
self.update_action_state();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn next(&self) {
|
|
|
|
|
let imp = imp::IvImageView::from_instance(&self);
|
|
|
|
|
// TODO: Replace with `Cell::update()` once stabilized
|
|
|
|
|
imp.index.set(imp.index.get() + 1);
|
|
|
|
|
self.update_image();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn previous(&self) {
|
|
|
|
|
let imp = imp::IvImageView::from_instance(&self);
|
|
|
|
|
// TODO: Replace with `Cell::update()` once stabilized
|
|
|
|
|
imp.index.set(imp.index.get() - 1);
|
|
|
|
|
self.update_image();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn update_action_state(&self) {
|
|
|
|
|
let imp = imp::IvImageView::from_instance(&self);
|
|
|
|
|
let index = imp.index.get();
|
|
|
|
|
self.action_set_enabled("iv.next", index < imp.directory_pictures.borrow().len() - 1);
|
|
|
|
|
self.action_set_enabled("iv.previous", index > 0);
|
|
|
|
|
}
|
|
|
|
|
|
2020-12-26 02:05:34 -08:00
|
|
|
pub fn set_wallpaper(&self) -> anyhow::Result<()> {
|
|
|
|
|
let wallpaper = self.uri().context("No URI for current file")?;
|
2021-08-17 20:37:04 -07:00
|
|
|
let ctx = glib::MainContext::default();
|
|
|
|
|
ctx.spawn_local(async move {
|
|
|
|
|
set_wallpaper(wallpaper).await;
|
|
|
|
|
});
|
2020-12-26 02:05:34 -08:00
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn print(&self) -> anyhow::Result<()> {
|
|
|
|
|
let imp = imp::IvImageView::from_instance(&self);
|
|
|
|
|
|
|
|
|
|
let operation = gtk::PrintOperation::new();
|
2021-09-28 22:08:57 -07:00
|
|
|
let path = &format!(
|
|
|
|
|
"{}/{}",
|
|
|
|
|
imp.directory.borrow().as_ref().unwrap(),
|
|
|
|
|
&imp.directory_pictures.borrow()[imp.index.get()]
|
|
|
|
|
);
|
2020-12-26 02:05:34 -08:00
|
|
|
let pb = gdk_pixbuf::Pixbuf::from_file(path)?;
|
|
|
|
|
|
|
|
|
|
let setup = gtk::PageSetup::default();
|
|
|
|
|
let page_size = gtk::PaperSize::new(Some(>k::PAPER_NAME_A4));
|
|
|
|
|
setup.set_paper_size(&page_size);
|
|
|
|
|
operation.set_default_page_setup(Some(&setup));
|
|
|
|
|
|
|
|
|
|
let settings = gtk::PrintSettings::default();
|
|
|
|
|
operation.set_print_settings(Some(&settings));
|
|
|
|
|
|
|
|
|
|
operation.connect_begin_print(move |op, _| {
|
|
|
|
|
op.set_n_pages(1);
|
|
|
|
|
});
|
|
|
|
|
|
2021-09-28 22:59:37 -07:00
|
|
|
// FIXME: Rework all of this after reading https://cairographics.org/manual/cairo-Image-Surfaces.html
|
|
|
|
|
// Since I don't know cairo; See also eog-print.c
|
2021-03-02 15:09:44 -08:00
|
|
|
operation.connect_draw_page(clone!(@weak pb => move |_, ctx, _| {
|
2021-06-27 15:28:35 -07:00
|
|
|
let cr = ctx.cairo_context().expect("No cairo context for print context");
|
2020-12-26 02:05:34 -08:00
|
|
|
cr.set_source_pixbuf(&pb, 0.0, 0.0);
|
2021-09-28 22:59:37 -07:00
|
|
|
let _ = cr.paint();
|
2020-12-26 02:05:34 -08:00
|
|
|
}));
|
|
|
|
|
|
|
|
|
|
log::debug!("Running print operation...");
|
2021-06-27 15:28:35 -07:00
|
|
|
let root = self.root().context("Could not get root for widget")?;
|
2020-12-26 02:05:34 -08:00
|
|
|
let window = root
|
|
|
|
|
.downcast_ref::<gtk::Window>()
|
|
|
|
|
.context("Could not downcast to GtkWindow")?;
|
|
|
|
|
operation.run(gtk::PrintOperationAction::PrintDialog, Some(window))?;
|
|
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn uri(&self) -> Option<String> {
|
|
|
|
|
let imp = imp::IvImageView::from_instance(&self);
|
2021-09-28 22:08:57 -07:00
|
|
|
imp.uri.borrow().to_owned()
|
2020-12-26 02:05:34 -08:00
|
|
|
}
|
|
|
|
|
|
2021-09-05 05:51:34 -07:00
|
|
|
pub fn filename(&self) -> Option<String> {
|
|
|
|
|
let imp = imp::IvImageView::from_instance(&self);
|
|
|
|
|
imp.filename.borrow().to_owned()
|
|
|
|
|
}
|
|
|
|
|
|
2020-12-26 02:05:34 -08:00
|
|
|
pub fn show_popover_at(&self, x: f64, y: f64) {
|
|
|
|
|
let imp = imp::IvImageView::from_instance(&self);
|
|
|
|
|
|
|
|
|
|
let rect = gdk::Rectangle {
|
|
|
|
|
x: x as i32,
|
|
|
|
|
y: y as i32,
|
|
|
|
|
width: 0,
|
|
|
|
|
height: 0,
|
|
|
|
|
};
|
|
|
|
|
|
2021-01-13 23:16:02 -08:00
|
|
|
imp.popover.set_pointing_to(&rect);
|
|
|
|
|
imp.popover.popup();
|
2020-12-26 02:05:34 -08:00
|
|
|
}
|
|
|
|
|
}
|
2021-08-17 20:37:04 -07:00
|
|
|
|
|
|
|
|
async fn set_wallpaper(uri: String) {
|
2021-09-22 22:57:54 -07:00
|
|
|
if let Err(e) = wallpaper::set_from_uri(
|
2021-08-17 20:37:04 -07:00
|
|
|
&WindowIdentifier::default(),
|
|
|
|
|
&uri,
|
2021-09-29 00:42:53 -07:00
|
|
|
true,
|
2021-08-17 20:37:04 -07:00
|
|
|
wallpaper::SetOn::Background,
|
|
|
|
|
)
|
|
|
|
|
.await
|
|
|
|
|
{
|
2021-09-22 22:57:54 -07:00
|
|
|
log::error!(
|
2021-08-17 20:37:04 -07:00
|
|
|
"Failed to set the wallpaper using the freedesktop portal: {}",
|
|
|
|
|
e
|
2021-09-22 22:57:54 -07:00
|
|
|
);
|
2021-08-17 20:37:04 -07:00
|
|
|
}
|
|
|
|
|
}
|