2020-12-26 02:05:34 -08:00
|
|
|
// window.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 gio::prelude::*;
|
|
|
|
|
use glib::clone;
|
|
|
|
|
use glib::subclass::prelude::*;
|
|
|
|
|
use gtk::prelude::*;
|
|
|
|
|
use gtk::subclass::prelude::*;
|
|
|
|
|
use gtk::CompositeTemplate;
|
|
|
|
|
use gtk_macros::*;
|
2021-01-14 21:36:30 -08:00
|
|
|
use libadwaita::subclass::prelude::*;
|
2020-12-26 02:05:34 -08:00
|
|
|
|
|
|
|
|
use crate::config;
|
|
|
|
|
use crate::widgets::IvImageView;
|
|
|
|
|
|
|
|
|
|
mod imp {
|
|
|
|
|
use super::*;
|
|
|
|
|
|
|
|
|
|
// To use composite templates, you need
|
|
|
|
|
// to use derive macro. Derive macros generate
|
|
|
|
|
// code to e.g. implement a trait on something.
|
|
|
|
|
// In this case, code is generated for Debug output
|
|
|
|
|
// and to handle binding the template children.
|
|
|
|
|
//
|
|
|
|
|
// For this derive macro, you need to have
|
|
|
|
|
// `use gtk::CompositeTemplate` in your code.
|
|
|
|
|
#[derive(Debug, CompositeTemplate)]
|
2021-01-30 15:52:32 -08:00
|
|
|
#[template(resource = "/org/gnome/ImageViewer/gtk/window.ui")]
|
2020-12-26 02:05:34 -08:00
|
|
|
pub struct IvWindow {
|
|
|
|
|
// Template children are used with the
|
|
|
|
|
// TemplateChild<T> wrapper, where T is the
|
|
|
|
|
// object type of the template child.
|
|
|
|
|
#[template_child]
|
|
|
|
|
pub headerbar: TemplateChild<gtk::HeaderBar>,
|
|
|
|
|
#[template_child]
|
|
|
|
|
pub stack: TemplateChild<gtk::Stack>,
|
|
|
|
|
#[template_child]
|
2021-01-14 21:36:30 -08:00
|
|
|
pub status_page: TemplateChild<libadwaita::StatusPage>,
|
2020-12-26 02:05:34 -08:00
|
|
|
#[template_child]
|
|
|
|
|
pub image_view: TemplateChild<IvImageView>,
|
|
|
|
|
#[template_child]
|
|
|
|
|
pub open_gesture: TemplateChild<gtk::GestureClick>,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl ObjectSubclass for IvWindow {
|
|
|
|
|
const NAME: &'static str = "IvWindow";
|
|
|
|
|
type Type = super::IvWindow;
|
2021-01-14 21:36:30 -08:00
|
|
|
type ParentType = libadwaita::ApplicationWindow;
|
2021-01-30 15:52:32 -08:00
|
|
|
type Interfaces = ();
|
2020-12-26 02:05:34 -08:00
|
|
|
type Instance = glib::subclass::simple::InstanceStruct<Self>;
|
|
|
|
|
type Class = glib::subclass::simple::ClassStruct<Self>;
|
|
|
|
|
|
|
|
|
|
glib::object_subclass!();
|
|
|
|
|
|
|
|
|
|
fn new() -> Self {
|
|
|
|
|
Self {
|
|
|
|
|
// For the initial value, use this function.
|
|
|
|
|
headerbar: TemplateChild::default(),
|
|
|
|
|
stack: TemplateChild::default(),
|
|
|
|
|
status_page: TemplateChild::default(),
|
|
|
|
|
image_view: TemplateChild::default(),
|
|
|
|
|
open_gesture: TemplateChild::default(),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn class_init(klass: &mut Self::Class) {
|
2021-01-30 15:52:32 -08:00
|
|
|
// bind_template() is a function generated by the
|
2020-12-26 02:05:34 -08:00
|
|
|
// CompositeTemplate macro to bind all children at once.
|
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 IvWindow {
|
|
|
|
|
fn constructed(&self, obj: &Self::Type) {
|
|
|
|
|
self.parent_constructed(obj);
|
|
|
|
|
|
|
|
|
|
if config::PROFILE == ".Devel" {
|
|
|
|
|
obj.add_css_class("devel");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
obj.setup_actions();
|
|
|
|
|
|
|
|
|
|
self.status_page
|
|
|
|
|
.set_icon_name(Some(&format!("{}-symbolic", config::APP_ID)));
|
|
|
|
|
|
|
|
|
|
// Set help overlay
|
|
|
|
|
let builder = gtk::Builder::from_resource("/org/gnome/ImageViewer/gtk/help_overlay.ui");
|
|
|
|
|
let help_overlay = builder.get_object("help_overlay").unwrap();
|
|
|
|
|
obj.set_help_overlay(Some(&help_overlay));
|
|
|
|
|
|
|
|
|
|
// For callbacks, you will want to reference the GTK docs on
|
|
|
|
|
// the relevant signal to see which parameters you need.
|
|
|
|
|
// In this case, we need none to react to the gesture,
|
|
|
|
|
// so "_" is used in all 4 spots.
|
|
|
|
|
self.open_gesture
|
|
|
|
|
.connect_released(clone!(@weak obj => move |_, _, _, _| {
|
|
|
|
|
obj.pick_file();
|
|
|
|
|
}));
|
2021-03-02 16:39:03 -08:00
|
|
|
|
|
|
|
|
self.image_view.connect_dimensions_loaded(
|
|
|
|
|
clone!(@weak obj => move |_, width, height| {
|
|
|
|
|
obj.resize_from_dimensions(width, height);
|
|
|
|
|
}),
|
|
|
|
|
);
|
2020-12-26 02:05:34 -08:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl WidgetImpl for IvWindow {}
|
|
|
|
|
impl WindowImpl for IvWindow {}
|
|
|
|
|
impl ApplicationWindowImpl for IvWindow {}
|
2021-01-14 21:36:30 -08:00
|
|
|
impl AdwApplicationWindowImpl for IvWindow {}
|
2020-12-26 02:05:34 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
glib::wrapper! {
|
|
|
|
|
pub struct IvWindow(ObjectSubclass<imp::IvWindow>)
|
2021-01-14 21:36:30 -08:00
|
|
|
@extends gtk::Widget, gtk::Window, gtk::ApplicationWindow, libadwaita::ApplicationWindow,
|
2020-12-26 02:05:34 -08:00
|
|
|
@implements gio::ActionMap, gio::ActionGroup;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl IvWindow {
|
|
|
|
|
pub fn new<A: IsA<gtk::Application>>(app: &A) -> Self {
|
|
|
|
|
glib::Object::new(&[("application", app)]).expect("Failed to create IvWindow")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn setup_actions(&self) {
|
|
|
|
|
action!(
|
|
|
|
|
self,
|
|
|
|
|
"toggle-fullscreen",
|
|
|
|
|
clone!(@weak self as win => move |_, _| {
|
|
|
|
|
win.set_property_fullscreened(!win.get_property_fullscreened());
|
|
|
|
|
})
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
action!(
|
|
|
|
|
self,
|
|
|
|
|
"open",
|
|
|
|
|
clone!(@weak self as win => move |_, _| {
|
|
|
|
|
win.pick_file();
|
|
|
|
|
})
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
action!(
|
|
|
|
|
self,
|
|
|
|
|
"open-with",
|
|
|
|
|
clone!(@weak self as win => move |_, _| {
|
|
|
|
|
win.open_with();
|
|
|
|
|
})
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
action!(
|
|
|
|
|
self,
|
|
|
|
|
"set-wallpaper",
|
|
|
|
|
clone!(@weak self as win => move |_, _| {
|
|
|
|
|
win.set_wallpaper();
|
|
|
|
|
})
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
action!(
|
|
|
|
|
self,
|
|
|
|
|
"print",
|
|
|
|
|
clone!(@weak self as win => move |_, _| {
|
|
|
|
|
win.print();
|
|
|
|
|
})
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
self.set_actions_enabled(false);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn pick_file(&self) {
|
|
|
|
|
let chooser = gtk::FileChooserNative::new(
|
|
|
|
|
Some(&"Open Image".to_string()),
|
|
|
|
|
Some(self),
|
|
|
|
|
gtk::FileChooserAction::Open,
|
|
|
|
|
None,
|
|
|
|
|
None,
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
chooser.set_modal(true);
|
|
|
|
|
chooser.set_transient_for(Some(self));
|
|
|
|
|
|
|
|
|
|
let filter = gtk::FileFilter::new();
|
|
|
|
|
filter
|
|
|
|
|
.set_property("name", &String::from("Supported image files"))
|
|
|
|
|
.unwrap();
|
|
|
|
|
filter.add_mime_type("image/*");
|
|
|
|
|
chooser.add_filter(&filter);
|
|
|
|
|
|
|
|
|
|
chooser.connect_response(
|
|
|
|
|
clone!(@weak self as win, @strong chooser => move |_, resp| {
|
|
|
|
|
if resp == gtk::ResponseType::Accept {
|
|
|
|
|
if let Some(file) = chooser.get_file() {
|
|
|
|
|
win.set_image_from_file(&file);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}),
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
chooser.show();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn open_with(&self) {
|
|
|
|
|
let imp = imp::IvWindow::from_instance(self);
|
|
|
|
|
|
2021-01-13 23:16:02 -08:00
|
|
|
if let Some(uri) = imp.image_view.uri() {
|
2020-12-26 02:05:34 -08:00
|
|
|
std::process::Command::new("xdg-open")
|
|
|
|
|
.arg(uri)
|
|
|
|
|
.output()
|
|
|
|
|
.unwrap();
|
|
|
|
|
} else {
|
|
|
|
|
log::error!("No URI for current image.")
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn set_wallpaper(&self) {
|
|
|
|
|
let imp = imp::IvWindow::from_instance(self);
|
|
|
|
|
|
2021-01-13 23:16:02 -08:00
|
|
|
if let Err(e) = imp.image_view.set_wallpaper() {
|
2020-12-26 02:05:34 -08:00
|
|
|
log::error!("Failed to set wallpaper: {}", e);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn print(&self) {
|
|
|
|
|
let imp = imp::IvWindow::from_instance(self);
|
|
|
|
|
|
2021-01-13 23:16:02 -08:00
|
|
|
if let Err(e) = imp.image_view.print() {
|
2020-12-26 02:05:34 -08:00
|
|
|
log::error!("Failed to print file: {}", e);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn set_image_from_file(&self, file: &gio::File) {
|
|
|
|
|
let imp = imp::IvWindow::from_instance(self);
|
|
|
|
|
|
2021-03-02 16:39:03 -08:00
|
|
|
log::debug!("Loading file: {}", file.get_uri().to_string());
|
|
|
|
|
|
2021-01-13 23:16:02 -08:00
|
|
|
imp.image_view.set_image_from_file(file);
|
|
|
|
|
imp.stack.set_visible_child(&*imp.image_view);
|
2020-12-26 02:05:34 -08:00
|
|
|
|
|
|
|
|
if let Some(name) = file.get_basename() {
|
|
|
|
|
self.set_title(Some(
|
|
|
|
|
name.file_name().expect("Missing name").to_str().unwrap(),
|
|
|
|
|
));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
self.set_actions_enabled(true);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn set_actions_enabled(&self, enabled: bool) {
|
|
|
|
|
// Here we need to downcast because lookup_action()` returns a plain
|
|
|
|
|
// GAction, not a GSimpleAction.
|
|
|
|
|
self.lookup_action("open-with")
|
|
|
|
|
.unwrap()
|
|
|
|
|
.downcast::<gio::SimpleAction>()
|
|
|
|
|
.unwrap()
|
|
|
|
|
.set_enabled(enabled);
|
|
|
|
|
self.lookup_action("set-wallpaper")
|
|
|
|
|
.unwrap()
|
|
|
|
|
.downcast::<gio::SimpleAction>()
|
|
|
|
|
.unwrap()
|
|
|
|
|
.set_enabled(enabled);
|
|
|
|
|
self.lookup_action("toggle-fullscreen")
|
|
|
|
|
.unwrap()
|
|
|
|
|
.downcast::<gio::SimpleAction>()
|
|
|
|
|
.unwrap()
|
|
|
|
|
.set_enabled(enabled);
|
|
|
|
|
self.lookup_action("print")
|
|
|
|
|
.unwrap()
|
|
|
|
|
.downcast::<gio::SimpleAction>()
|
|
|
|
|
.unwrap()
|
|
|
|
|
.set_enabled(enabled);
|
|
|
|
|
}
|
2021-03-02 16:39:03 -08:00
|
|
|
|
|
|
|
|
// Adapted from https://gitlab.gnome.org/GNOME/eog/-/blob/master/src/eog-window.c:eog_window_obtain_desired_size
|
|
|
|
|
pub fn resize_from_dimensions(&self, img_width: i32, img_height: i32) {
|
|
|
|
|
let imp = imp::IvWindow::from_instance(self);
|
|
|
|
|
let mut final_width = img_width;
|
|
|
|
|
let mut final_height = img_height;
|
|
|
|
|
|
|
|
|
|
let header_height = imp.headerbar.get_height();
|
|
|
|
|
|
|
|
|
|
// Ensure the window surface exists
|
|
|
|
|
if !self.get_realized() {
|
|
|
|
|
self.realize();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let display = gdk::Display::get_default().unwrap();
|
|
|
|
|
let monitor = display
|
|
|
|
|
.get_monitor_at_surface(&self.get_native().unwrap().get_surface().unwrap())
|
|
|
|
|
.unwrap();
|
|
|
|
|
let monitor_geometry = monitor.get_geometry();
|
|
|
|
|
|
|
|
|
|
let monitor_width = monitor_geometry.width;
|
|
|
|
|
let monitor_height = monitor_geometry.height;
|
|
|
|
|
|
|
|
|
|
if img_width > monitor_width || img_height + header_height > monitor_height {
|
|
|
|
|
let width_factor = (monitor_width as f32 * 0.85) / img_width as f32;
|
|
|
|
|
let height_factor =
|
|
|
|
|
(monitor_height as f32 * 0.85 - header_height as f32) / img_height as f32;
|
|
|
|
|
let factor = width_factor.min(height_factor);
|
|
|
|
|
|
|
|
|
|
final_width = (final_width as f32 * factor).round() as i32;
|
|
|
|
|
final_height = (final_height as f32 * factor).round() as i32;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
self.set_default_size(final_width, final_height);
|
|
|
|
|
log::debug!("Window resized to {} x {}", final_width, final_height);
|
|
|
|
|
}
|
2020-12-26 02:05:34 -08:00
|
|
|
}
|