// Copyright (c) 2023-2025 Sophie Herold
// Copyright (c) 2023 Julian Hofer
// Copyright (c) 2025 Mukul Joshi
//
// 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
//! The print layout widget that also coordinates the print process
use std::cell::{Cell, OnceCell, RefCell};
use std::rc::Rc;
use adw::prelude::*;
use adw::subclass::prelude::*;
use glib::Properties;
use gtk::CompositeTemplate;
use crate::decoder::{self};
use crate::deps::*;
use crate::util::gettext::*;
use crate::widgets::{LpImage, LpPrintPreview};
#[derive(Default, Clone, Copy)]
struct PageAlignment {
horizontal: HAlignment,
vertical: VAlignment,
}
#[derive(Default, Clone, Copy)]
enum HAlignment {
Left,
#[default]
Center,
Right,
}
#[derive(Default, Clone, Copy)]
enum VAlignment {
Top,
#[default]
Middle,
Bottom,
}
/// Scope guard for non user ui changes
///
/// Creates a context in which other signals know that changes are not user
/// input. This avoids things like loops between value change signals.
#[derive(Default, Clone, Debug)]
struct UiUpdates {
disabled: Rc| >,
}
impl UiUpdates {
pub fn disable(&self) -> Self {
self.disabled.set(self.disabled.get().saturating_add(1));
self.clone()
}
}
impl Drop for UiUpdates {
fn drop(&mut self) {
self.disabled.set(self.disabled.get().saturating_sub(1));
}
}
impl From<&str> for PageAlignment {
fn from(s: &str) -> Self {
let alignment = match s {
"top" => (HAlignment::Center, VAlignment::Top),
"center" => (HAlignment::Center, VAlignment::Middle),
"bottom" => (HAlignment::Center, VAlignment::Bottom),
"left" => (HAlignment::Left, VAlignment::Middle),
"right" => (HAlignment::Right, VAlignment::Middle),
pos => {
tracing::error!("Unknown alignment '{pos}'");
(HAlignment::default(), VAlignment::default())
}
};
Self::from(alignment)
}
}
impl From<(HAlignment, VAlignment)> for PageAlignment {
fn from((horizontal, vertical): (HAlignment, VAlignment)) -> Self {
Self {
horizontal,
vertical,
}
}
}
impl std::fmt::Display for PageAlignment {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
let s = match (self.horizontal, self.vertical) {
(HAlignment::Center, VAlignment::Top) => gettext("Top"),
(HAlignment::Center, VAlignment::Middle) => gettext("Center"),
(HAlignment::Center, VAlignment::Bottom) => gettext("Bottom"),
(HAlignment::Left, VAlignment::Middle) => gettext("Left"),
(HAlignment::Right, VAlignment::Middle) => gettext("Right"),
_ => String::from("Unsupported (Error)"),
};
f.write_str(&s)
}
}
#[derive(Default, Clone, Copy)]
enum Unit {
Centimeter,
Inch,
#[default]
Pixel,
Percent,
}
impl> From for Unit {
fn from(s: T) -> Self {
match s.as_ref() {
"cm" => Self::Centimeter,
"in" => Self::Inch,
"px" => Self::Pixel,
"%" => Self::Percent,
unit => {
tracing::error!("Unknown unit '{unit}'");
Self::default()
}
}
}
}
impl std::fmt::Display for Unit {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
let s = match self {
// Translators: Shorthand for centimeter
Self::Centimeter => gettext("cm"),
// Translators: Shorthand for inch
Self::Inch => gettext("in"),
// Translators: Shorthand for pixel
Self::Pixel => gettext("px"),
Self::Percent => String::from("%"),
};
f.write_str(&s)
}
}
impl Unit {
fn factor(&self, dpi: f64, total_size: f64) -> f64 {
match self {
Self::Centimeter => 2.54 / dpi,
Self::Inch => 1. / dpi,
Self::Pixel => 1.,
Self::Percent => 100. / total_size,
}
}
fn digits(&self) -> u32 {
match self {
Self::Centimeter => 2,
Self::Inch => 2,
Self::Pixel => 0,
Self::Percent => 2,
}
}
fn ceil(&self, num: f64) -> f64 {
let f = 10_f64.powi(self.digits() as i32);
(num * f).ceil() / f
}
fn floor(&self, num: f64) -> f64 {
let f = 10_f64.powi(self.digits() as i32);
(num * f).floor() / f
}
fn round(&self, num: f64) -> f64 {
let f = 10_f64.powi(self.digits() as i32);
(num * f).round() / f
}
fn expression() -> gtk::ClosureExpression {
gtk::ClosureExpression::new::(
[] as [gtk::Expression; 0],
glib::closure!(|s: gtk::StringObject| Self::from(s.string().as_str()).to_string()),
)
}
}
mod imp {
use super::*;
#[derive(Debug, Default, CompositeTemplate, Properties)]
#[template(file = "print.ui")]
#[properties(wrapper_type = super::LpPrint)]
pub struct LpPrint {
#[template_child]
pub(super) title: TemplateChild,
#[template_child]
pub(super) preview: TemplateChild,
// ListBox entries
#[template_child]
pub(super) alignment: TemplateChild,
#[template_child]
pub(super) margin_unit: TemplateChild,
#[property(get, set)]
pub(super) margin_unit_factor: Cell,
#[template_child]
pub(super) margin_horizontal: TemplateChild,
#[template_child]
pub(super) margin_vertical: TemplateChild,
#[template_child]
pub(super) size_unit: TemplateChild,
#[property(get, set)]
pub(super) width_unit_factor: Cell,
#[property(get, set)]
pub(super) height_unit_factor: Cell,
#[template_child]
pub(super) fill_space: TemplateChild,
#[template_child]
pub(super) width: TemplateChild,
#[template_child]
pub(super) height: TemplateChild,
#[property(get, set, construct_only)]
pub(super) image: OnceCell,
#[property(get, set, construct_only)]
pub(super) preview_image: OnceCell,
#[property(get, set, construct_only)]
pub(super) parent_window: OnceCell,
#[property(get, set, construct_only)]
pub(super) print_dialog: OnceCell,
#[property(get, set, construct_only)]
pub(super) print_setup: OnceCell,
#[property(get, set=Self::set_orientation)]
pub(super) orientation: RefCell,
pub(super) ui_updates: UiUpdates,
pub(super) previous_width: Cell |