2022-01-19 23:11:38 -08:00
|
|
|
// image_page.rs
|
|
|
|
|
//
|
|
|
|
|
// Copyright 2022 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 crate::deps::*;
|
2023-01-23 01:10:15 +01:00
|
|
|
use crate::widgets::LpImage;
|
2022-01-19 23:11:38 -08:00
|
|
|
|
|
|
|
|
use adw::subclass::prelude::*;
|
|
|
|
|
use glib::clone;
|
|
|
|
|
use gtk::prelude::*;
|
|
|
|
|
use gtk::CompositeTemplate;
|
2022-08-03 19:32:02 -04:00
|
|
|
use gtk_macros::spawn;
|
2023-01-23 01:10:15 +01:00
|
|
|
use once_cell::sync::Lazy;
|
2022-08-03 19:32:02 -04:00
|
|
|
|
2022-11-16 20:14:39 +01:00
|
|
|
use std::cell::RefCell;
|
2023-01-23 01:10:15 +01:00
|
|
|
use std::path::{Path, PathBuf};
|
2022-01-19 23:11:38 -08:00
|
|
|
|
|
|
|
|
mod imp {
|
|
|
|
|
use super::*;
|
|
|
|
|
|
|
|
|
|
#[derive(Debug, Default, CompositeTemplate)]
|
2023-01-23 01:10:15 +01:00
|
|
|
#[template(file = "../../data/gtk/image_page.ui")]
|
2022-01-19 23:11:38 -08:00
|
|
|
pub struct LpImagePage {
|
|
|
|
|
#[template_child]
|
|
|
|
|
pub(super) stack: TemplateChild<gtk::Stack>,
|
|
|
|
|
#[template_child]
|
|
|
|
|
pub(super) spinner: TemplateChild<gtk::Spinner>,
|
|
|
|
|
#[template_child]
|
|
|
|
|
pub(super) error_page: TemplateChild<adw::StatusPage>,
|
|
|
|
|
#[template_child]
|
2022-11-10 19:39:23 +01:00
|
|
|
pub(super) scrolled_window: TemplateChild<gtk::ScrolledWindow>,
|
|
|
|
|
#[template_child]
|
2022-01-19 23:11:38 -08:00
|
|
|
pub(super) image: TemplateChild<LpImage>,
|
|
|
|
|
#[template_child]
|
|
|
|
|
pub(super) popover: TemplateChild<gtk::PopoverMenu>,
|
|
|
|
|
#[template_child]
|
2023-02-06 18:40:32 +01:00
|
|
|
pub(super) right_click_gesture: TemplateChild<gtk::GestureClick>,
|
2022-01-19 23:11:38 -08:00
|
|
|
#[template_child]
|
|
|
|
|
pub(super) press_gesture: TemplateChild<gtk::GestureLongPress>,
|
|
|
|
|
|
2022-11-16 20:14:39 +01:00
|
|
|
/// set if an error has occured and is shown on error_page
|
|
|
|
|
pub(super) error: RefCell<Option<String>>,
|
2022-01-19 23:11:38 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[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<Self>) {
|
|
|
|
|
obj.init_template();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl ObjectImpl for LpImagePage {
|
2022-11-10 19:39:23 +01:00
|
|
|
fn properties() -> &'static [glib::ParamSpec] {
|
|
|
|
|
static PROPERTIES: Lazy<Vec<glib::ParamSpec>> = Lazy::new(|| {
|
2022-11-16 20:14:39 +01:00
|
|
|
vec![
|
|
|
|
|
glib::ParamSpecObject::builder::<LpImage>("image")
|
|
|
|
|
.read_only()
|
|
|
|
|
.build(),
|
|
|
|
|
glib::ParamSpecString::builder("error").read_only().build(),
|
|
|
|
|
]
|
2022-11-10 19:39:23 +01:00
|
|
|
});
|
|
|
|
|
|
|
|
|
|
PROPERTIES.as_ref()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn property(&self, _id: usize, pspec: &glib::ParamSpec) -> glib::Value {
|
|
|
|
|
let obj = self.instance();
|
|
|
|
|
match pspec.name() {
|
|
|
|
|
"image" => obj.image().to_value(),
|
2022-11-16 20:14:39 +01:00
|
|
|
"error" => obj.error().to_value(),
|
2022-11-10 19:39:23 +01:00
|
|
|
name => unimplemented!("property {name}"),
|
|
|
|
|
}
|
|
|
|
|
}
|
2023-01-23 01:10:15 +01:00
|
|
|
|
2022-11-02 21:11:43 +01:00
|
|
|
fn constructed(&self) {
|
|
|
|
|
let obj = self.instance();
|
|
|
|
|
|
|
|
|
|
self.parent_constructed();
|
2022-01-19 23:11:38 -08:00
|
|
|
|
2023-02-06 18:40:32 +01:00
|
|
|
self.right_click_gesture
|
2022-02-05 16:26:15 -08:00
|
|
|
.connect_pressed(clone!(@weak obj => move |gesture, _, x, y| {
|
2022-01-19 23:11:38 -08:00
|
|
|
obj.show_popover_at(x, y);
|
2022-02-05 16:26:15 -08:00
|
|
|
gesture.set_state(gtk::EventSequenceState::Claimed);
|
2022-01-19 23:11:38 -08:00
|
|
|
}));
|
|
|
|
|
|
|
|
|
|
self.press_gesture
|
2022-02-05 16:26:15 -08:00
|
|
|
.connect_pressed(clone!(@weak obj => move |gesture, x, y| {
|
|
|
|
|
log::debug!("Long press triggered");
|
2022-01-19 23:11:38 -08:00
|
|
|
obj.show_popover_at(x, y);
|
2022-02-05 16:26:15 -08:00
|
|
|
gesture.set_state(gtk::EventSequenceState::Claimed);
|
2022-01-19 23:11:38 -08:00
|
|
|
}));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl WidgetImpl for LpImagePage {}
|
|
|
|
|
impl BinImpl for LpImagePage {}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
glib::wrapper! {
|
|
|
|
|
pub struct LpImagePage(ObjectSubclass<imp::LpImagePage>)
|
|
|
|
|
@extends gtk::Widget, adw::Bin,
|
|
|
|
|
@implements gtk::Buildable, gtk::ConstraintTarget, gtk::Orientable;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl LpImagePage {
|
2023-01-23 01:10:15 +01:00
|
|
|
pub fn from_path(path: &Path) -> Self {
|
2022-11-02 21:11:43 +01:00
|
|
|
let obj = glib::Object::new::<Self>(&[]);
|
2023-01-23 01:10:15 +01:00
|
|
|
let path = path.to_path_buf();
|
|
|
|
|
let file = gio::File::for_path(&path);
|
|
|
|
|
|
|
|
|
|
obj.imp().image.set_file(&file);
|
2022-01-19 23:11:38 -08:00
|
|
|
|
|
|
|
|
// This doesn't work properly for items not explicitly selected
|
|
|
|
|
// via the file chooser portal. I'm not sure how to make this work.
|
|
|
|
|
gtk::RecentManager::default().add_item(&file.uri());
|
|
|
|
|
|
2023-01-23 01:10:15 +01:00
|
|
|
spawn!(clone!(@weak obj, @strong path => async move {
|
2022-01-19 23:11:38 -08:00
|
|
|
let imp = obj.imp();
|
2023-01-23 01:10:15 +01:00
|
|
|
match imp.image.load(&path).await {
|
2022-11-16 20:14:39 +01:00
|
|
|
Ok(()) => {
|
2022-11-10 19:39:23 +01:00
|
|
|
imp.stack.set_visible_child(&*imp.scrolled_window);
|
2022-01-19 23:11:38 -08:00
|
|
|
imp.spinner.set_spinning(false);
|
|
|
|
|
},
|
|
|
|
|
Err(e) => {
|
2022-11-16 20:14:39 +01:00
|
|
|
imp.error.replace(Some(e.to_string()));
|
2022-01-19 23:11:38 -08:00
|
|
|
imp.stack.set_visible_child(&*imp.error_page);
|
2022-11-16 20:14:39 +01:00
|
|
|
obj.notify("error");
|
2022-01-19 23:11:38 -08:00
|
|
|
imp.spinner.set_spinning(false);
|
|
|
|
|
log::error!("Could not load image: {e}");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}));
|
|
|
|
|
|
|
|
|
|
obj
|
|
|
|
|
}
|
|
|
|
|
|
2023-01-23 01:10:15 +01:00
|
|
|
pub fn path(&self) -> PathBuf {
|
|
|
|
|
self.image().path().unwrap()
|
2022-01-19 23:11:38 -08:00
|
|
|
}
|
|
|
|
|
|
2022-11-03 17:40:06 +01:00
|
|
|
pub fn image(&self) -> LpImage {
|
|
|
|
|
self.imp().image.get()
|
|
|
|
|
}
|
|
|
|
|
|
2022-01-19 23:11:38 -08:00
|
|
|
pub fn texture(&self) -> Option<gdk::Texture> {
|
|
|
|
|
self.imp().image.texture()
|
|
|
|
|
}
|
|
|
|
|
|
2022-11-16 20:14:39 +01:00
|
|
|
pub fn error(&self) -> Option<String> {
|
|
|
|
|
self.imp().error.borrow().clone()
|
|
|
|
|
}
|
|
|
|
|
|
2022-11-28 18:27:40 +01:00
|
|
|
pub fn content_provider(&self) -> Option<gdk::ContentProvider> {
|
2022-01-19 23:11:38 -08:00
|
|
|
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();
|
|
|
|
|
}
|
|
|
|
|
}
|