// Map, wardley map editor for linux
// Copyright (C) 2026 Rubén Beltrán del Río
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero 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 Affero General Public License for more details.
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see .
use gtk::prelude::*;
use relm4::Sender;
use relm4::gtk;
use crate::tr;
use crate::ui_helpers::create_switch_row;
use super::super::models::UserPreferences;
use super::super::window::PreferencesInput;
const DEFAULT_FONT_SIZE: f64 = 14.0;
pub struct EditorPage {
container: gtk::Box,
font_size_scale: gtk::Scale,
soft_wrap_switch: gtk::Switch,
custom_font_switch: gtk::Switch,
font_button: gtk::FontDialogButton,
}
impl EditorPage {
pub fn new(preferences: &UserPreferences, sender: &Sender) -> Self {
let container = gtk::Box::new(gtk::Orientation::Vertical, 20);
container.set_margin_top(20);
container.set_margin_bottom(20);
container.set_margin_start(20);
container.set_margin_end(20);
container.set_valign(gtk::Align::Start);
let (font_size_box, font_size_scale) = Self::create_font_size_section(preferences);
container.append(&font_size_box);
let (soft_wrap_switch, custom_font_switch) =
Self::create_style_section(&container, preferences);
let font_button = Self::create_font_button(preferences);
container.append(&Self::create_font_row(&font_button));
Self::connect_signals(
sender,
&font_size_scale,
&soft_wrap_switch,
&custom_font_switch,
&font_button,
);
Self {
container,
font_size_scale,
soft_wrap_switch,
custom_font_switch,
font_button,
}
}
fn create_font_size_section(preferences: &UserPreferences) -> (gtk::Box, gtk::Scale) {
let label = gtk::Label::new(Some(&tr!("preferences.editor.font_size.title")));
label.add_css_class("heading");
label.set_halign(gtk::Align::Start);
let scale = gtk::Scale::with_range(gtk::Orientation::Horizontal, 7.0, 21.0, 1.0);
scale.set_value(preferences.editor_font_size);
scale.set_draw_value(true);
scale.set_hexpand(true);
#[allow(clippy::cast_possible_truncation)]
scale.set_format_value_func(|_, value| {
#[allow(clippy::float_cmp)]
if value == DEFAULT_FONT_SIZE {
format!(
"{} ({})",
value as i32,
tr!("preferences.editor.default_font_size")
)
} else {
format!("{}", value as i32)
}
});
let vbox = gtk::Box::new(gtk::Orientation::Vertical, 10);
vbox.append(&label);
vbox.append(&scale);
(vbox, scale)
}
fn create_style_section(
container: >k::Box,
preferences: &UserPreferences,
) -> (gtk::Switch, gtk::Switch) {
let label = gtk::Label::new(Some(&tr!("preferences.editor.editor_style.title")));
label.add_css_class("heading");
label.set_halign(gtk::Align::Start);
label.set_margin_top(10);
container.append(&label);
let soft_wrap_row = create_switch_row(
&tr!("preferences.editor.editor_style.soft_wrap_lines"),
preferences.soft_wrap_lines,
);
container.append(&soft_wrap_row.0);
let custom_font_row = create_switch_row(
&tr!("preferences.editor.editor_style.use_custom_font"),
preferences.use_custom_editor_font,
);
container.append(&custom_font_row.0);
(soft_wrap_row.1, custom_font_row.1)
}
fn create_font_button(preferences: &UserPreferences) -> gtk::FontDialogButton {
let font_dialog = gtk::FontDialog::new();
let font_button = gtk::FontDialogButton::new(Some(font_dialog));
#[allow(clippy::cast_possible_truncation)]
let font_desc = gtk::pango::FontDescription::from_string(&format!(
"{} {}",
preferences.custom_editor_font_name, preferences.editor_font_size as i32
));
font_button.set_font_desc(&font_desc);
font_button.set_sensitive(preferences.use_custom_editor_font);
font_button.set_hexpand(true);
font_button.set_use_font(true);
font_button.set_use_size(false);
font_button
}
fn create_font_row(font_button: >k::FontDialogButton) -> gtk::Box {
let row = gtk::Box::new(gtk::Orientation::Horizontal, 10);
row.append(>k::Label::new(Some(&tr!("preferences.editor.font"))));
row.append(font_button);
row
}
fn connect_signals(
sender: &Sender,
font_size_scale: >k::Scale,
soft_wrap_switch: >k::Switch,
custom_font_switch: >k::Switch,
font_button: >k::FontDialogButton,
) {
let s = sender.clone();
let fb = font_button.clone();
font_size_scale.connect_value_changed(move |scale| {
s.emit(PreferencesInput::SetEditorFontSize(scale.value()));
Self::update_font_button_size(&fb, scale.value());
});
let s = sender.clone();
soft_wrap_switch.connect_state_set(move |_, state| {
s.emit(PreferencesInput::SetSoftWrapLines(state));
gtk::glib::Propagation::Proceed
});
let s = sender.clone();
let scale = font_size_scale.clone();
font_button.connect_font_desc_notify(move |button| {
if let Some(font) = button.font_desc() {
if let Some(family) = font.family() {
s.emit(PreferencesInput::SetCustomEditorFontName(
family.to_string(),
));
}
let size = font.size() / gtk::pango::SCALE;
if size > 0 {
scale.set_value(f64::from(size));
}
}
});
let s = sender.clone();
let fb = font_button.clone();
custom_font_switch.connect_state_set(move |_, state| {
fb.set_sensitive(state);
s.emit(PreferencesInput::SetUseCustomEditorFont(state));
gtk::glib::Propagation::Proceed
});
}
fn update_font_button_size(font_button: >k::FontDialogButton, size: f64) {
if let Some(mut font_desc) = font_button.font_desc() {
#[allow(clippy::cast_possible_truncation)]
font_desc.set_size(size as i32 * gtk::pango::SCALE);
font_button.set_font_desc(&font_desc);
}
}
pub fn widget(&self) -> gtk::Box {
self.container.clone()
}
pub fn sync_from(&self, preferences: &UserPreferences) {
self.font_size_scale.set_value(preferences.editor_font_size);
self.soft_wrap_switch
.set_active(preferences.soft_wrap_lines);
self.custom_font_switch
.set_active(preferences.use_custom_editor_font);
self.font_button
.set_sensitive(preferences.use_custom_editor_font);
#[allow(clippy::cast_possible_truncation)]
let font_desc = gtk::pango::FontDescription::from_string(&format!(
"{} {}",
preferences.custom_editor_font_name, preferences.editor_font_size as i32
));
self.font_button.set_font_desc(&font_desc);
}
}