aboutsummaryrefslogtreecommitdiff
path: root/src/preferences/pages/templates.rs
diff options
context:
space:
mode:
authorRubén Beltrán del Río <jj@r.bdr.sh>2026-01-16 08:26:35 +0100
committerRubén Beltrán del Río <jj@r.bdr.sh>2026-01-16 11:57:13 +0100
commit81cd06b3334f2f91f6a1ab62a28e11c7d0c677eb (patch)
treef0b0cf37b406eda2dec8db4382aeb5f606cd1f0d /src/preferences/pages/templates.rs
parent4211b2ae06777d5bbe8261a1ab5c0dd057829a35 (diff)
Add preferences
Diffstat (limited to 'src/preferences/pages/templates.rs')
-rw-r--r--src/preferences/pages/templates.rs312
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::<&gtk::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();
+}