diff options
| author | Rubén Beltrán del Río <jj@r.bdr.sh> | 2026-01-16 08:26:35 +0100 |
|---|---|---|
| committer | Rubén Beltrán del Río <jj@r.bdr.sh> | 2026-01-16 11:57:13 +0100 |
| commit | 81cd06b3334f2f91f6a1ab62a28e11c7d0c677eb (patch) | |
| tree | f0b0cf37b406eda2dec8db4382aeb5f606cd1f0d /src/preferences/pages/templates.rs | |
| parent | 4211b2ae06777d5bbe8261a1ab5c0dd057829a35 (diff) | |
Add preferences
Diffstat (limited to 'src/preferences/pages/templates.rs')
| -rw-r--r-- | src/preferences/pages/templates.rs | 312 |
1 files changed, 312 insertions, 0 deletions
diff --git a/src/preferences/pages/templates.rs b/src/preferences/pages/templates.rs new file mode 100644 index 0000000..2611896 --- /dev/null +++ b/src/preferences/pages/templates.rs @@ -0,0 +1,312 @@ +// Copyright (C) 2024 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 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 https://map.tranquil.systems. + +use std::cell::RefCell; +use std::rc::Rc; + +use relm4::Sender; +use relm4::gtk; +use sourceview5::prelude::*; +use uuid::Uuid; + +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("Templates")); + 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("Add template")); + { + let sender = sender.clone(); + add_button.connect_clicked(move |_| { + // Show a simple dialog to get the template name + show_add_template_dialog(sender.clone()); + }); + } + button_box.append(&add_button); + + let remove_button = gtk::Button::from_icon_name("list-remove-symbolic"); + remove_button.set_tooltip_text(Some("Remove template")); + { + 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("Template 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<Template> = prefs.map_templates.clone(); + + self.list_box.connect_row_selected(move |_, row| { + if let Some(row) = row { + let index = row.index() as usize; + if let Some(template) = templates.get(index) { + *selected_id.borrow_mut() = Some(template.id); + editor_buffer.set_text(&template.content); + } + } + }); + } + } + + 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("Default template")); + } else { + default_button.set_icon_name("radio-symbolic"); + default_button.set_tooltip_text(Some("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 + let name_label = gtk::Label::new(Some(&template.name)); + name_label.set_hexpand(true); + name_label.set_xalign(0.0); + hbox.append(&name_label); + + row.set_child(Some(&hbox)); + row + } +} + +fn show_add_template_dialog(sender: Sender<PreferencesInput>) { + let dialog = gtk::Dialog::with_buttons( + Some("New Template"), + None::<>k::Window>, + gtk::DialogFlags::MODAL | gtk::DialogFlags::DESTROY_WITH_PARENT, + &[ + ("Cancel", gtk::ResponseType::Cancel), + ("Add", gtk::ResponseType::Accept), + ], + ); + dialog.set_default_response(gtk::ResponseType::Accept); + + let content = dialog.content_area(); + content.set_margin_top(10); + content.set_margin_bottom(10); + content.set_margin_start(10); + content.set_margin_end(10); + content.set_spacing(10); + + let label = gtk::Label::new(Some("Template name:")); + label.set_halign(gtk::Align::Start); + content.append(&label); + + let entry = gtk::Entry::new(); + entry.set_placeholder_text(Some("My Template")); + entry.set_activates_default(true); + content.append(&entry); + + dialog.connect_response(move |dialog, response| { + if response == gtk::ResponseType::Accept { + let name = entry.text().to_string(); + if !name.is_empty() { + sender.emit(PreferencesInput::AddTemplate(name)); + } + } + dialog.close(); + }); + + dialog.present(); +} |