aboutsummaryrefslogtreecommitdiff
path: root/src/preferences
diff options
context:
space:
mode:
Diffstat (limited to 'src/preferences')
-rw-r--r--src/preferences/mod.rs3
-rw-r--r--src/preferences/models.rs9
-rw-r--r--src/preferences/pages/editor.rs222
-rw-r--r--src/preferences/pages/general.rs144
-rw-r--r--src/preferences/pages/map.rs212
-rw-r--r--src/preferences/pages/mod.rs21
-rw-r--r--src/preferences/pages/stages.rs253
-rw-r--r--src/preferences/pages/templates.rs349
-rw-r--r--src/preferences/window.rs371
9 files changed, 9 insertions, 1575 deletions
diff --git a/src/preferences/mod.rs b/src/preferences/mod.rs
index b369e88..6dd21b2 100644
--- a/src/preferences/mod.rs
+++ b/src/preferences/mod.rs
@@ -15,9 +15,6 @@
// along with this program. If not, see <http://www.gnu.org/licenses/>.
pub mod models;
-pub mod pages;
pub mod storage;
-pub mod window;
pub use models::UserPreferences;
-pub use window::PreferencesWindow;
diff --git a/src/preferences/models.rs b/src/preferences/models.rs
index a595fc2..93f3fa0 100644
--- a/src/preferences/models.rs
+++ b/src/preferences/models.rs
@@ -267,3 +267,12 @@ impl UserPreferences {
}
}
}
+
+/// Which stage label to update (Roman numerals I-IV)
+#[derive(Debug, Clone, Copy)]
+pub enum StageLabel {
+ I,
+ Ii,
+ Iii,
+ Iv,
+}
diff --git a/src/preferences/pages/editor.rs b/src/preferences/pages/editor.rs
deleted file mode 100644
index c0e9b41..0000000
--- a/src/preferences/pages/editor.rs
+++ /dev/null
@@ -1,222 +0,0 @@
-// 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 <http://www.gnu.org/licenses/>.
-
-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<PreferencesInput>) -> 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: &gtk::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: &gtk::FontDialogButton) -> gtk::Box {
- let row = gtk::Box::new(gtk::Orientation::Horizontal, 10);
- row.append(&gtk::Label::new(Some(&tr!("preferences.editor.font"))));
- row.append(font_button);
- row
- }
-
- fn connect_signals(
- sender: &Sender<PreferencesInput>,
- font_size_scale: &gtk::Scale,
- soft_wrap_switch: &gtk::Switch,
- custom_font_switch: &gtk::Switch,
- font_button: &gtk::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: &gtk::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);
- }
-}
diff --git a/src/preferences/pages/general.rs b/src/preferences/pages/general.rs
deleted file mode 100644
index e65ede6..0000000
--- a/src/preferences/pages/general.rs
+++ /dev/null
@@ -1,144 +0,0 @@
-// 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 <http://www.gnu.org/licenses/>.
-
-use gtk::FileFilter;
-use gtk::prelude::*;
-use relm4::Sender;
-use relm4::{adw, gtk};
-
-use crate::tr;
-
-use super::super::window::PreferencesInput;
-
-pub struct GeneralPage {
- container: gtk::Box,
-}
-
-impl GeneralPage {
- pub fn new(sender: &Sender<PreferencesInput>) -> 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);
-
- // Preferences section
- let prefs_label = gtk::Label::new(Some(&tr!("preferences.general.preferences.title")));
- prefs_label.add_css_class("heading");
- prefs_label.set_halign(gtk::Align::Start);
- container.append(&prefs_label);
-
- let button_box = gtk::Box::new(gtk::Orientation::Horizontal, 10);
-
- // Export button
- let export_button = gtk::Button::with_label(&tr!("preferences.general.preferences.export"));
- {
- let sender = sender.clone();
- export_button.connect_clicked(move |_| {
- sender.emit(PreferencesInput::Export);
- });
- }
- button_box.append(&export_button);
-
- // Import button
- let import_button = gtk::Button::with_label(&tr!("preferences.general.preferences.import"));
- {
- let sender = sender.clone();
- import_button.connect_clicked(move |_| {
- sender.emit(PreferencesInput::Import);
- });
- }
- button_box.append(&import_button);
-
- // Reset button
- let reset_button = gtk::Button::with_label(&tr!("preferences.general.preferences.reset"));
- reset_button.add_css_class("destructive-action");
- {
- let sender = sender.clone();
- reset_button.connect_clicked(move |_| {
- sender.emit(PreferencesInput::ResetToDefaults);
- });
- }
- button_box.append(&reset_button);
-
- container.append(&button_box);
-
- // Help text
- let help_label = gtk::Label::new(Some(&tr!("preferences.general.explanation")));
- help_label.add_css_class("dim-label");
- help_label.set_halign(gtk::Align::Start);
- help_label.set_wrap(true);
- container.append(&help_label);
-
- Self { container }
- }
-
- pub fn widget(&self) -> gtk::Box {
- self.container.clone()
- }
-}
-
-pub fn show_export_dialog(sender: &Sender<PreferencesInput>, window: &adw::Window) {
- let filter = FileFilter::new();
- filter.add_pattern("*.json");
- filter.set_name(Some(&tr!("dialog.file_type.json")));
-
- let filters = gtk::gio::ListStore::new::<gtk::FileFilter>();
- filters.append(&filter);
-
- let dialog = gtk::FileDialog::builder()
- .title(tr!("dialog.export_preferences.title"))
- .modal(true)
- .filters(&filters)
- .initial_name("preferences.json")
- .build();
-
- let sender = sender.clone();
- let window = window.clone();
- gtk::glib::spawn_future_local(async move {
- if let Ok(file) = dialog.save_future(Some(&window)).await
- && let Some(path) = file.path()
- {
- sender.emit(PreferencesInput::ExportToFile(path));
- }
- });
-}
-
-pub fn show_import_dialog(sender: &Sender<PreferencesInput>, window: &adw::Window) {
- let filter = FileFilter::new();
- filter.add_pattern("*.json");
- filter.set_name(Some(&tr!("dialog.file_type.json")));
-
- let filters = gtk::gio::ListStore::new::<gtk::FileFilter>();
- filters.append(&filter);
-
- let dialog = gtk::FileDialog::builder()
- .title(tr!("dialog.import_preferences.title"))
- .modal(true)
- .filters(&filters)
- .build();
-
- let sender = sender.clone();
- let window = window.clone();
- gtk::glib::spawn_future_local(async move {
- if let Ok(file) = dialog.open_future(Some(&window)).await
- && let Some(path) = file.path()
- {
- sender.emit(PreferencesInput::ImportFromFile(path));
- }
- });
-}
diff --git a/src/preferences/pages/map.rs b/src/preferences/pages/map.rs
deleted file mode 100644
index 5645a59..0000000
--- a/src/preferences/pages/map.rs
+++ /dev/null
@@ -1,212 +0,0 @@
-// 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 <http://www.gnu.org/licenses/>.
-
-use relm4::Sender;
-use relm4::gtk;
-use sourceview5::prelude::*;
-
-use crate::tr;
-use crate::ui_helpers::create_switch_row;
-
-use super::super::models::UserPreferences;
-use super::super::window::PreferencesInput;
-
-pub struct MapPage {
- container: gtk::Box,
- show_background_switch: gtk::Switch,
- smart_positioning_switch: gtk::Switch,
- custom_font_switch: gtk::Switch,
- font_button: gtk::FontDialogButton,
- export_format_dropdown: gtk::DropDown,
-}
-
-impl MapPage {
- pub fn new(preferences: &UserPreferences, sender: &Sender<PreferencesInput>) -> 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 (show_background_switch, smart_positioning_switch, custom_font_switch, font_button) =
- Self::init_style_section(&container, preferences, sender);
-
- let export_format_dropdown = Self::init_export_section(&container, preferences, sender);
-
- Self {
- container,
- show_background_switch,
- smart_positioning_switch,
- custom_font_switch,
- font_button,
- export_format_dropdown,
- }
- }
-
- fn init_style_section(
- container: &gtk::Box,
- preferences: &UserPreferences,
- sender: &Sender<PreferencesInput>,
- ) -> (gtk::Switch, gtk::Switch, gtk::Switch, gtk::FontDialogButton) {
- let style_label = gtk::Label::new(Some(&tr!("preferences.map.map_style.title")));
- style_label.add_css_class("heading");
- style_label.set_halign(gtk::Align::Start);
- container.append(&style_label);
-
- // Show Background
- let background_row = create_switch_row(
- &tr!("preferences.map.map_style.show_background"),
- preferences.show_map_background,
- );
- let show_background_switch = background_row.1.clone();
- {
- let sender = sender.clone();
- show_background_switch.connect_state_set(move |_, state| {
- sender.emit(PreferencesInput::SetShowMapBackground(state));
- gtk::glib::Propagation::Proceed
- });
- }
- container.append(&background_row.0);
-
- // Smart Label Positioning
- let smart_row = create_switch_row(
- &tr!("preferences.map.map_style.use_smart_label_positioning"),
- preferences.use_smart_label_positioning,
- );
- let smart_positioning_switch = smart_row.1.clone();
- {
- let sender = sender.clone();
- smart_positioning_switch.connect_state_set(move |_, state| {
- sender.emit(PreferencesInput::SetUseSmartLabelPositioning(state));
- gtk::glib::Propagation::Proceed
- });
- }
- container.append(&smart_row.0);
-
- // Use Custom Font
- let custom_font_row = create_switch_row(
- &tr!("preferences.map.map_style.use_custom_font"),
- preferences.use_custom_font,
- );
- let custom_font_switch = custom_font_row.1.clone();
- container.append(&custom_font_row.0);
-
- // Font selection
- let font_row = gtk::Box::new(gtk::Orientation::Horizontal, 10);
- font_row.append(&gtk::Label::new(Some(&tr!("preferences.map.font"))));
-
- let font_dialog = gtk::FontDialog::new();
- let font_button = gtk::FontDialogButton::new(Some(font_dialog));
- let font_desc = gtk::pango::FontDescription::from_string(&preferences.custom_font_name);
- font_button.set_font_desc(&font_desc);
- font_button.set_sensitive(preferences.use_custom_font);
- font_button.set_hexpand(true);
- font_button.set_use_font(true);
- {
- let sender = sender.clone();
- font_button.connect_font_desc_notify(move |button| {
- if let Some(font) = button.font_desc()
- && let Some(family) = font.family()
- {
- sender.emit(PreferencesInput::SetCustomFontName(family.to_string()));
- }
- });
- }
- font_row.append(&font_button);
- container.append(&font_row);
-
- // Connect custom font switch to font button sensitivity
- {
- let font_button_clone = font_button.clone();
- let sender = sender.clone();
- custom_font_switch.connect_state_set(move |_, state| {
- font_button_clone.set_sensitive(state);
- sender.emit(PreferencesInput::SetUseCustomFont(state));
- gtk::glib::Propagation::Proceed
- });
- }
-
- (
- show_background_switch,
- smart_positioning_switch,
- custom_font_switch,
- font_button,
- )
- }
-
- fn init_export_section(
- container: &gtk::Box,
- preferences: &UserPreferences,
- sender: &Sender<PreferencesInput>,
- ) -> gtk::DropDown {
- let export_label = gtk::Label::new(Some(&tr!("preferences.map.export.title")));
- export_label.add_css_class("heading");
- export_label.set_halign(gtk::Align::Start);
- export_label.set_margin_top(10);
- container.append(&export_label);
-
- let export_row = gtk::Box::new(gtk::Orientation::Horizontal, 10);
- export_row.append(&gtk::Label::new(Some(&tr!(
- "preferences.map.export.default_format"
- ))));
-
- let formats =
- gtk::StringList::new(&[&tr!("export_formats.png"), &tr!("export_formats.svg")]);
- let export_format_dropdown = gtk::DropDown::new(Some(formats), None::<gtk::Expression>);
- let selected = match preferences.default_export_format.as_str() {
- "svg" => 1,
- _ => 0,
- };
- export_format_dropdown.set_selected(selected);
- {
- let sender = sender.clone();
- export_format_dropdown.connect_selected_notify(move |dropdown| {
- let format = match dropdown.selected() {
- 1 => "svg",
- _ => "png",
- };
- sender.emit(PreferencesInput::SetDefaultExportFormat(format.to_string()));
- });
- }
- export_row.append(&export_format_dropdown);
- container.append(&export_row);
-
- export_format_dropdown
- }
-
- pub fn widget(&self) -> gtk::Box {
- self.container.clone()
- }
-
- pub fn sync_from(&self, preferences: &UserPreferences) {
- self.show_background_switch
- .set_active(preferences.show_map_background);
- self.smart_positioning_switch
- .set_active(preferences.use_smart_label_positioning);
- self.custom_font_switch
- .set_active(preferences.use_custom_font);
- self.font_button.set_sensitive(preferences.use_custom_font);
- let font_desc = gtk::pango::FontDescription::from_string(&preferences.custom_font_name);
- self.font_button.set_font_desc(&font_desc);
-
- let selected = match preferences.default_export_format.as_str() {
- "svg" => 1,
- _ => 0,
- };
- self.export_format_dropdown.set_selected(selected);
- }
-}
diff --git a/src/preferences/pages/mod.rs b/src/preferences/pages/mod.rs
deleted file mode 100644
index 2ace921..0000000
--- a/src/preferences/pages/mod.rs
+++ /dev/null
@@ -1,21 +0,0 @@
-// 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 <http://www.gnu.org/licenses/>.
-
-pub mod editor;
-pub mod general;
-pub mod map;
-pub mod stages;
-pub mod templates;
diff --git a/src/preferences/pages/stages.rs b/src/preferences/pages/stages.rs
deleted file mode 100644
index 4bd24e1..0000000
--- a/src/preferences/pages/stages.rs
+++ /dev/null
@@ -1,253 +0,0 @@
-// 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 <http://www.gnu.org/licenses/>.
-
-use gtk::prelude::*;
-use relm4::Sender;
-use relm4::gtk;
-use uuid::Uuid;
-
-use crate::tr;
-
-use super::super::models::UserPreferences;
-use super::super::window::{PreferencesInput, StageLabel};
-
-pub struct StagesPage {
- container: gtk::Box,
- list_box: gtk::ListBox,
- sender: Sender<PreferencesInput>,
-}
-
-impl StagesPage {
- pub fn new(preferences: &UserPreferences, sender: Sender<PreferencesInput>) -> Self {
- let container = gtk::Box::new(gtk::Orientation::Vertical, 10);
- container.set_margin_top(20);
- container.set_margin_bottom(20);
- container.set_margin_start(20);
- container.set_margin_end(20);
-
- // Header
- let header_label = gtk::Label::new(Some(&tr!("preferences.stages.title")));
- header_label.add_css_class("heading");
- header_label.set_halign(gtk::Align::Start);
- container.append(&header_label);
-
- let help_label = gtk::Label::new(Some(&tr!("preferences.stages.explanation")));
- help_label.add_css_class("dim-label");
- help_label.set_halign(gtk::Align::Start);
- help_label.set_wrap(true);
- help_label.set_margin_bottom(10);
- container.append(&help_label);
-
- // Column headers
- let header_row = gtk::Box::new(gtk::Orientation::Horizontal, 5);
- header_row.add_css_class("dim-label");
-
- let name_header = gtk::Label::new(Some(&tr!("preferences.stages.column.name")));
- name_header.set_width_chars(15);
- name_header.set_xalign(0.0);
- header_row.append(&name_header);
-
- let first_header = gtk::Label::new(Some(&tr!("preferences.stages.column.stage_i")));
- first_header.set_width_chars(12);
- first_header.set_xalign(0.0);
- header_row.append(&first_header);
-
- let second_header = gtk::Label::new(Some(&tr!("preferences.stages.column.stage_ii")));
- second_header.set_width_chars(12);
- second_header.set_xalign(0.0);
- header_row.append(&second_header);
-
- let third_header = gtk::Label::new(Some(&tr!("preferences.stages.column.stage_iii")));
- third_header.set_width_chars(12);
- third_header.set_xalign(0.0);
- header_row.append(&third_header);
-
- let fourth_header = gtk::Label::new(Some(&tr!("preferences.stages.column.stage_iv")));
- fourth_header.set_width_chars(12);
- fourth_header.set_xalign(0.0);
- header_row.append(&fourth_header);
-
- // Spacer for delete button column
- let spacer = gtk::Box::new(gtk::Orientation::Horizontal, 0);
- spacer.set_width_request(32);
- header_row.append(&spacer);
-
- container.append(&header_row);
-
- // Scrollable list area
- let scrolled = gtk::ScrolledWindow::new();
- scrolled.set_vexpand(true);
- scrolled.set_min_content_height(200);
-
- let list_box = gtk::ListBox::new();
- list_box.set_selection_mode(gtk::SelectionMode::None);
- list_box.add_css_class("boxed-list");
-
- scrolled.set_child(Some(&list_box));
- container.append(&scrolled);
-
- // Add button
- let button_box = gtk::Box::new(gtk::Orientation::Horizontal, 10);
- button_box.set_halign(gtk::Align::End);
-
- let add_button = gtk::Button::from_icon_name("list-add-symbolic");
- add_button.set_tooltip_text(Some(&tr!("preferences.stages.help.add")));
- {
- let sender = sender.clone();
- add_button.connect_clicked(move |_| {
- sender.emit(PreferencesInput::AddCustomStage);
- });
- }
- button_box.append(&add_button);
- container.append(&button_box);
-
- let page = Self {
- container,
- list_box,
- sender,
- };
-
- page.populate_stages(preferences);
- page
- }
-
- pub fn widget(&self) -> gtk::Box {
- self.container.clone()
- }
-
- pub fn refresh(&self, preferences: &UserPreferences) {
- // Clear existing rows
- while let Some(child) = self.list_box.first_child() {
- self.list_box.remove(&child);
- }
- self.populate_stages(preferences);
- }
-
- fn populate_stages(&self, preferences: &UserPreferences) {
- for stage in &preferences.custom_stages {
- let row = self.create_stage_row(stage.id, &stage.name, &stage.stage);
- self.list_box.append(&row);
- }
- }
-
- fn create_stage_row(
- &self,
- id: Uuid,
- name: &str,
- stage: &super::super::models::Stage,
- ) -> gtk::ListBoxRow {
- let row = gtk::ListBoxRow::new();
- let hbox = gtk::Box::new(gtk::Orientation::Horizontal, 5);
- hbox.set_margin_top(5);
- hbox.set_margin_bottom(5);
- hbox.set_margin_start(5);
- hbox.set_margin_end(5);
-
- // Name entry
- let name_entry = gtk::Entry::new();
- name_entry.set_text(name);
- name_entry.set_width_chars(15);
- {
- let sender = self.sender.clone();
- name_entry.connect_changed(move |entry| {
- sender.emit(PreferencesInput::SetCustomStageName(
- id,
- entry.text().to_string(),
- ));
- });
- }
- hbox.append(&name_entry);
-
- // Stage I entry
- let first_entry = gtk::Entry::new();
- first_entry.set_text(&stage.i);
- first_entry.set_width_chars(12);
- {
- let sender = self.sender.clone();
- first_entry.connect_changed(move |entry| {
- sender.emit(PreferencesInput::SetCustomStageLabel(
- id,
- StageLabel::I,
- entry.text().to_string(),
- ));
- });
- }
- hbox.append(&first_entry);
-
- // Stage II entry
- let second_entry = gtk::Entry::new();
- second_entry.set_text(&stage.ii);
- second_entry.set_width_chars(12);
- {
- let sender = self.sender.clone();
- second_entry.connect_changed(move |entry| {
- sender.emit(PreferencesInput::SetCustomStageLabel(
- id,
- StageLabel::Ii,
- entry.text().to_string(),
- ));
- });
- }
- hbox.append(&second_entry);
-
- // Stage III entry
- let third_entry = gtk::Entry::new();
- third_entry.set_text(&stage.iii);
- third_entry.set_width_chars(12);
- {
- let sender = self.sender.clone();
- third_entry.connect_changed(move |entry| {
- sender.emit(PreferencesInput::SetCustomStageLabel(
- id,
- StageLabel::Iii,
- entry.text().to_string(),
- ));
- });
- }
- hbox.append(&third_entry);
-
- // Stage IV entry
- let fourth_entry = gtk::Entry::new();
- fourth_entry.set_text(&stage.iv);
- fourth_entry.set_width_chars(12);
- {
- let sender = self.sender.clone();
- fourth_entry.connect_changed(move |entry| {
- sender.emit(PreferencesInput::SetCustomStageLabel(
- id,
- StageLabel::Iv,
- entry.text().to_string(),
- ));
- });
- }
- hbox.append(&fourth_entry);
-
- // Delete button
- let delete_button = gtk::Button::from_icon_name("list-remove-symbolic");
- delete_button.add_css_class("flat");
- delete_button.set_tooltip_text(Some(&tr!("preferences.stages.help.remove")));
- {
- let sender = self.sender.clone();
- delete_button.connect_clicked(move |_| {
- sender.emit(PreferencesInput::RemoveCustomStage(id));
- });
- }
- hbox.append(&delete_button);
-
- row.set_child(Some(&hbox));
- row
- }
-}
diff --git a/src/preferences/pages/templates.rs b/src/preferences/pages/templates.rs
deleted file mode 100644
index 029b3e9..0000000
--- a/src/preferences/pages/templates.rs
+++ /dev/null
@@ -1,349 +0,0 @@
-// 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 <http://www.gnu.org/licenses/>.
-
-use std::cell::RefCell;
-use std::rc::Rc;
-
-use relm4::Sender;
-use relm4::gtk;
-use sourceview5::prelude::*;
-use uuid::Uuid;
-
-use crate::tr;
-
-use super::super::models::{Template, UserPreferences};
-use super::super::window::PreferencesInput;
-
-pub struct TemplatesPage {
- container: gtk::Box,
- list_box: gtk::ListBox,
- #[allow(dead_code)]
- editor: sourceview5::View,
- editor_buffer: sourceview5::Buffer,
- selected_id: Rc<RefCell<Option<Uuid>>>,
- sender: Sender<PreferencesInput>,
-}
-
-impl TemplatesPage {
- pub fn new(prefs: &UserPreferences, sender: Sender<PreferencesInput>) -> Self {
- let container = gtk::Box::new(gtk::Orientation::Horizontal, 0);
- container.set_margin_top(10);
- container.set_margin_bottom(10);
- container.set_margin_start(10);
- container.set_margin_end(10);
-
- let selected_id: Rc<RefCell<Option<Uuid>>> = Rc::new(RefCell::new(None));
-
- // Left sidebar
- let sidebar = gtk::Box::new(gtk::Orientation::Vertical, 5);
- sidebar.set_width_request(200);
-
- let sidebar_label = gtk::Label::new(Some(&tr!("preferences.templates.title")));
- sidebar_label.add_css_class("heading");
- sidebar_label.set_halign(gtk::Align::Start);
- sidebar_label.set_margin_start(5);
- sidebar.append(&sidebar_label);
-
- // Template list
- let scrolled = gtk::ScrolledWindow::new();
- scrolled.set_vexpand(true);
-
- let list_box = gtk::ListBox::new();
- list_box.set_selection_mode(gtk::SelectionMode::Single);
- list_box.add_css_class("boxed-list");
-
- scrolled.set_child(Some(&list_box));
- sidebar.append(&scrolled);
-
- // Sidebar buttons
- let button_box = gtk::Box::new(gtk::Orientation::Horizontal, 5);
- button_box.set_halign(gtk::Align::End);
- button_box.set_margin_top(5);
-
- let add_button = gtk::Button::from_icon_name("list-add-symbolic");
- add_button.set_tooltip_text(Some(&tr!("preferences.templates.help.add")));
- {
- let sender = sender.clone();
- add_button.connect_clicked(move |_| {
- sender.emit(PreferencesInput::AddTemplate(tr!(
- "dialog.template.default_label"
- )));
- });
- }
- button_box.append(&add_button);
-
- let remove_button = gtk::Button::from_icon_name("list-remove-symbolic");
- remove_button.set_tooltip_text(Some(&tr!("preferences.templates.help.remove")));
- {
- let sender = sender.clone();
- let selected_id = selected_id.clone();
- remove_button.connect_clicked(move |_| {
- if let Some(id) = *selected_id.borrow() {
- sender.emit(PreferencesInput::RemoveTemplate(id));
- }
- });
- }
- button_box.append(&remove_button);
-
- sidebar.append(&button_box);
- container.append(&sidebar);
-
- // Separator
- let separator = gtk::Separator::new(gtk::Orientation::Vertical);
- separator.set_margin_start(10);
- separator.set_margin_end(10);
- container.append(&separator);
-
- // Right side: editor
- let editor_box = gtk::Box::new(gtk::Orientation::Vertical, 5);
- editor_box.set_hexpand(true);
-
- let editor_label = gtk::Label::new(Some(&tr!("preferences.templates.content")));
- editor_label.add_css_class("heading");
- editor_label.set_halign(gtk::Align::Start);
- editor_box.append(&editor_label);
-
- let editor_scrolled = gtk::ScrolledWindow::new();
- editor_scrolled.set_vexpand(true);
- editor_scrolled.set_hexpand(true);
-
- let editor_buffer = sourceview5::Buffer::new(None);
- editor_buffer.set_highlight_syntax(true);
-
- // Try to set wmap language
- let language_manager = sourceview5::LanguageManager::default();
- if let Some(language) = language_manager.language("wmap") {
- editor_buffer.set_language(Some(&language));
- }
-
- let editor = sourceview5::View::with_buffer(&editor_buffer);
- editor.set_monospace(true);
- editor.set_show_line_numbers(true);
- editor.set_tab_width(2);
-
- // Connect buffer changes to save
- {
- let sender = sender.clone();
- let selected_id = selected_id.clone();
- editor_buffer.connect_changed(move |buffer| {
- if let Some(id) = *selected_id.borrow() {
- let start = buffer.start_iter();
- let end = buffer.end_iter();
- let content = buffer.text(&start, &end, false).to_string();
- sender.emit(PreferencesInput::SetTemplateContent(id, content));
- }
- });
- }
-
- editor_scrolled.set_child(Some(&editor));
- editor_box.append(&editor_scrolled);
- container.append(&editor_box);
-
- let page = Self {
- container,
- list_box,
- editor,
- editor_buffer,
- selected_id,
- sender,
- };
-
- page.populate_templates(prefs);
-
- // Select first template if available
- if let Some(first) = prefs.map_templates.first() {
- *page.selected_id.borrow_mut() = Some(first.id);
- page.editor_buffer.set_text(&first.content);
- }
-
- page
- }
-
- pub fn widget(&self) -> gtk::Box {
- self.container.clone()
- }
-
- pub fn refresh(&self, prefs: &UserPreferences) {
- // Check if we need to recreate rows (templates added/removed/reordered)
- let current_row_count = {
- let mut count = 0;
- let mut child = self.list_box.first_child();
- while child.is_some() {
- count += 1;
- child = child.and_then(|c| c.next_sibling());
- }
- count
- };
-
- let needs_recreate = current_row_count != prefs.map_templates.len();
-
- if needs_recreate {
- // Clear existing rows
- while let Some(child) = self.list_box.first_child() {
- self.list_box.remove(&child);
- }
- self.populate_templates(prefs);
- } else {
- // Just update existing rows in place
- let mut child = self.list_box.first_child();
- let mut index = 0;
- while let Some(row) = child {
- if let Some(template) = prefs.map_templates.get(index) {
- TemplatesPage::update_template_row(&row, template);
- }
- child = row.next_sibling();
- index += 1;
- }
- }
-
- // Update editor if selected template still exists
- let selected = *self.selected_id.borrow();
- if let Some(id) = selected {
- if let Some(template) = prefs.map_templates.iter().find(|t| t.id == id) {
- // Only update if content differs to avoid cursor jumping
- let current = {
- let start = self.editor_buffer.start_iter();
- let end = self.editor_buffer.end_iter();
- self.editor_buffer.text(&start, &end, false).to_string()
- };
- if current != template.content {
- self.editor_buffer.set_text(&template.content);
- }
- } else {
- // Selected template was deleted, select first
- if let Some(first) = prefs.map_templates.first() {
- *self.selected_id.borrow_mut() = Some(first.id);
- self.editor_buffer.set_text(&first.content);
- } else {
- *self.selected_id.borrow_mut() = None;
- self.editor_buffer.set_text("");
- }
- }
- }
- }
-
- fn populate_templates(&self, prefs: &UserPreferences) {
- for template in &prefs.map_templates {
- let row = self.create_template_row(template);
- self.list_box.append(&row);
- }
-
- // Connect selection changed
- {
- let selected_id = self.selected_id.clone();
- let editor_buffer = self.editor_buffer.clone();
- let _sender = self.sender.clone();
-
- // We need to store templates for the closure
- let templates: Vec<Template> = prefs.map_templates.clone();
-
- self.list_box.connect_row_selected(move |_, row| {
- if let Some(row) = row {
- let index: usize = row.index().try_into().unwrap_or(0);
- if let Some(template) = templates.get(index) {
- *selected_id.borrow_mut() = Some(template.id);
- editor_buffer.set_text(&template.content);
- }
- }
- });
- }
- }
-
- fn update_template_row(row: &gtk::Widget, template: &Template) {
- if let Some(list_row) = row.downcast_ref::<gtk::ListBoxRow>()
- && let Some(hbox) = list_row.child().and_then(|c| c.downcast::<gtk::Box>().ok())
- {
- let mut child = hbox.first_child();
- let mut widget_index = 0;
-
- while let Some(widget) = child {
- if widget_index == 0 {
- // Update default button
- if let Some(button) = widget.downcast_ref::<gtk::Button>() {
- if template.is_default {
- button.set_icon_name("emblem-default-symbolic");
- button
- .set_tooltip_text(Some(&tr!("preferences.templates.help.default")));
- } else {
- button.set_icon_name("radio-symbolic");
- button.set_tooltip_text(Some(&tr!(
- "preferences.templates.help.set_as_default"
- )));
- }
- }
- } else if widget_index == 1 {
- // Update name entry
- if let Some(entry) = widget.downcast_ref::<gtk::Entry>() {
- // Only update if different to avoid cursor reset
- if entry.text() != template.name {
- entry.set_text(&template.name);
- }
- }
- }
- child = widget.next_sibling();
- widget_index += 1;
- }
- }
- }
-
- fn create_template_row(&self, template: &Template) -> gtk::ListBoxRow {
- let row = gtk::ListBoxRow::new();
- let hbox = gtk::Box::new(gtk::Orientation::Horizontal, 10);
- hbox.set_margin_top(8);
- hbox.set_margin_bottom(8);
- hbox.set_margin_start(8);
- hbox.set_margin_end(8);
-
- // Default indicator button
- let default_button = gtk::Button::new();
- if template.is_default {
- default_button.set_icon_name("emblem-default-symbolic");
- default_button.set_tooltip_text(Some(&tr!("preferences.templates.help.default")));
- } else {
- default_button.set_icon_name("radio-symbolic");
- default_button
- .set_tooltip_text(Some(&tr!("preferences.templates.help.set_as_default")));
- }
- default_button.add_css_class("flat");
- {
- let sender = self.sender.clone();
- let id = template.id;
- default_button.connect_clicked(move |_| {
- sender.emit(PreferencesInput::SetDefaultTemplate(id));
- });
- }
- hbox.append(&default_button);
-
- // Template name (editable)
- let name_entry = gtk::Entry::new();
- name_entry.set_text(&template.name);
- name_entry.set_hexpand(true);
- {
- let sender = self.sender.clone();
- let id = template.id;
- name_entry.connect_changed(move |entry| {
- sender.emit(PreferencesInput::SetTemplateName(
- id,
- entry.text().to_string(),
- ));
- });
- }
- hbox.append(&name_entry);
-
- row.set_child(Some(&hbox));
- row
- }
-}
diff --git a/src/preferences/window.rs b/src/preferences/window.rs
deleted file mode 100644
index 9d58b95..0000000
--- a/src/preferences/window.rs
+++ /dev/null
@@ -1,371 +0,0 @@
-// 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 <http://www.gnu.org/licenses/>.
-
-// Suppress warnings from relm4 view! macro internals
-#![allow(unused_assignments)]
-
-use std::path::PathBuf;
-
-use crate::icon_names::shipped::{AGENDA, BUILD, MAP, PAPYRUS, SETTINGS};
-use gtk::prelude::*;
-use relm4::{ComponentParts, ComponentSender, SimpleComponent, adw, gtk};
-
-use crate::tr;
-
-use super::models::UserPreferences;
-use super::pages::{editor, general, map, stages, templates};
-use super::storage;
-
-/// Messages for preferences window
-#[derive(Debug)]
-pub enum PreferencesInput {
- // General page actions
- Export,
- Import,
- ImportFromFile(PathBuf),
- ExportToFile(PathBuf),
- ResetToDefaults,
-
- // Preference updates from pages
- SetShowMapBackground(bool),
- SetUseCustomFont(bool),
- SetCustomFontName(String),
- SetDefaultExportFormat(String),
- SetUseSmartLabelPositioning(bool),
-
- SetUseCustomEditorFont(bool),
- SetCustomEditorFontName(String),
- SetEditorFontSize(f64),
- SetSoftWrapLines(bool),
-
- // Templates
- AddTemplate(String),
- RemoveTemplate(uuid::Uuid),
- SetTemplateContent(uuid::Uuid, String),
- SetTemplateName(uuid::Uuid, String),
- SetDefaultTemplate(uuid::Uuid),
-
- // Custom stages
- AddCustomStage,
- RemoveCustomStage(uuid::Uuid),
- SetCustomStageName(uuid::Uuid, String),
- SetCustomStageLabel(uuid::Uuid, StageLabel, String),
-
- // Close
- Close,
-}
-
-/// Which stage label to update (Roman numerals I-IV)
-#[derive(Debug, Clone, Copy)]
-pub enum StageLabel {
- I,
- Ii,
- Iii,
- Iv,
-}
-
-/// Output messages to parent app
-#[derive(Debug)]
-pub enum PreferencesOutput {
- PreferencesChanged(UserPreferences),
- Closed,
-}
-
-pub struct PreferencesWindow {
- preferences: UserPreferences,
- window: adw::Window,
- // Widget references for pages
- general_page: general::GeneralPage,
- editor_page: editor::EditorPage,
- map_page: map::MapPage,
- stages_page: stages::StagesPage,
- templates_page: templates::TemplatesPage,
-}
-
-#[relm4::component(pub)]
-impl SimpleComponent for PreferencesWindow {
- type Init = UserPreferences;
- type Input = PreferencesInput;
- type Output = PreferencesOutput;
-
- view! {
- adw::Window {
- set_title: Some(&tr!("preferences.title")),
- set_default_width: 700,
- set_default_height: 500,
- set_modal: true,
- set_resizable: true,
-
- connect_close_request[sender] => move |_| {
- sender.input(PreferencesInput::Close);
- gtk::glib::Propagation::Proceed
- },
-
- adw::ToolbarView {
- add_top_bar = &adw::HeaderBar {
- #[wrap(Some)]
- set_title_widget = &adw::ViewSwitcher {
- set_stack: Some(&stack),
- },
- },
-
- #[name = "stack"]
- adw::ViewStack {
- set_vexpand: true,
-
- add_titled_with_icon[Some("general"), &tr!("preferences.menu.general"), SETTINGS] = &model.general_page.widget() -> gtk::Box {},
- add_titled_with_icon[Some("editor"), &tr!("preferences.menu.editor"), BUILD] = &model.editor_page.widget() -> gtk::Box {},
- add_titled_with_icon[Some("map"), &tr!("preferences.menu.map"), MAP] = &model.map_page.widget() -> gtk::Box {},
- add_titled_with_icon[Some("stages"), &tr!("preferences.menu.stages"), AGENDA] = &model.stages_page.widget() -> gtk::Box {},
- add_titled_with_icon[Some("templates"), &tr!("preferences.menu.templates"), PAPYRUS] = &model.templates_page.widget() -> gtk::Box {},
- },
- },
- }
- }
-
- fn init(
- preferences: Self::Init,
- root: Self::Root,
- sender: ComponentSender<Self>,
- ) -> ComponentParts<Self> {
- let general_page = general::GeneralPage::new(&sender.input_sender().clone());
- let editor_page = editor::EditorPage::new(&preferences, &sender.input_sender().clone());
- let map_page = map::MapPage::new(&preferences, &sender.input_sender().clone());
- let stages_page = stages::StagesPage::new(&preferences, sender.input_sender().clone());
- let templates_page =
- templates::TemplatesPage::new(&preferences, sender.input_sender().clone());
-
- let model = PreferencesWindow {
- preferences,
- window: root.clone(),
- general_page,
- editor_page,
- map_page,
- stages_page,
- templates_page,
- };
-
- let widgets = view_output!();
-
- ComponentParts { model, widgets }
- }
-
- fn update(&mut self, message: Self::Input, sender: ComponentSender<Self>) {
- match message {
- PreferencesInput::Export => {
- general::show_export_dialog(sender.input_sender(), &self.window);
- }
- PreferencesInput::Import => {
- general::show_import_dialog(sender.input_sender(), &self.window);
- }
- PreferencesInput::ImportFromFile(path) => {
- if storage::import_from_file(&mut self.preferences, &path).is_ok() {
- self.sync_ui_from_preferences();
- self.save_and_notify(&sender);
- }
- }
- PreferencesInput::ExportToFile(path) => {
- let _ = storage::export_to_file(&self.preferences, &path);
- }
- PreferencesInput::ResetToDefaults => {
- self.preferences = UserPreferences::default();
- self.sync_ui_from_preferences();
- self.save_and_notify(&sender);
- }
- PreferencesInput::SetShowMapBackground(v) => {
- self.preferences.show_map_background = v;
- self.save_and_notify(&sender);
- }
- PreferencesInput::SetUseCustomFont(v) => {
- self.preferences.use_custom_font = v;
- self.save_and_notify(&sender);
- }
- PreferencesInput::SetCustomFontName(v) => {
- self.preferences.custom_font_name = v;
- self.save_and_notify(&sender);
- }
- PreferencesInput::SetDefaultExportFormat(v) => {
- self.preferences.default_export_format = v;
- self.save_and_notify(&sender);
- }
- PreferencesInput::SetUseSmartLabelPositioning(v) => {
- self.preferences.use_smart_label_positioning = v;
- self.save_and_notify(&sender);
- }
- PreferencesInput::SetUseCustomEditorFont(v) => {
- self.preferences.use_custom_editor_font = v;
- self.save_and_notify(&sender);
- }
- PreferencesInput::SetCustomEditorFontName(v) => {
- self.preferences.custom_editor_font_name = v;
- self.save_and_notify(&sender);
- }
- PreferencesInput::SetEditorFontSize(v) => {
- self.preferences.editor_font_size = v;
- self.save_and_notify(&sender);
- }
- PreferencesInput::SetSoftWrapLines(v) => {
- self.preferences.soft_wrap_lines = v;
- self.save_and_notify(&sender);
- }
- PreferencesInput::AddTemplate(name) => self.handle_add_template(name, &sender),
- PreferencesInput::RemoveTemplate(id) => self.handle_remove_template(id, &sender),
- PreferencesInput::SetTemplateContent(id, content) => {
- self.handle_set_template_content(id, content, &sender);
- }
- PreferencesInput::SetTemplateName(id, name) => {
- self.handle_set_template_name(id, name, &sender);
- }
- PreferencesInput::SetDefaultTemplate(id) => {
- self.preferences.set_default_template(id);
- self.templates_page.refresh(&self.preferences);
- self.save_and_notify(&sender);
- }
- PreferencesInput::AddCustomStage => self.handle_add_custom_stage(&sender),
- PreferencesInput::RemoveCustomStage(id) => {
- self.handle_remove_custom_stage(id, &sender);
- }
- PreferencesInput::SetCustomStageName(id, name) => {
- self.handle_set_custom_stage_name(id, name, &sender);
- }
- PreferencesInput::SetCustomStageLabel(id, label, value) => {
- self.handle_set_custom_stage_label(id, label, value, &sender);
- }
- PreferencesInput::Close => {
- sender.output(PreferencesOutput::Closed).ok();
- }
- }
- }
-}
-
-impl PreferencesWindow {
- fn save_and_notify(&self, sender: &ComponentSender<Self>) {
- let _ = storage::save(&self.preferences);
- sender
- .output(PreferencesOutput::PreferencesChanged(
- self.preferences.clone(),
- ))
- .ok();
- }
-
- fn sync_ui_from_preferences(&mut self) {
- self.editor_page.sync_from(&self.preferences);
- self.map_page.sync_from(&self.preferences);
- self.stages_page.refresh(&self.preferences);
- self.templates_page.refresh(&self.preferences);
- }
-
- fn handle_add_template(&mut self, name: String, sender: &ComponentSender<Self>) {
- let template = super::models::Template::new(name, String::new(), false);
- self.preferences.map_templates.push(template);
- self.templates_page.refresh(&self.preferences);
- self.save_and_notify(sender);
- }
-
- fn handle_remove_template(&mut self, id: uuid::Uuid, sender: &ComponentSender<Self>) {
- self.preferences.map_templates.retain(|t| t.id != id);
- self.templates_page.refresh(&self.preferences);
- self.save_and_notify(sender);
- }
-
- fn handle_set_template_content(
- &mut self,
- id: uuid::Uuid,
- content: String,
- sender: &ComponentSender<Self>,
- ) {
- if let Some(template) = self
- .preferences
- .map_templates
- .iter_mut()
- .find(|t| t.id == id)
- {
- template.content = content;
- self.save_and_notify(sender);
- }
- }
-
- fn handle_set_template_name(
- &mut self,
- id: uuid::Uuid,
- name: String,
- sender: &ComponentSender<Self>,
- ) {
- if let Some(template) = self
- .preferences
- .map_templates
- .iter_mut()
- .find(|t| t.id == id)
- {
- template.name = name;
- self.templates_page.refresh(&self.preferences);
- self.save_and_notify(sender);
- }
- }
-
- fn handle_add_custom_stage(&mut self, sender: &ComponentSender<Self>) {
- let stage = super::models::CustomStage::placeholder_default();
- self.preferences.custom_stages.push(stage);
- self.stages_page.refresh(&self.preferences);
- self.save_and_notify(sender);
- }
-
- fn handle_remove_custom_stage(&mut self, id: uuid::Uuid, sender: &ComponentSender<Self>) {
- self.preferences.custom_stages.retain(|s| s.id != id);
- self.stages_page.refresh(&self.preferences);
- self.save_and_notify(sender);
- }
-
- fn handle_set_custom_stage_name(
- &mut self,
- id: uuid::Uuid,
- name: String,
- sender: &ComponentSender<Self>,
- ) {
- if let Some(stage) = self
- .preferences
- .custom_stages
- .iter_mut()
- .find(|s| s.id == id)
- {
- stage.name = name;
- self.save_and_notify(sender);
- }
- }
-
- fn handle_set_custom_stage_label(
- &mut self,
- id: uuid::Uuid,
- label: StageLabel,
- value: String,
- sender: &ComponentSender<Self>,
- ) {
- if let Some(stage) = self
- .preferences
- .custom_stages
- .iter_mut()
- .find(|s| s.id == id)
- {
- match label {
- StageLabel::I => stage.stage.i = value,
- StageLabel::Ii => stage.stage.ii = value,
- StageLabel::Iii => stage.stage.iii = value,
- StageLabel::Iv => stage.stage.iv = value,
- }
- self.save_and_notify(sender);
- }
- }
-}