// 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 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, } impl StagesPage { pub fn new(preferences: &UserPreferences, sender: Sender) -> 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 } }