diff options
| author | Rubén Beltrán del Río <jj@r.bdr.sh> | 2026-01-17 11:22:22 +0100 |
|---|---|---|
| committer | Rubén Beltrán del Río <jj@r.bdr.sh> | 2026-01-17 12:24:45 +0100 |
| commit | 88d6bf80f5cdbbb90ead197bd41a67eb8c44e50e (patch) | |
| tree | 401549b24fe8a747ef6739389b1b38f33ef016bb /src | |
| parent | 17898fbabde35ab346c133114e78614e707c0eca (diff) | |
Make clippy stricter and fix
Diffstat (limited to 'src')
| -rw-r--r-- | src/actions.rs | 4 | ||||
| -rw-r--r-- | src/dialogs.rs | 12 | ||||
| -rw-r--r-- | src/file_registry.rs | 2 | ||||
| -rw-r--r-- | src/handlers/export.rs | 28 | ||||
| -rw-r--r-- | src/handlers/view.rs | 6 | ||||
| -rw-r--r-- | src/i18n.rs | 4 | ||||
| -rw-r--r-- | src/main.rs | 239 | ||||
| -rw-r--r-- | src/preferences/models.rs | 21 | ||||
| -rw-r--r-- | src/preferences/pages/editor.rs | 24 | ||||
| -rw-r--r-- | src/preferences/pages/general.rs | 2 | ||||
| -rw-r--r-- | src/preferences/pages/map.rs | 65 | ||||
| -rw-r--r-- | src/preferences/pages/stages.rs | 72 | ||||
| -rw-r--r-- | src/preferences/pages/templates.rs | 16 | ||||
| -rw-r--r-- | src/preferences/window.rs | 187 | ||||
| -rw-r--r-- | src/stages.rs | 46 |
15 files changed, 449 insertions, 279 deletions
diff --git a/src/actions.rs b/src/actions.rs index 859d559..fdea125 100644 --- a/src/actions.rs +++ b/src/actions.rs @@ -25,14 +25,14 @@ pub enum ImageFormat { } impl ImageFormat { - pub fn extension(&self) -> &'static str { + pub fn extension(self) -> &'static str { match self { ImageFormat::Png => "png", ImageFormat::Svg => "svg", } } - pub fn mime_type(&self) -> &'static str { + pub fn mime_type(self) -> &'static str { match self { ImageFormat::Png => "image/png", ImageFormat::Svg => "image/svg+xml", diff --git a/src/dialogs.rs b/src/dialogs.rs index 8f5b951..41b8c86 100644 --- a/src/dialogs.rs +++ b/src/dialogs.rs @@ -94,7 +94,7 @@ pub fn show_open_dialog( /// Shows a file save dialog and handles the response. pub fn show_save_dialog( window: >k::Window, - current_file: Option<PathBuf>, + current_file: Option<&PathBuf>, close_after: bool, sender: ComponentSender<AppModel>, ) { @@ -110,7 +110,7 @@ pub fn show_save_dialog( dialog.add_filter(&create_file_filter()); let default_filename = "map.wmap"; - if let Some(ref current_path) = current_file { + if let Some(current_path) = current_file { if let Some(parent) = current_path.parent() { let folder = gtk::gio::File::for_path(parent); dialog.set_current_folder(Some(&folder)).ok(); @@ -191,7 +191,7 @@ fn create_image_filter(format: ImageFormat) -> gtk::FileFilter { /// Shows an export image dialog and handles the response. pub fn show_export_dialog( window: >k::Window, - current_file: Option<PathBuf>, + current_file: Option<&PathBuf>, sender: ComponentSender<AppModel>, ) { let dialog = gtk::FileChooserDialog::new( @@ -217,13 +217,13 @@ pub fn show_export_dialog( .unwrap_or("map") .to_string(); - if let Some(ref current_path) = current_file + if let Some(current_path) = current_file && let Some(parent) = current_path.parent() { let folder = gtk::gio::File::for_path(parent); dialog.set_current_folder(Some(&folder)).ok(); } - dialog.set_current_name(&format!("{}.png", base_name)); + dialog.set_current_name(&format!("{base_name}.png")); // Update extension when filter changes { @@ -235,7 +235,7 @@ pub fn show_export_dialog( } else { "svg" }; - dialog.set_current_name(&format!("{}.{}", base_name_clone, ext)); + dialog.set_current_name(&format!("{base_name_clone}.{ext}")); }); } diff --git a/src/file_registry.rs b/src/file_registry.rs index 73299f6..749697d 100644 --- a/src/file_registry.rs +++ b/src/file_registry.rs @@ -78,7 +78,7 @@ pub fn register_sender(sender: relm4::Sender<Action>) { } /// Broadcasts a message to all windows via their registered senders. -pub fn broadcast_to_all_windows(message: Action) { +pub fn broadcast_to_all_windows(message: &Action) { WINDOW_SENDERS.with_borrow(|senders| { for sender in senders { sender.emit(message.clone()); diff --git a/src/handlers/export.rs b/src/handlers/export.rs index a0a4451..bf9dfc5 100644 --- a/src/handlers/export.rs +++ b/src/handlers/export.rs @@ -18,23 +18,27 @@ use std::path::PathBuf; use crate::AppModel; use crate::actions::ImageFormat; use crate::dialogs; -use crate::tr; use crate::stages::LocalizedStageType; +use crate::tr; use wmap_renderer::{render_to_png, render_to_svg}; /// Exports the map to an image file in the specified format. -pub fn export_to_file(model: &AppModel, path: PathBuf, format: ImageFormat) { +pub fn export_to_file(model: &AppModel, path: &PathBuf, format: ImageFormat) { let result: Result<(), String> = match format { - ImageFormat::Png => { - render_to_png(&model.map, &model.stage_type.to_localized(), &model.render_configuration) - .map_err(|e| e.to_string()) - .and_then(|data| std::fs::write(&path, data).map_err(|e| e.to_string())) - } - ImageFormat::Svg => { - render_to_svg(&model.map, &model.stage_type.to_localized(), &model.render_configuration) - .map_err(|e| e.to_string()) - .and_then(|data| std::fs::write(&path, data).map_err(|e| e.to_string())) - } + ImageFormat::Png => render_to_png( + &model.map, + &model.stage_type.to_localized(), + &model.render_configuration, + ) + .map_err(|e| e.to_string()) + .and_then(|data| std::fs::write(path, data).map_err(|e| e.to_string())), + ImageFormat::Svg => render_to_svg( + &model.map, + &model.stage_type.to_localized(), + &model.render_configuration, + ) + .map_err(|e| e.to_string()) + .and_then(|data| std::fs::write(path, data).map_err(|e| e.to_string())), }; if let Err(error) = result { diff --git a/src/handlers/view.rs b/src/handlers/view.rs index 58d44b2..660f7b9 100644 --- a/src/handlers/view.rs +++ b/src/handlers/view.rs @@ -27,8 +27,10 @@ pub fn change_orientation(model: &mut AppModel) { /// Updates the selected stage type from dropdown selection. pub fn stage_type_selected(model: &mut AppModel, index: usize) { - if index < model.available_stage_types.len() { - model.stage_type = model.available_stage_types[index].clone(); + if index < model.available_stage_types.len() + && let Some(stage_type) = model.available_stage_types.get(index) + { + model.stage_type = stage_type.clone(); model.update_image(); } } diff --git a/src/i18n.rs b/src/i18n.rs index 1c574e0..35955a1 100644 --- a/src/i18n.rs +++ b/src/i18n.rs @@ -16,7 +16,7 @@ //! Internationalization support using gettext. -use gettextrs::{bindtextdomain, setlocale, textdomain, LocaleCategory}; +use gettextrs::{LocaleCategory, bindtextdomain, setlocale, textdomain}; /// The gettext domain for the application. pub const GETTEXT_DOMAIN: &str = "systems.tranquil.Map"; @@ -43,7 +43,7 @@ pub fn init() { } } - textdomain(GETTEXT_DOMAIN).expect("Failed to set gettext domain"); + textdomain(GETTEXT_DOMAIN).ok(); } /// Macro for translating strings at runtime. diff --git a/src/main.rs b/src/main.rs index c15ffc2..4feef3b 100644 --- a/src/main.rs +++ b/src/main.rs @@ -28,6 +28,7 @@ mod stages; mod ui_helpers; mod icon_names { + #![allow(clippy::doc_markdown)] // Generated code from relm4_icons_build include!(concat!(env!("OUT_DIR"), "/icon_names.rs")); } @@ -93,7 +94,7 @@ macro_rules! register_action { }}; } -fn setup_actions(window: gtk::Window, sender: ComponentSender<AppModel>) { +fn setup_actions(window: >k::Window, sender: &ComponentSender<AppModel>) { let application = relm4::main_application(); let mut action_group = RelmActionGroup::<WindowActionGroup>::new(); @@ -179,11 +180,12 @@ fn setup_actions(window: gtk::Window, sender: ComponentSender<AppModel>) { ); let sender = sender.clone(); - let action: RelmAction<NewFromTemplateAction> = RelmAction::new_with_target_value(move |_, template_id| { - sender.input(Action::NewFromTemplateById(template_id)) - }); + let action: RelmAction<NewFromTemplateAction> = + RelmAction::new_with_target_value(move |_, template_id| { + sender.input(Action::NewFromTemplateById(template_id)); + }); action_group.add_action(action); - action_group.register_for_widget(&window); + action_group.register_for_widget(window); } struct AppInit { @@ -219,12 +221,15 @@ struct AppModel { } impl AppModel { + #[allow(clippy::cast_possible_truncation)] fn update_image(&mut self) { - if let Ok(surface) = - render_to_surface(&self.map, &self.stage_type.to_localized(), &self.render_configuration) - { - self.surface_width = (surface.width() as f64 * self.zoom).round() as i32; - self.surface_height = (surface.height() as f64 * self.zoom).round() as i32; + if let Ok(surface) = render_to_surface( + &self.map, + &self.stage_type.to_localized(), + &self.render_configuration, + ) { + self.surface_width = (f64::from(surface.width()) * self.zoom).round() as i32; + self.surface_height = (f64::from(surface.height()) * self.zoom).round() as i32; self.surface = surface; let zoom = self.zoom; @@ -272,6 +277,7 @@ impl AppModel { self.source.set_text(content); } + #[allow(clippy::cast_possible_truncation)] fn apply_preferences(&mut self) { self.source_view .set_wrap_mode(if self.preferences.soft_wrap_lines { @@ -325,8 +331,7 @@ impl AppModel { } for stage_type in &stage_types { - self.stage_type_list - .append(&stage_type.localized_name()); + self.stage_type_list.append(&stage_type.localized_name()); } self.available_stage_types = stage_types; @@ -337,7 +342,7 @@ impl AppModel { for template in &self.preferences.map_templates { self.templates_menu.append( Some(&template.name), - Some(&format!("window.new-from-template::{}", template.id)) + Some(&format!("window.new-from-template::{}", template.id)), ); } } @@ -358,6 +363,121 @@ impl AppModel { file_registry::store_controller(controller); } + + fn init_stage_type_list() -> gtk::StringList { + let stage_types: Vec<String> = ALL_STAGE_TYPES + .iter() + .map(stages::LocalizedStageType::localized_name) + .collect(); + let stage_type_refs: Vec<&str> = stage_types + .iter() + .map(std::string::String::as_str) + .collect(); + gtk::StringList::new(&stage_type_refs) + } + + fn init_source_buffer( + init: &AppInit, + sender: &ComponentSender<Self>, + ) -> (sourceview5::Buffer, String, Option<PathBuf>) { + let source = sourceview5::Buffer::new(None); + source.set_highlight_syntax(true); + + let language_manager = LanguageManager::default(); + if let Some(language) = language_manager.language("wmap") { + source.set_language(Some(&language)); + } + + let (initial_content, current_file) = if let Some(ref path) = init.file_path { + match std::fs::read_to_string(path) { + Ok(content) => (content, Some(path.clone())), + Err(_) => (String::new(), None), + } + } else { + (init.initial_content.clone().unwrap_or_default(), None) + }; + + source.set_text(&initial_content); + + { + let sender = sender.clone(); + source.connect_changed(move |_| { + sender.input(Action::SourceChanged); + }); + } + + (source, initial_content, current_file) + } + + fn init_drawing_area( + initial_content: &str, + ) -> ( + gtk::DrawingArea, + Map, + Configuration, + StageType, + ImageSurface, + ) { + let drawing_area = gtk::DrawingArea::new(); + let map = parse(initial_content); + let mut render_configuration = Configuration::default(); + let stage_type = StageType::Activities; + render_configuration.options.smart_label_positioning = true; + let surface = render_to_surface(&map, &stage_type.to_localized(), &render_configuration) + .or_else(|_| ImageSurface::create(cairo::Format::ARgb32, 1, 1)) + .unwrap_or_else(|e| { + eprintln!("Fatal: Cairo failed to create surface: {e}"); + std::process::exit(1); + }); + + (drawing_area, map, render_configuration, stage_type, surface) + } + + fn init_main_menu(prefs: &preferences::UserPreferences) -> (gio::Menu, gio::Menu) { + let templates_menu = gio::Menu::new(); + for template in &prefs.map_templates { + templates_menu.append( + Some(&template.name), + Some(&format!("window.new-from-template::{}", template.id)), + ); + } + + let main_menu = gio::Menu::new(); + main_menu.append(Some(&tr!("command.file.new")), Some("window.new")); + main_menu.append_submenu( + Some(&tr!("command.file.new_from_template")), + &templates_menu, + ); + main_menu.append(Some(&tr!("command.file.open")), Some("window.open")); + main_menu.append(Some(&tr!("command.file.save")), Some("window.save")); + main_menu.append(Some(&tr!("command.file.save_as")), Some("window.save-as")); + main_menu.append(Some(&tr!("command.file.close")), Some("window.close")); + main_menu.append( + Some(&tr!("command.file.export")), + Some("window.export-image"), + ); + + let layout_section = gio::Menu::new(); + layout_section.append( + Some(&tr!("command.view.use_vertical_layout")), + Some("window.change-orientation"), + ); + main_menu.append_section(None, &layout_section); + + let zoom_section = gio::Menu::new(); + zoom_section.append(Some(&tr!("command.view.zoom_in")), Some("window.zoom-in")); + zoom_section.append(Some(&tr!("command.view.zoom_out")), Some("window.zoom-out")); + main_menu.append_section(None, &zoom_section); + + let prefs_section = gio::Menu::new(); + prefs_section.append( + Some(&tr!("command.application.preferences")), + Some("window.preferences"), + ); + main_menu.append_section(None, &prefs_section); + + (templates_menu, main_menu) + } } #[relm4::component] @@ -496,40 +616,11 @@ impl SimpleComponent for AppModel { root: Self::Root, sender: ComponentSender<Self>, ) -> ComponentParts<Self> { - let stage_types: Vec<String> = ALL_STAGE_TYPES - .iter() - .map(|stage_type| stage_type.localized_name()) - .collect(); - let stage_type_refs: Vec<&str> = stage_types.iter().map(|s| s.as_str()).collect(); - let stage_type_list = gtk::StringList::new(&stage_type_refs); + let stage_type_list = Self::init_stage_type_list(); - let source = sourceview5::Buffer::new(None); - source.set_highlight_syntax(true); - - let language_manager = LanguageManager::default(); - if let Some(language) = language_manager.language("wmap") { - source.set_language(Some(&language)); - } - - let (initial_content, current_file) = if let Some(ref path) = init.file_path { - match std::fs::read_to_string(path) { - Ok(content) => (content, Some(path.clone())), - Err(_) => (String::new(), None), - } - } else { - (init.initial_content.unwrap_or_default(), None) - }; - - source.set_text(&initial_content); - - { - let sender = sender.clone(); - source.connect_changed(move |_| { - sender.input(Action::SourceChanged); - }); - } + let (source, initial_content, current_file) = Self::init_source_buffer(&init, &sender); - setup_actions(root.clone(), sender.clone()); + setup_actions(&root, &sender); let source_view = sourceview5::View::with_buffer(&source); source_view.set_monospace(true); @@ -540,55 +631,15 @@ impl SimpleComponent for AppModel { gtk::STYLE_PROVIDER_PRIORITY_APPLICATION, ); - let drawing_area = gtk::DrawingArea::new(); - let map = parse(&initial_content); - let mut render_configuration = Configuration::default(); - let stage_type = StageType::Activities; - render_configuration.options.smart_label_positioning = true; - let surface = - render_to_surface(&map, &stage_type.to_localized(), &render_configuration).unwrap_or_else(|_| { - ImageSurface::create(cairo::Format::ARgb32, 1, 1) - .expect("errors.render.failed_to_create_surface") - }); + let (drawing_area, map, render_configuration, stage_type, surface) = + Self::init_drawing_area(&initial_content); if let Some(ref path) = current_file { file_registry::register_file(path, &root); } let prefs = preferences::storage::load(); - - let templates_menu = gio::Menu::new(); - for template in &prefs.map_templates { - templates_menu.append( - Some(&template.name), - Some(&format!("window.new-from-template::{}", template.id)) - ); - } - - let main_menu = gio::Menu::new(); - main_menu.append(Some(&tr!("command.file.new")), Some("window.new")); - main_menu.append_submenu(Some(&tr!("command.file.new_from_template")), &templates_menu); - main_menu.append(Some(&tr!("command.file.open")), Some("window.open")); - main_menu.append(Some(&tr!("command.file.save")), Some("window.save")); - main_menu.append(Some(&tr!("command.file.save_as")), Some("window.save-as")); - main_menu.append(Some(&tr!("command.file.close")), Some("window.close")); - main_menu.append(Some(&tr!("command.file.export")), Some("window.export-image")); - - let layout_section = gio::Menu::new(); - layout_section.append( - Some(&tr!("command.view.use_vertical_layout")), - Some("window.change-orientation"), - ); - main_menu.append_section(None, &layout_section); - - let zoom_section = gio::Menu::new(); - zoom_section.append(Some(&tr!("command.view.zoom_in")), Some("window.zoom-in")); - zoom_section.append(Some(&tr!("command.view.zoom_out")), Some("window.zoom-out")); - main_menu.append_section(None, &zoom_section); - - let prefs_section = gio::Menu::new(); - prefs_section.append(Some(&tr!("command.application.preferences")), Some("window.preferences")); - main_menu.append_section(None, &prefs_section); + let (templates_menu, main_menu) = Self::init_main_menu(&prefs); let mut model = AppModel { orientation: gtk::Orientation::Horizontal, @@ -663,10 +714,10 @@ impl SimpleComponent for AppModel { Action::ZoomOut => handlers::zoom::zoom_out(self), Action::ExportImage => { - dialogs::show_export_dialog(&self.window, self.current_file.clone(), sender) + dialogs::show_export_dialog(&self.window, self.current_file.as_ref(), sender); } Action::ExportToFile { path, format } => { - handlers::export::export_to_file(self, path, format) + handlers::export::export_to_file(self, &path, format); } Action::New => { @@ -687,18 +738,18 @@ impl SimpleComponent for AppModel { } } Action::Open => { - dialogs::show_open_dialog(&self.window, self.is_empty_document(), sender) + dialogs::show_open_dialog(&self.window, self.is_empty_document(), sender); } Action::Save { close_after } => handlers::file::save(self, close_after, &sender), Action::SaveAs { close_after } => dialogs::show_save_dialog( &self.window, - self.current_file.clone(), + self.current_file.as_ref(), close_after, sender, ), Action::LoadFile(path) => handlers::file::load_file(self, path), Action::SaveToFile { path, close_after } => { - handlers::file::save_to_file(self, path, close_after) + handlers::file::save_to_file(self, path, close_after); } Action::CloseWindow => handlers::file::close_window(self, &sender), @@ -729,7 +780,7 @@ impl SimpleComponent for AppModel { self.rebuild_stage_type_list(); self.rebuild_templates_menu(); self.update_image(); - file_registry::broadcast_to_all_windows(Action::ReloadPreferences); + file_registry::broadcast_to_all_windows(&Action::ReloadPreferences); } Action::PreferencesWindowClosed => { self.preferences_window = None; diff --git a/src/preferences/models.rs b/src/preferences/models.rs index dee84e6..81fa4d1 100644 --- a/src/preferences/models.rs +++ b/src/preferences/models.rs @@ -17,7 +17,7 @@ use serde::{Deserialize, Serialize}; use uuid::Uuid; -/// Stage labels for custom stages (matches Swift Stage struct) +/// Stage labels for custom stages #[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq)] pub struct Stage { pub i: String, @@ -26,7 +26,7 @@ pub struct Stage { pub iv: String, } -/// Custom stage definition (matches Swift CustomStage) +/// Custom stage definition #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] #[serde(rename_all = "camelCase")] pub struct CustomStage { @@ -73,7 +73,7 @@ impl CustomStage { } } -/// Template for new maps (matches Swift Template) +/// Template for new maps #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] #[serde(rename_all = "camelCase")] pub struct Template { @@ -106,7 +106,7 @@ impl Template { } } -/// JSON import/export format (matches Swift UserPreferencesJSON) +/// JSON import/export format /// All fields optional to support partial imports #[derive(Debug, Clone, Serialize, Deserialize, Default)] #[serde(rename_all = "camelCase")] @@ -136,6 +136,7 @@ pub struct UserPreferencesJson { /// Main preferences struct stored in app #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] #[serde(rename_all = "camelCase")] +#[allow(clippy::struct_excessive_bools)] // These are independent boolean preferences, not a state machine pub struct UserPreferences { // Map preferences pub show_map_background: bool, @@ -218,10 +219,10 @@ impl UserPreferences { self.use_custom_font = *v; } if let Some(ref v) = json.custom_font_name { - self.custom_font_name = v.clone(); + self.custom_font_name.clone_from(v); } if let Some(ref v) = json.default_export_format { - self.default_export_format = v.clone(); + self.default_export_format.clone_from(v); } if let Some(v) = json.use_smart_label_positioning { self.use_smart_label_positioning = v; @@ -230,7 +231,7 @@ impl UserPreferences { self.use_custom_editor_font = *v; } if let Some(ref v) = json.custom_editor_font_name { - self.custom_editor_font_name = v.clone(); + self.custom_editor_font_name.clone_from(v); } if let Some(v) = json.editor_font_size { self.editor_font_size = v; @@ -239,13 +240,13 @@ impl UserPreferences { self.soft_wrap_lines = v; } if let Some(ref v) = json.map_templates { - self.map_templates = v.clone(); + self.map_templates.clone_from(v); } if let Some(ref v) = json.custom_stages { - self.custom_stages = v.clone(); + self.custom_stages.clone_from(v); } if let Some(ref v) = json.view_style { - self.view_style = v.clone(); + self.view_style.clone_from(v); } if let Some(v) = json.zoom { self.zoom = v; diff --git a/src/preferences/pages/editor.rs b/src/preferences/pages/editor.rs index d4acb94..336a488 100644 --- a/src/preferences/pages/editor.rs +++ b/src/preferences/pages/editor.rs @@ -33,7 +33,7 @@ pub struct EditorPage { } impl EditorPage { - pub fn new(preferences: &UserPreferences, sender: Sender<PreferencesInput>) -> Self { + 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); @@ -57,7 +57,9 @@ impl EditorPage { }); } font_size_box.append(&font_size_spin); - font_size_box.append(>k::Label::new(Some(&tr!("preferences.editor.font_size.unit")))); + font_size_box.append(>k::Label::new(Some(&tr!( + "preferences.editor.font_size.unit" + )))); container.append(&font_size_box); // Editor Style section @@ -68,7 +70,10 @@ impl EditorPage { container.append(&style_label); // Soft Wrap Lines - let soft_wrap_row = create_switch_row(&tr!("preferences.editor.editor_style.soft_wrap_lines"), preferences.soft_wrap_lines); + let soft_wrap_row = create_switch_row( + &tr!("preferences.editor.editor_style.soft_wrap_lines"), + preferences.soft_wrap_lines, + ); let soft_wrap_switch = soft_wrap_row.1.clone(); { let sender = sender.clone(); @@ -80,8 +85,10 @@ impl EditorPage { container.append(&soft_wrap_row.0); // Use Custom Font - let custom_font_row = - create_switch_row(&tr!("preferences.editor.editor_style.use_custom_font"), preferences.use_custom_editor_font); + let custom_font_row = create_switch_row( + &tr!("preferences.editor.editor_style.use_custom_font"), + preferences.use_custom_editor_font, + ); let custom_font_switch = custom_font_row.1.clone(); container.append(&custom_font_row.0); @@ -90,6 +97,8 @@ impl EditorPage { font_row.append(>k::Label::new(Some(&tr!("preferences.editor.font")))); let font_button = gtk::FontButton::new(); + + #[allow(clippy::cast_possible_truncation)] font_button.set_font(&format!( "{} {}", preferences.custom_editor_font_name, preferences.editor_font_size as i32 @@ -140,15 +149,16 @@ impl EditorPage { pub fn sync_from(&self, preferences: &UserPreferences) { self.font_size_spin.set_value(preferences.editor_font_size); - self.soft_wrap_switch.set_active(preferences.soft_wrap_lines); + 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)] // Font size is always a small positive number self.font_button.set_font(&format!( "{} {}", preferences.custom_editor_font_name, preferences.editor_font_size as i32 )); } } - diff --git a/src/preferences/pages/general.rs b/src/preferences/pages/general.rs index d70e9e6..ad15c82 100644 --- a/src/preferences/pages/general.rs +++ b/src/preferences/pages/general.rs @@ -28,7 +28,7 @@ pub struct GeneralPage { } impl GeneralPage { - pub fn new(sender: Sender<PreferencesInput>) -> Self { + 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); diff --git a/src/preferences/pages/map.rs b/src/preferences/pages/map.rs index 5ffd04a..4355c6f 100644 --- a/src/preferences/pages/map.rs +++ b/src/preferences/pages/map.rs @@ -34,7 +34,7 @@ pub struct MapPage { } impl MapPage { - pub fn new(preferences: &UserPreferences, sender: Sender<PreferencesInput>) -> Self { + 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); @@ -42,15 +42,36 @@ impl MapPage { container.set_margin_end(20); container.set_valign(gtk::Align::Start); - // Map Style section + 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: >k::Box, + preferences: &UserPreferences, + sender: &Sender<PreferencesInput>, + ) -> (gtk::Switch, gtk::Switch, gtk::Switch, gtk::FontButton) { 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 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(); @@ -77,7 +98,10 @@ impl MapPage { 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_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); @@ -115,7 +139,19 @@ impl MapPage { }); } - // Export section + ( + show_background_switch, + smart_positioning_switch, + custom_font_switch, + font_button, + ) + } + + fn init_export_section( + container: >k::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); @@ -123,9 +159,12 @@ impl MapPage { container.append(&export_label); let export_row = gtk::Box::new(gtk::Orientation::Horizontal, 10); - export_row.append(>k::Label::new(Some(&tr!("preferences.map.export.default_format")))); + export_row.append(>k::Label::new(Some(&tr!( + "preferences.map.export.default_format" + )))); - let formats = gtk::StringList::new(&[&tr!("export_formats.png"), &tr!("export_formats.svg")]); + 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, @@ -145,14 +184,7 @@ impl MapPage { export_row.append(&export_format_dropdown); container.append(&export_row); - Self { - container, - show_background_switch, - smart_positioning_switch, - custom_font_switch, - font_button, - export_format_dropdown, - } + export_format_dropdown } pub fn widget(&self) -> gtk::Box { @@ -176,4 +208,3 @@ impl MapPage { self.export_format_dropdown.set_selected(selected); } } - diff --git a/src/preferences/pages/stages.rs b/src/preferences/pages/stages.rs index 9f0685d..4bd24e1 100644 --- a/src/preferences/pages/stages.rs +++ b/src/preferences/pages/stages.rs @@ -60,25 +60,25 @@ impl StagesPage { name_header.set_xalign(0.0); header_row.append(&name_header); - let i_header = gtk::Label::new(Some(&tr!("preferences.stages.column.stage_i"))); - i_header.set_width_chars(12); - i_header.set_xalign(0.0); - header_row.append(&i_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 ii_header = gtk::Label::new(Some(&tr!("preferences.stages.column.stage_ii"))); - ii_header.set_width_chars(12); - ii_header.set_xalign(0.0); - header_row.append(&ii_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 iii_header = gtk::Label::new(Some(&tr!("preferences.stages.column.stage_iii"))); - iii_header.set_width_chars(12); - iii_header.set_xalign(0.0); - header_row.append(&iii_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 iv_header = gtk::Label::new(Some(&tr!("preferences.stages.column.stage_iv"))); - iv_header.set_width_chars(12); - iv_header.set_xalign(0.0); - header_row.append(&iv_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); @@ -172,12 +172,12 @@ impl StagesPage { hbox.append(&name_entry); // Stage I entry - let i_entry = gtk::Entry::new(); - i_entry.set_text(&stage.i); - i_entry.set_width_chars(12); + let first_entry = gtk::Entry::new(); + first_entry.set_text(&stage.i); + first_entry.set_width_chars(12); { let sender = self.sender.clone(); - i_entry.connect_changed(move |entry| { + first_entry.connect_changed(move |entry| { sender.emit(PreferencesInput::SetCustomStageLabel( id, StageLabel::I, @@ -185,15 +185,15 @@ impl StagesPage { )); }); } - hbox.append(&i_entry); + hbox.append(&first_entry); // Stage II entry - let ii_entry = gtk::Entry::new(); - ii_entry.set_text(&stage.ii); - ii_entry.set_width_chars(12); + let second_entry = gtk::Entry::new(); + second_entry.set_text(&stage.ii); + second_entry.set_width_chars(12); { let sender = self.sender.clone(); - ii_entry.connect_changed(move |entry| { + second_entry.connect_changed(move |entry| { sender.emit(PreferencesInput::SetCustomStageLabel( id, StageLabel::Ii, @@ -201,15 +201,15 @@ impl StagesPage { )); }); } - hbox.append(&ii_entry); + hbox.append(&second_entry); // Stage III entry - let iii_entry = gtk::Entry::new(); - iii_entry.set_text(&stage.iii); - iii_entry.set_width_chars(12); + let third_entry = gtk::Entry::new(); + third_entry.set_text(&stage.iii); + third_entry.set_width_chars(12); { let sender = self.sender.clone(); - iii_entry.connect_changed(move |entry| { + third_entry.connect_changed(move |entry| { sender.emit(PreferencesInput::SetCustomStageLabel( id, StageLabel::Iii, @@ -217,15 +217,15 @@ impl StagesPage { )); }); } - hbox.append(&iii_entry); + hbox.append(&third_entry); // Stage IV entry - let iv_entry = gtk::Entry::new(); - iv_entry.set_text(&stage.iv); - iv_entry.set_width_chars(12); + let fourth_entry = gtk::Entry::new(); + fourth_entry.set_text(&stage.iv); + fourth_entry.set_width_chars(12); { let sender = self.sender.clone(); - iv_entry.connect_changed(move |entry| { + fourth_entry.connect_changed(move |entry| { sender.emit(PreferencesInput::SetCustomStageLabel( id, StageLabel::Iv, @@ -233,7 +233,7 @@ impl StagesPage { )); }); } - hbox.append(&iv_entry); + hbox.append(&fourth_entry); // Delete button let delete_button = gtk::Button::from_icon_name("list-remove-symbolic"); diff --git a/src/preferences/pages/templates.rs b/src/preferences/pages/templates.rs index acc679f..1760085 100644 --- a/src/preferences/pages/templates.rs +++ b/src/preferences/pages/templates.rs @@ -78,9 +78,9 @@ impl TemplatesPage { { let sender = sender.clone(); add_button.connect_clicked(move |_| { - sender.emit(PreferencesInput::AddTemplate( - tr!("dialog.template.default_label"), - )); + sender.emit(PreferencesInput::AddTemplate(tr!( + "dialog.template.default_label" + ))); }); } button_box.append(&add_button); @@ -226,7 +226,7 @@ impl TemplatesPage { self.list_box.connect_row_selected(move |_, row| { if let Some(row) = row { - let index = row.index() as usize; + 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); @@ -251,7 +251,8 @@ impl TemplatesPage { 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 + .set_tooltip_text(Some(&tr!("preferences.templates.help.set_as_default"))); } default_button.add_css_class("flat"); { @@ -271,7 +272,10 @@ impl TemplatesPage { let sender = self.sender.clone(); let id = template.id; name_entry.connect_changed(move |entry| { - sender.emit(PreferencesInput::SetTemplateName(id, entry.text().to_string())); + sender.emit(PreferencesInput::SetTemplateName( + id, + entry.text().to_string(), + )); }); } hbox.append(&name_entry); diff --git a/src/preferences/window.rs b/src/preferences/window.rs index 2118056..0997716 100644 --- a/src/preferences/window.rs +++ b/src/preferences/window.rs @@ -143,9 +143,9 @@ impl SimpleComponent for PreferencesWindow { 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 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()); @@ -167,7 +167,6 @@ impl SimpleComponent for PreferencesWindow { fn update(&mut self, message: Self::Input, sender: ComponentSender<Self>) { match message { - // General page actions PreferencesInput::Export => { general::show_export_dialog(sender.input_sender(), &self.window); } @@ -188,8 +187,6 @@ impl SimpleComponent for PreferencesWindow { self.sync_ui_from_preferences(); self.save_and_notify(&sender); } - - // Map preferences PreferencesInput::SetShowMapBackground(v) => { self.preferences.show_map_background = v; self.save_and_notify(&sender); @@ -210,8 +207,6 @@ impl SimpleComponent for PreferencesWindow { self.preferences.use_smart_label_positioning = v; self.save_and_notify(&sender); } - - // Editor preferences PreferencesInput::SetUseCustomEditorFont(v) => { self.preferences.use_custom_editor_font = v; self.save_and_notify(&sender); @@ -228,88 +223,29 @@ impl SimpleComponent for PreferencesWindow { self.preferences.soft_wrap_lines = v; self.save_and_notify(&sender); } - - // Templates - PreferencesInput::AddTemplate(name) => { - 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); - } - PreferencesInput::RemoveTemplate(id) => { - self.preferences.map_templates.retain(|t| t.id != id); - self.templates_page.refresh(&self.preferences); - 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) => { - if let Some(template) = self - .preferences - .map_templates - .iter_mut() - .find(|t| t.id == id) - { - template.content = content; - self.save_and_notify(&sender); - } + self.handle_set_template_content(id, content, &sender); } PreferencesInput::SetTemplateName(id, name) => { - 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); - } + 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); } - - // Custom stages - PreferencesInput::AddCustomStage => { - let stage = super::models::CustomStage::placeholder_default(); - self.preferences.custom_stages.push(stage); - self.stages_page.refresh(&self.preferences); - self.save_and_notify(&sender); - } + PreferencesInput::AddCustomStage => self.handle_add_custom_stage(&sender), PreferencesInput::RemoveCustomStage(id) => { - self.preferences.custom_stages.retain(|s| s.id != id); - self.stages_page.refresh(&self.preferences); - self.save_and_notify(&sender); + self.handle_remove_custom_stage(id, &sender); } PreferencesInput::SetCustomStageName(id, name) => { - if let Some(stage) = self - .preferences - .custom_stages - .iter_mut() - .find(|s| s.id == id) - { - stage.name = name; - self.save_and_notify(&sender); - } + self.handle_set_custom_stage_name(id, name, &sender); } PreferencesInput::SetCustomStageLabel(id, label, value) => { - 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); - } + self.handle_set_custom_stage_label(id, label, value, &sender); } - PreferencesInput::Close => { sender.output(PreferencesOutput::Closed).ok(); } @@ -333,4 +269,105 @@ impl PreferencesWindow { 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); + } + } } diff --git a/src/stages.rs b/src/stages.rs index 8de8f89..3ab1f5a 100644 --- a/src/stages.rs +++ b/src/stages.rs @@ -14,8 +14,8 @@ // 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 wmap_renderer::StageType; use crate::tr; +use wmap_renderer::StageType; /// All available stage types for rendering Wardley Maps. /// Order determines dropdown menu order. @@ -77,7 +77,13 @@ impl LocalizedStageType for StageType { StageType::Behavior => tr!("stages.behavior.name"), StageType::Cynefin => tr!("stages.cynefin.name"), StageType::EvolutionStage => tr!("stages.evolution_stage.name"), - StageType::Custom{name, i: _, ii: _, iii: _, iv: _} => name.to_string() + StageType::Custom { + name, + i: _, + ii: _, + iii: _, + iv: _, + } => name.clone(), } } fn localized_i(&self) -> String { @@ -104,7 +110,13 @@ impl LocalizedStageType for StageType { StageType::Behavior => tr!("stages.behavior.i"), StageType::Cynefin => tr!("stages.cynefin.i"), StageType::EvolutionStage => tr!("stages.evolution_stage.i"), - StageType::Custom{name: _, i, ii: _, iii: _, iv: _} => i.to_string() + StageType::Custom { + name: _, + i, + ii: _, + iii: _, + iv: _, + } => i.clone(), } } fn localized_ii(&self) -> String { @@ -131,7 +143,13 @@ impl LocalizedStageType for StageType { StageType::Behavior => tr!("stages.behavior.ii"), StageType::Cynefin => tr!("stages.cynefin.ii"), StageType::EvolutionStage => tr!("stages.evolution_stage.ii"), - StageType::Custom{name: _, i: _, ii, iii: _, iv: _} => ii.to_string() + StageType::Custom { + name: _, + i: _, + ii, + iii: _, + iv: _, + } => ii.clone(), } } fn localized_iii(&self) -> String { @@ -158,7 +176,13 @@ impl LocalizedStageType for StageType { StageType::Behavior => tr!("stages.behavior.iii"), StageType::Cynefin => tr!("stages.cynefin.iii"), StageType::EvolutionStage => tr!("stages.evolution_stage.iii"), - StageType::Custom{name: _, i: _, ii: _, iii, iv: _} => iii.to_string() + StageType::Custom { + name: _, + i: _, + ii: _, + iii, + iv: _, + } => iii.clone(), } } fn localized_iv(&self) -> String { @@ -185,17 +209,23 @@ impl LocalizedStageType for StageType { StageType::Behavior => tr!("stages.behavior.iv"), StageType::Cynefin => tr!("stages.cynefin.iv"), StageType::EvolutionStage => tr!("stages.evolution_stage.iv"), - StageType::Custom{name: _, i: _, ii: _, iii: _, iv} => iv.to_string() + StageType::Custom { + name: _, + i: _, + ii: _, + iii: _, + iv, + } => iv.clone(), } } fn to_localized(&self) -> StageType { - StageType::Custom{ + StageType::Custom { name: self.localized_name(), i: self.localized_i(), ii: self.localized_ii(), iii: self.localized_iii(), - iv: self.localized_iv() + iv: self.localized_iv(), } } } |