// Copyright (c) 2022-2023 Sophie Herold
// Copyright (c) 2022 Christopher Davis
//
// 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 .
//
// SPDX-License-Identifier: GPL-3.0-or-later
//! A widget that shows the image, a loading screen, or an error page
//!
//! This widget also handles showing the context menu.
use crate::deps::*;
use crate::widgets::LpImage;
use crate::util::spawn;
use adw::subclass::prelude::*;
use glib::clone;
use gtk::prelude::*;
use gtk::CompositeTemplate;
use once_cell::sync::Lazy;
mod imp {
use super::*;
#[derive(Debug, Default, CompositeTemplate)]
#[template(file = "../../data/gtk/image_page.ui")]
pub struct LpImagePage {
#[template_child]
pub(super) stack: TemplateChild,
#[template_child]
pub(super) spinner: TemplateChild,
#[template_child]
pub(super) error_page: TemplateChild,
#[template_child]
pub(super) image_stack_page: TemplateChild,
#[template_child]
pub(super) scrolled_window: TemplateChild,
#[template_child]
pub(super) image: TemplateChild,
#[template_child]
pub(super) popover: TemplateChild,
#[template_child]
pub(super) right_click_gesture: TemplateChild,
#[template_child]
pub(super) press_gesture: TemplateChild,
}
#[glib::object_subclass]
impl ObjectSubclass for LpImagePage {
const NAME: &'static str = "LpImagePage";
type Type = super::LpImagePage;
type ParentType = adw::Bin;
fn class_init(klass: &mut Self::Class) {
Self::bind_template(klass);
}
fn instance_init(obj: &glib::subclass::InitializingObject) {
obj.init_template();
}
}
impl ObjectImpl for LpImagePage {
fn properties() -> &'static [glib::ParamSpec] {
static PROPERTIES: Lazy> = Lazy::new(|| {
vec![glib::ParamSpecObject::builder::("image")
.read_only()
.build()]
});
PROPERTIES.as_ref()
}
fn property(&self, _id: usize, pspec: &glib::ParamSpec) -> glib::Value {
let obj = self.obj();
match pspec.name() {
"image" => obj.image().to_value(),
name => unimplemented!("property {name}"),
}
}
fn constructed(&self) {
let obj = self.obj();
self.parent_constructed();
self.right_click_gesture
.connect_pressed(clone!(@weak obj => move |gesture, _, x, y| {
obj.show_popover_at(x, y);
gesture.set_state(gtk::EventSequenceState::Claimed);
}));
self.press_gesture
.connect_pressed(clone!(@weak obj => move |gesture, x, y| {
log::debug!("Long press triggered");
obj.show_popover_at(x, y);
gesture.set_state(gtk::EventSequenceState::Claimed);
}));
obj.image().connect_notify_local(
Some("is-loaded"),
clone!(@weak obj => move |_,_| {
if obj.image().is_loaded() {
obj.imp()
.stack
.set_visible_child(&*obj.imp().image_stack_page);
}
}),
);
obj.image().connect_notify_local(
Some("error"),
clone!(@weak obj => move |_,_| {
if obj.image().error().is_some() {
obj.imp().stack.set_visible_child(&*obj.imp().error_page);
}
}),
);
// Do not waste CPU on spinner if it is not visible
self.spinner.connect_map(|s| s.start());
self.spinner.connect_unmap(|s| s.stop());
}
}
impl WidgetImpl for LpImagePage {}
impl BinImpl for LpImagePage {}
}
glib::wrapper! {
pub struct LpImagePage(ObjectSubclass)
@extends gtk::Widget, adw::Bin,
@implements gtk::Buildable, gtk::ConstraintTarget, gtk::Orientable;
}
impl LpImagePage {
pub fn from_file(file: &gio::File) -> Self {
let obj = glib::Object::new::();
obj.imp().image.set_file(file);
spawn(clone!(@weak obj, @strong file => async move {
let imp = obj.imp();
imp.image.load(&file).await;
}));
obj
}
pub fn file(&self) -> gio::File {
self.image().file().unwrap()
}
pub fn scrolled_window(&self) -> gtk::ScrolledWindow {
self.imp().scrolled_window.clone()
}
pub fn image(&self) -> LpImage {
self.imp().image.get()
}
pub fn content_provider(&self) -> Option {
self.imp().image.content_provider()
}
pub fn show_popover_at(&self, x: f64, y: f64) {
let imp = self.imp();
let rect = gdk::Rectangle::new(x as i32, y as i32, 0, 0);
imp.popover.set_pointing_to(Some(&rect));
imp.popover.popup();
}
}