// 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 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>>, sender: Sender, } impl TemplatesPage { pub fn new(prefs: &UserPreferences, sender: Sender) -> 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>> = 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) { // Clear existing rows while let Some(child) = self.list_box.first_child() { self.list_box.remove(&child); } self.populate_templates(prefs); // 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