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::subclass::widget::WidgetImplExt;
|
|
|
|
|
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;
|
|
|
|
|
use ashpd::desktop::wallpaper::{SetOn, WallpaperOptions, WallpaperProxy};
|
|
|
|
|
use ashpd::{BasicResponse as Basic, RequestProxy, Response, 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};
|
|
|
|
|
|
|
|
|
|
mod imp {
|
|
|
|
|
use super::*;
|
|
|
|
|
use glib::subclass;
|
|
|
|
|
|
|
|
|
|
#[derive(Debug, CompositeTemplate)]
|
2021-01-30 15:52:32 -08:00
|
|
|
#[template(resource = "/org/gnome/ImageViewer/gtk/image_view.ui")]
|
2020-12-26 02:05:34 -08:00
|
|
|
pub struct IvImageView {
|
|
|
|
|
#[template_child]
|
|
|
|
|
pub headerbar: TemplateChild<gtk::HeaderBar>,
|
|
|
|
|
#[template_child]
|
|
|
|
|
pub menu_button: TemplateChild<gtk::MenuButton>,
|
|
|
|
|
#[template_child]
|
|
|
|
|
pub picture: TemplateChild<gtk::Picture>,
|
|
|
|
|
#[template_child]
|
|
|
|
|
pub popover: TemplateChild<gtk::PopoverMenu>,
|
|
|
|
|
#[template_child]
|
|
|
|
|
pub click_gesture: TemplateChild<gtk::GestureClick>,
|
|
|
|
|
#[template_child]
|
|
|
|
|
pub press_gesture: TemplateChild<gtk::GestureLongPress>,
|
|
|
|
|
|
|
|
|
|
pub connection: zbus::Connection,
|
|
|
|
|
// Cell<T> allows for interior mutability of primitive types.
|
|
|
|
|
pub header_visible: Cell<bool>,
|
|
|
|
|
// RefCell<T> does the same for non-primitive types.
|
|
|
|
|
pub menu_model: RefCell<Option<gio::MenuModel>>,
|
|
|
|
|
pub popover_menu_model: RefCell<Option<gio::MenuModel>>,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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;
|
2021-01-30 15:52:32 -08:00
|
|
|
type Interfaces = ();
|
2020-12-26 02:05:34 -08:00
|
|
|
type Instance = subclass::simple::InstanceStruct<Self>;
|
|
|
|
|
type Class = subclass::simple::ClassStruct<Self>;
|
|
|
|
|
|
|
|
|
|
glib::object_subclass!();
|
|
|
|
|
|
|
|
|
|
fn new() -> Self {
|
|
|
|
|
let connection =
|
|
|
|
|
zbus::Connection::new_session().expect("Could not create zbus session");
|
|
|
|
|
|
|
|
|
|
Self {
|
|
|
|
|
headerbar: TemplateChild::default(),
|
|
|
|
|
menu_button: TemplateChild::default(),
|
|
|
|
|
picture: TemplateChild::default(),
|
|
|
|
|
popover: TemplateChild::default(),
|
|
|
|
|
click_gesture: TemplateChild::default(),
|
|
|
|
|
press_gesture: TemplateChild::default(),
|
|
|
|
|
connection,
|
|
|
|
|
header_visible: Cell::new(false),
|
|
|
|
|
menu_model: RefCell::default(),
|
|
|
|
|
popover_menu_model: RefCell::default(),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn class_init(klass: &mut Self::Class) {
|
2021-01-30 15:52:32 -08:00
|
|
|
Self::bind_template(klass);
|
2020-12-26 02:05:34 -08:00
|
|
|
}
|
2021-01-13 23:05:45 -08:00
|
|
|
|
|
|
|
|
fn instance_init(obj: &glib::subclass::InitializingObject<Self::Type>) {
|
|
|
|
|
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![
|
|
|
|
|
glib::ParamSpec::boolean(
|
|
|
|
|
"header-visible",
|
|
|
|
|
"Header visible",
|
|
|
|
|
"Whether or not the headerbar is visible",
|
|
|
|
|
false,
|
|
|
|
|
glib::ParamFlags::READWRITE,
|
|
|
|
|
),
|
|
|
|
|
glib::ParamSpec::object(
|
|
|
|
|
"primary-menu-model",
|
|
|
|
|
"Primary Menu Model",
|
|
|
|
|
"The menu model for the menu button",
|
|
|
|
|
gio::MenuModel::static_type(),
|
|
|
|
|
glib::ParamFlags::READWRITE,
|
|
|
|
|
),
|
|
|
|
|
glib::ParamSpec::object(
|
|
|
|
|
"popover-menu-model",
|
|
|
|
|
"Popover Menu Model",
|
|
|
|
|
"The menu model for the menu button",
|
|
|
|
|
gio::MenuModel::static_type(),
|
|
|
|
|
glib::ParamFlags::READWRITE,
|
|
|
|
|
),
|
|
|
|
|
]
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
PROPERTIES.as_ref()
|
2020-12-26 02:05:34 -08:00
|
|
|
}
|
|
|
|
|
|
2021-02-06 20:59:17 +01:00
|
|
|
fn get_property(
|
|
|
|
|
&self,
|
|
|
|
|
_obj: &Self::Type,
|
|
|
|
|
_id: usize,
|
|
|
|
|
psec: &glib::ParamSpec,
|
|
|
|
|
) -> glib::Value {
|
2021-01-30 15:52:32 -08:00
|
|
|
match psec.get_name() {
|
|
|
|
|
"header-visible" => self.header_visible.get().to_value(),
|
|
|
|
|
"primary-menu-model" => self.menu_model.borrow().to_value(),
|
|
|
|
|
"popover-menu-model" => self.popover_menu_model.borrow().to_value(),
|
2020-12-26 02:05:34 -08:00
|
|
|
_ => unimplemented!(),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2021-02-06 20:59:17 +01:00
|
|
|
fn set_property(
|
|
|
|
|
&self,
|
|
|
|
|
_obj: &Self::Type,
|
|
|
|
|
_id: usize,
|
|
|
|
|
value: &glib::Value,
|
|
|
|
|
psec: &glib::ParamSpec,
|
|
|
|
|
) {
|
2021-01-30 15:52:32 -08:00
|
|
|
match psec.get_name() {
|
|
|
|
|
"header-visible" => {
|
2020-12-26 02:05:34 -08:00
|
|
|
self.header_visible
|
|
|
|
|
.set(value.get().unwrap().unwrap_or_default());
|
|
|
|
|
}
|
2021-01-30 15:52:32 -08:00
|
|
|
"primary-menu-model" => {
|
2020-12-26 02:05:34 -08:00
|
|
|
let model: Option<gio::MenuModel> = value.get().unwrap();
|
2021-01-13 23:16:02 -08:00
|
|
|
self.menu_button.set_menu_model(model.as_ref());
|
2020-12-26 02:05:34 -08:00
|
|
|
*self.menu_model.borrow_mut() = model;
|
|
|
|
|
}
|
2021-01-30 15:52:32 -08:00
|
|
|
"popover-menu-model" => {
|
2020-12-26 02:05:34 -08:00
|
|
|
let model: Option<gio::MenuModel> = value.get().unwrap();
|
2021-01-13 23:16:02 -08:00
|
|
|
self.popover.set_menu_model(model.as_ref());
|
2020-12-26 02:05:34 -08:00
|
|
|
*self.popover_menu_model.borrow_mut() = model;
|
|
|
|
|
}
|
|
|
|
|
_ => 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
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl WidgetImpl for IvImageView {
|
|
|
|
|
fn size_allocate(&self, widget: &Self::Type, width: i32, height: i32, baseline: i32) {
|
|
|
|
|
self.parent_size_allocate(widget, width, height, baseline);
|
2021-01-13 23:16:02 -08:00
|
|
|
self.popover.present();
|
2020-12-26 02:05:34 -08:00
|
|
|
}
|
|
|
|
|
}
|
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 {
|
|
|
|
|
pub fn set_image_from_file(&self, file: &gio::File) {
|
|
|
|
|
let imp = imp::IvImageView::from_instance(&self);
|
|
|
|
|
|
2021-01-13 23:16:02 -08:00
|
|
|
imp.picture.set_file(Some(file));
|
2020-12-26 02:05:34 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn set_wallpaper(&self) -> anyhow::Result<()> {
|
|
|
|
|
let imp = imp::IvImageView::from_instance(&self);
|
|
|
|
|
|
|
|
|
|
let wallpaper = self.uri().context("No URI for current file")?;
|
|
|
|
|
let wallpaper_proxy = WallpaperProxy::new(&imp.connection)?;
|
|
|
|
|
|
|
|
|
|
let request_handle = wallpaper_proxy.set_wallpaper_uri(
|
|
|
|
|
WindowIdentifier::default(),
|
|
|
|
|
&wallpaper,
|
|
|
|
|
WallpaperOptions::default().set_on(SetOn::Background),
|
|
|
|
|
)?;
|
|
|
|
|
|
|
|
|
|
let request = RequestProxy::new(&imp.connection, &request_handle)?;
|
|
|
|
|
request.on_response(|response: Response<Basic>| {
|
|
|
|
|
log::debug!("Response is OK: {}", response.is_ok());
|
|
|
|
|
})?;
|
|
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn print(&self) -> anyhow::Result<()> {
|
|
|
|
|
let imp = imp::IvImageView::from_instance(&self);
|
|
|
|
|
|
2021-01-13 23:16:02 -08:00
|
|
|
let file = imp.picture.get_file().context("No file to print")?;
|
2020-12-26 02:05:34 -08:00
|
|
|
let operation = gtk::PrintOperation::new();
|
|
|
|
|
let path = file.get_path().context("No path for current file")?;
|
|
|
|
|
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);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
operation.connect_draw_page(clone!(@weak pb => move |op, ctx, _| {
|
|
|
|
|
let cr = ctx.get_cairo_context().expect("No cairo context for print context");
|
|
|
|
|
cr.set_source_pixbuf(&pb, 0.0, 0.0);
|
|
|
|
|
cr.paint();
|
|
|
|
|
}));
|
|
|
|
|
|
|
|
|
|
log::debug!("Running print operation...");
|
|
|
|
|
let root = self.get_root().context("Could not get root for widget")?;
|
|
|
|
|
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-01-13 23:16:02 -08:00
|
|
|
let file = imp.picture.get_file()?;
|
2020-12-26 02:05:34 -08:00
|
|
|
Some(file.get_uri().to_string())
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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
|
|
|
}
|
|
|
|
|
}
|