2024-05-09 23:27:45 +02:00
|
|
|
// Copyright (c) 2022-2024 Sophie Herold
|
2024-07-17 19:06:34 +02:00
|
|
|
// Copyright (c) 2022, 2024 Christopher Davis
|
2022-01-19 23:11:38 -08:00
|
|
|
//
|
|
|
|
|
// 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
|
|
|
|
|
|
2023-03-06 22:59:40 +01:00
|
|
|
//! A widget that shows the image, a loading screen, or an error page
|
|
|
|
|
//!
|
|
|
|
|
//! This widget also handles showing the context menu.
|
|
|
|
|
|
2022-01-19 23:11:38 -08:00
|
|
|
use adw::subclass::prelude::*;
|
2023-11-29 21:15:36 +01:00
|
|
|
use glib::{clone, Properties};
|
2022-01-19 23:11:38 -08:00
|
|
|
use gtk::prelude::*;
|
|
|
|
|
use gtk::CompositeTemplate;
|
2022-08-03 19:32:02 -04:00
|
|
|
|
2024-05-09 01:30:01 +02:00
|
|
|
use super::LpErrorDetails;
|
2024-07-19 23:19:34 +02:00
|
|
|
use crate::decoder::DecoderError;
|
2023-12-06 23:45:02 +01:00
|
|
|
use crate::deps::*;
|
2024-05-09 01:30:01 +02:00
|
|
|
use crate::util::gettext::*;
|
2023-12-06 23:45:02 +01:00
|
|
|
use crate::widgets::LpImage;
|
|
|
|
|
|
2022-01-19 23:11:38 -08:00
|
|
|
mod imp {
|
2024-07-29 15:16:06 +02:00
|
|
|
use std::time::Duration;
|
|
|
|
|
|
2022-01-19 23:11:38 -08:00
|
|
|
use super::*;
|
|
|
|
|
|
2023-11-29 19:19:49 +01:00
|
|
|
#[derive(Debug, Default, CompositeTemplate, Properties)]
|
|
|
|
|
#[properties(wrapper_type = super::LpImagePage)]
|
2023-11-28 21:50:51 +01:00
|
|
|
#[template(file = "image_page.ui")]
|
2022-01-19 23:11:38 -08:00
|
|
|
pub struct LpImagePage {
|
|
|
|
|
#[template_child]
|
|
|
|
|
pub(super) stack: TemplateChild<gtk::Stack>,
|
|
|
|
|
#[template_child]
|
2023-04-26 18:15:36 +02:00
|
|
|
pub(super) spinner_page: TemplateChild<gtk::Widget>,
|
2024-07-29 15:16:06 +02:00
|
|
|
#[template_child]
|
|
|
|
|
pub(super) spinner_revealer: TemplateChild<gtk::Revealer>,
|
2024-05-09 01:30:01 +02:00
|
|
|
|
2022-01-19 23:11:38 -08:00
|
|
|
#[template_child]
|
|
|
|
|
pub(super) error_page: TemplateChild<adw::StatusPage>,
|
2024-05-09 01:30:01 +02:00
|
|
|
#[template_child]
|
|
|
|
|
pub(super) error_more_info: TemplateChild<gtk::Button>,
|
|
|
|
|
|
2022-01-19 23:11:38 -08:00
|
|
|
#[template_child]
|
2023-03-18 01:22:30 +01:00
|
|
|
pub(super) image_stack_page: TemplateChild<gtk::Widget>,
|
2022-11-10 19:39:23 +01:00
|
|
|
#[template_child]
|
2024-07-06 13:52:59 -04:00
|
|
|
pub(super) spinner: TemplateChild<gtk::Widget>,
|
2023-04-26 18:15:36 +02:00
|
|
|
#[template_child]
|
2023-03-21 21:50:54 +01:00
|
|
|
pub(super) scrolled_window: TemplateChild<gtk::ScrolledWindow>,
|
|
|
|
|
#[template_child]
|
2023-11-29 19:19:49 +01:00
|
|
|
#[property(type = LpImage, get = Self::image)]
|
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>,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[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] {
|
2023-11-29 19:19:49 +01:00
|
|
|
Self::derived_properties()
|
2022-11-10 19:39:23 +01:00
|
|
|
}
|
|
|
|
|
|
2023-11-29 19:19:49 +01:00
|
|
|
fn property(&self, id: usize, pspec: &glib::ParamSpec) -> glib::Value {
|
|
|
|
|
self.derived_property(id, pspec)
|
2022-11-10 19:39:23 +01:00
|
|
|
}
|
2023-01-23 01:10:15 +01:00
|
|
|
|
2022-11-02 21:11:43 +01:00
|
|
|
fn constructed(&self) {
|
2023-02-12 02:07:42 +01:00
|
|
|
let obj = self.obj();
|
2022-11-02 21:11:43 +01:00
|
|
|
|
|
|
|
|
self.parent_constructed();
|
2022-01-19 23:11:38 -08:00
|
|
|
|
2024-06-18 00:37:38 +02:00
|
|
|
self.right_click_gesture.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);
|
2024-06-18 00:37:38 +02:00
|
|
|
}
|
|
|
|
|
));
|
2022-01-19 23:11:38 -08:00
|
|
|
|
2024-06-18 00:37:38 +02:00
|
|
|
self.press_gesture.connect_pressed(clone!(
|
|
|
|
|
#[weak]
|
|
|
|
|
obj,
|
|
|
|
|
move |gesture, x, y| {
|
2022-02-05 16:26:15 -08:00
|
|
|
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);
|
2024-06-18 00:37:38 +02:00
|
|
|
}
|
|
|
|
|
));
|
decoder: Use image-rs, libheif-rs, librsvg
This adds proper support for a lot of image formats including SVGs.
It will be the basis for many features to come. The technical core
is an asynchronous renderer that can arrange textures in smaller tiles
which might be generated on demand.
Closes #34, #40, #41, #49
Basis for fixing the following issues #7, #27, #36, #43, #68, #71
2023-03-04 03:14:52 +01:00
|
|
|
|
2024-06-18 00:37:38 +02:00
|
|
|
self.stack.connect_visible_child_notify(clone!(
|
|
|
|
|
#[weak]
|
|
|
|
|
obj,
|
|
|
|
|
move |stack| {
|
2023-04-26 18:15:36 +02:00
|
|
|
let imp = obj.imp();
|
|
|
|
|
let spinner_visible =
|
|
|
|
|
stack.visible_child().as_ref() == Some(imp.spinner_page.upcast_ref());
|
|
|
|
|
// Hide spinner for crossfading into other content since it does not look great
|
|
|
|
|
imp.spinner.set_visible(spinner_visible);
|
2024-06-18 00:37:38 +02:00
|
|
|
}
|
|
|
|
|
));
|
2023-04-26 18:15:36 +02:00
|
|
|
|
2024-06-18 00:37:38 +02:00
|
|
|
obj.image().connect_is_loaded_notify(clone!(
|
|
|
|
|
#[weak]
|
|
|
|
|
obj,
|
|
|
|
|
move |_| {
|
decoder: Use image-rs, libheif-rs, librsvg
This adds proper support for a lot of image formats including SVGs.
It will be the basis for many features to come. The technical core
is an asynchronous renderer that can arrange textures in smaller tiles
which might be generated on demand.
Closes #34, #40, #41, #49
Basis for fixing the following issues #7, #27, #36, #43, #68, #71
2023-03-04 03:14:52 +01:00
|
|
|
if obj.image().is_loaded() {
|
2023-04-26 18:15:36 +02:00
|
|
|
log::debug!("Showing image");
|
decoder: Use image-rs, libheif-rs, librsvg
This adds proper support for a lot of image formats including SVGs.
It will be the basis for many features to come. The technical core
is an asynchronous renderer that can arrange textures in smaller tiles
which might be generated on demand.
Closes #34, #40, #41, #49
Basis for fixing the following issues #7, #27, #36, #43, #68, #71
2023-03-04 03:14:52 +01:00
|
|
|
obj.imp()
|
|
|
|
|
.stack
|
2023-03-18 01:22:30 +01:00
|
|
|
.set_visible_child(&*obj.imp().image_stack_page);
|
decoder: Use image-rs, libheif-rs, librsvg
This adds proper support for a lot of image formats including SVGs.
It will be the basis for many features to come. The technical core
is an asynchronous renderer that can arrange textures in smaller tiles
which might be generated on demand.
Closes #34, #40, #41, #49
Basis for fixing the following issues #7, #27, #36, #43, #68, #71
2023-03-04 03:14:52 +01:00
|
|
|
}
|
2024-06-18 00:37:38 +02:00
|
|
|
}
|
|
|
|
|
));
|
decoder: Use image-rs, libheif-rs, librsvg
This adds proper support for a lot of image formats including SVGs.
It will be the basis for many features to come. The technical core
is an asynchronous renderer that can arrange textures in smaller tiles
which might be generated on demand.
Closes #34, #40, #41, #49
Basis for fixing the following issues #7, #27, #36, #43, #68, #71
2023-03-04 03:14:52 +01:00
|
|
|
|
2024-06-18 00:37:38 +02:00
|
|
|
obj.image().connect_error_notify(clone!(
|
|
|
|
|
#[weak]
|
|
|
|
|
obj,
|
|
|
|
|
move |_| {
|
2024-05-09 01:30:01 +02:00
|
|
|
glib::spawn_future_local(async move { obj.show_error().await });
|
2024-06-18 00:37:38 +02:00
|
|
|
}
|
|
|
|
|
));
|
2024-07-29 15:16:06 +02:00
|
|
|
|
|
|
|
|
self.spinner_revealer.connect_map(|revealer| {
|
|
|
|
|
let revealer = revealer.clone();
|
|
|
|
|
glib::timeout_add_local_once(Duration::from_millis(200), move || {
|
|
|
|
|
revealer.set_reveal_child(true);
|
|
|
|
|
});
|
|
|
|
|
});
|
2022-01-19 23:11:38 -08:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl WidgetImpl for LpImagePage {}
|
|
|
|
|
impl BinImpl for LpImagePage {}
|
2023-11-29 19:19:49 +01:00
|
|
|
|
|
|
|
|
impl LpImagePage {
|
|
|
|
|
pub fn image(&self) -> LpImage {
|
|
|
|
|
self.image.get()
|
|
|
|
|
}
|
|
|
|
|
}
|
2022-01-19 23:11:38 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
glib::wrapper! {
|
|
|
|
|
pub struct LpImagePage(ObjectSubclass<imp::LpImagePage>)
|
|
|
|
|
@extends gtk::Widget, adw::Bin,
|
|
|
|
|
@implements gtk::Buildable, gtk::ConstraintTarget, gtk::Orientable;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl LpImagePage {
|
2023-04-12 01:11:59 +02:00
|
|
|
pub fn from_file(file: &gio::File) -> Self {
|
2023-02-12 02:07:42 +01:00
|
|
|
let obj = glib::Object::new::<Self>();
|
2023-01-23 01:10:15 +01:00
|
|
|
|
2023-12-04 17:19:33 +01:00
|
|
|
obj.imp().image.init(file);
|
2022-01-19 23:11:38 -08:00
|
|
|
|
2024-06-18 00:37:38 +02:00
|
|
|
glib::spawn_future_local(clone!(
|
|
|
|
|
#[weak]
|
|
|
|
|
obj,
|
|
|
|
|
#[strong]
|
|
|
|
|
file,
|
|
|
|
|
async move {
|
|
|
|
|
let imp = obj.imp();
|
|
|
|
|
imp.image.load(&file).await;
|
|
|
|
|
}
|
|
|
|
|
));
|
2022-01-19 23:11:38 -08:00
|
|
|
|
|
|
|
|
obj
|
|
|
|
|
}
|
|
|
|
|
|
2023-04-12 01:11:59 +02:00
|
|
|
pub fn file(&self) -> gio::File {
|
|
|
|
|
self.image().file().unwrap()
|
2022-01-19 23:11:38 -08:00
|
|
|
}
|
|
|
|
|
|
2023-03-21 21:50:54 +01:00
|
|
|
pub fn scrolled_window(&self) -> gtk::ScrolledWindow {
|
|
|
|
|
self.imp().scrolled_window.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();
|
|
|
|
|
}
|
2024-05-09 01:30:01 +02:00
|
|
|
|
|
|
|
|
pub async fn show_error(&self) {
|
|
|
|
|
let imp = self.imp();
|
|
|
|
|
let image = self.image();
|
|
|
|
|
|
2024-07-19 23:19:34 +02:00
|
|
|
if image.specific_error() == DecoderError::NoLoadersConfigured {
|
|
|
|
|
// Translators: {} is replaced with a version number
|
|
|
|
|
let message = gettext_f(
|
|
|
|
|
"No image loaders available. Maybe the “glycin-loaders” package with compatibility version “{}” is not installed.",
|
|
|
|
|
[ format!("{}+", glycin::COMPAT_VERSION)]);
|
|
|
|
|
|
|
|
|
|
imp.error_page.set_description(Some(&message));
|
|
|
|
|
imp.stack.set_visible_child(&*imp.error_page);
|
|
|
|
|
imp.error_more_info.set_visible(false);
|
|
|
|
|
} else if image.specific_error() == DecoderError::UnsupportedFormat {
|
|
|
|
|
let mime_type = image.metadata().mime_type().unwrap_or_default();
|
|
|
|
|
let content_type = gio::content_type_from_mime_type(&mime_type).unwrap_or_default();
|
|
|
|
|
let description = gio::content_type_get_description(&content_type).to_string();
|
|
|
|
|
|
|
|
|
|
let message = if glycin::Loader::DEFAULT_MIME_TYPES.contains(&mime_type.as_str()) {
|
|
|
|
|
// Translators: The first occurance of {} is replace with a description of the
|
|
|
|
|
// format and the second with an id (mime-type) of the format.
|
2024-05-09 01:30:01 +02:00
|
|
|
gettext_f(
|
2024-07-19 23:19:34 +02:00
|
|
|
"The image format “{} ({})” is known but not installed.",
|
|
|
|
|
[&description, &mime_type],
|
|
|
|
|
)
|
2024-05-09 01:30:01 +02:00
|
|
|
} else {
|
2024-07-19 23:19:34 +02:00
|
|
|
// Translators: The first occurance of {} is replace with a description of the
|
|
|
|
|
// format and the second with an id (mime-type) of the format.
|
|
|
|
|
gettext_f(
|
|
|
|
|
"Unknown image format “{} ({}).”",
|
|
|
|
|
[&description, &mime_type],
|
|
|
|
|
)
|
2024-05-09 01:30:01 +02:00
|
|
|
};
|
|
|
|
|
|
2024-07-19 23:19:34 +02:00
|
|
|
if let Some(err) = image.error() {
|
|
|
|
|
imp.error_more_info.connect_clicked(glib::clone!(
|
|
|
|
|
#[weak(rename_to = obj)]
|
|
|
|
|
self,
|
|
|
|
|
move |_| {
|
2024-12-18 01:29:14 +01:00
|
|
|
LpErrorDetails::new(
|
|
|
|
|
&obj.root().unwrap(),
|
|
|
|
|
&err,
|
|
|
|
|
super::error_details::ErrorType::Loader,
|
|
|
|
|
);
|
2024-07-19 23:19:34 +02:00
|
|
|
}
|
|
|
|
|
));
|
|
|
|
|
}
|
2024-05-09 22:51:05 +02:00
|
|
|
|
2024-07-19 23:19:34 +02:00
|
|
|
imp.error_page.set_description(Some(&message));
|
2024-05-09 22:51:05 +02:00
|
|
|
imp.stack.set_visible_child(&*imp.error_page);
|
2024-05-09 01:30:01 +02:00
|
|
|
} else if let Some(err) = image.error() {
|
2024-06-18 00:37:38 +02:00
|
|
|
imp.error_more_info.connect_clicked(glib::clone!(
|
|
|
|
|
#[weak(rename_to = obj)]
|
|
|
|
|
self,
|
|
|
|
|
move |_| {
|
2024-12-18 01:29:14 +01:00
|
|
|
LpErrorDetails::new(
|
|
|
|
|
&obj.root().unwrap(),
|
|
|
|
|
&err,
|
|
|
|
|
super::error_details::ErrorType::Loader,
|
|
|
|
|
);
|
2024-06-18 00:37:38 +02:00
|
|
|
}
|
|
|
|
|
));
|
2024-05-09 01:30:01 +02:00
|
|
|
|
2024-07-26 22:54:24 +02:00
|
|
|
if image.specific_error() == DecoderError::OutOfMemory {
|
|
|
|
|
imp.error_page.set_description(Some(&gettext(
|
|
|
|
|
"There is not enough free memory available to load this image.",
|
|
|
|
|
)));
|
|
|
|
|
} else {
|
|
|
|
|
imp.error_page.set_description(Some(&gettext(
|
|
|
|
|
"Either the image file is corrupted or it contains unsupported elements.",
|
|
|
|
|
)));
|
|
|
|
|
}
|
2024-05-09 22:51:05 +02:00
|
|
|
|
|
|
|
|
imp.error_more_info.set_visible(true);
|
|
|
|
|
imp.stack.set_visible_child(&*imp.error_page);
|
2024-05-09 01:30:01 +02:00
|
|
|
}
|
|
|
|
|
}
|
2022-01-19 23:11:38 -08:00
|
|
|
}
|