diff options
| author | Rubén Beltrán del Río <jj@r.bdr.sh> | 2026-01-19 00:38:37 +0100 |
|---|---|---|
| committer | Rubén Beltrán del Río <jj@r.bdr.sh> | 2026-01-19 02:28:00 +0100 |
| commit | b41c4768d9b2e41532ae0018469809ff37280216 (patch) | |
| tree | cd73007bd2690efeca6ecfbc8eab4f61232bfb1f /src | |
| parent | a5ac6203f247d884aba8530b108d554ff80be333 (diff) | |
Update to use Gnome 48 features1.0.0
Diffstat (limited to 'src')
| -rw-r--r-- | src/dialogs.rs | 189 | ||||
| -rw-r--r-- | src/main.rs | 25 | ||||
| -rw-r--r-- | src/preferences/pages/editor.rs | 43 | ||||
| -rw-r--r-- | src/preferences/pages/general.rs | 58 | ||||
| -rw-r--r-- | src/preferences/pages/map.rs | 26 |
5 files changed, 156 insertions, 185 deletions
diff --git a/src/dialogs.rs b/src/dialogs.rs index 41b8c86..242f3d6 100644 --- a/src/dialogs.rs +++ b/src/dialogs.rs @@ -19,6 +19,7 @@ use std::path::PathBuf; use gtk::prelude::*; +use relm4::gtk::gio; use relm4::{ComponentSender, gtk}; use crate::AppModel; @@ -38,18 +39,13 @@ pub fn create_file_filter() -> gtk::FileFilter { /// Shows an error dialog with the given title and message. pub fn show_error(window: >k::Window, title: &str, message: &str) { - let dialog = gtk::MessageDialog::new( - Some(window), - gtk::DialogFlags::MODAL | gtk::DialogFlags::DESTROY_WITH_PARENT, - gtk::MessageType::Error, - gtk::ButtonsType::Ok, - message, - ); - dialog.set_title(Some(title)); - dialog.connect_response(|dialog, _| { - dialog.close(); - }); - dialog.show(); + let dialog = gtk::AlertDialog::builder() + .modal(true) + .buttons([&tr!("dialog.action.cancel"), "Ok"]) + .message(title) + .detail(message) + .build(); + dialog.show(Some(window)); } /// Shows a file open dialog and handles the response. @@ -58,21 +54,19 @@ pub fn show_open_dialog( is_empty_document: bool, sender: ComponentSender<AppModel>, ) { - let dialog = gtk::FileChooserDialog::new( - Some(&tr!("dialog.open.title")), - Some(window), - gtk::FileChooserAction::Open, - &[ - (&tr!("dialog.action.cancel"), gtk::ResponseType::Cancel), - (&tr!("dialog.action.open"), gtk::ResponseType::Accept), - ], - ); - dialog.add_filter(&create_file_filter()); + let dialog = gtk::FileDialog::builder() + .title(tr!("dialog.open.title")) + .modal(true) + .build(); - dialog.connect_response(move |dialog, response| { - dialog.close(); - if response == gtk::ResponseType::Accept - && let Some(file) = dialog.file() + let filter = create_file_filter(); + let filters = gtk::gio::ListStore::new::<gtk::FileFilter>(); + filters.append(&filter); + dialog.set_filters(Some(&filters)); + + let window_clone = window.clone(); + gtk::glib::spawn_future_local(async move { + if let Ok(file) = dialog.open_future(Some(&window_clone)).await && let Some(path) = file.path() { // Check if file is already open - if so, focus that window @@ -88,7 +82,6 @@ pub fn show_open_dialog( } } }); - dialog.present(); } /// Shows a file save dialog and handles the response. @@ -98,40 +91,38 @@ pub fn show_save_dialog( close_after: bool, sender: ComponentSender<AppModel>, ) { - let dialog = gtk::FileChooserDialog::new( - Some(&tr!("dialog.save.title")), - Some(window), - gtk::FileChooserAction::Save, - &[ - (&tr!("dialog.action.cancel"), gtk::ResponseType::Cancel), - (&tr!("dialog.action.save"), gtk::ResponseType::Accept), - ], - ); - dialog.add_filter(&create_file_filter()); + let mut dialog_builder = gtk::FileDialog::builder() + .title(tr!("dialog.save.title")) + .modal(true); + + let filter = create_file_filter(); + let filters = gtk::gio::ListStore::new::<gtk::FileFilter>(); + filters.append(&filter); + dialog_builder = dialog_builder.filters(&filters); let default_filename = "map.wmap"; 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(); + dialog_builder = dialog_builder.initial_folder(&folder); } if let Some(filename) = current_path.file_name() { - dialog.set_current_name(filename.to_str().unwrap_or(default_filename)); + dialog_builder = + dialog_builder.initial_name(filename.to_str().unwrap_or(default_filename)); } } else { - dialog.set_current_name(default_filename); + dialog_builder = dialog_builder.initial_name(default_filename); } - dialog.connect_response(move |dialog, response| { - dialog.close(); - if response == gtk::ResponseType::Accept - && let Some(file) = dialog.file() + let dialog = dialog_builder.build(); + let window_clone = window.clone(); + gtk::glib::spawn_future_local(async move { + if let Ok(file) = dialog.save_future(Some(&window_clone)).await && let Some(path) = file.path() { sender.input(Action::SaveToFile { path, close_after }); } }); - dialog.present(); } /// Shows a dialog asking whether to save unsaved changes before closing. @@ -140,39 +131,42 @@ pub fn show_unsaved_changes_dialog( current_file: Option<PathBuf>, sender: ComponentSender<AppModel>, ) { - let dialog = gtk::MessageDialog::new( - Some(window), - gtk::DialogFlags::MODAL | gtk::DialogFlags::DESTROY_WITH_PARENT, - gtk::MessageType::Warning, - gtk::ButtonsType::None, - tr!("dialog.unsaved_changes.message"), - ); - dialog.set_title(Some(&tr!("dialog.unsaved_changes.title"))); - dialog.add_button(&tr!("dialog.action.dont_save"), gtk::ResponseType::Reject); - dialog.add_button(&tr!("dialog.action.cancel"), gtk::ResponseType::Cancel); - dialog.add_button(&tr!("dialog.action.save"), gtk::ResponseType::Accept); + let dialog = gtk::AlertDialog::builder() + .modal(true) + .message(tr!("dialog.unsaved_changes.title")) + .detail(tr!("dialog.unsaved_changes.message")) + .buttons([ + tr!("dialog.action.dont_save"), + tr!("dialog.action.cancel"), + tr!("dialog.action.save"), + ]) + .cancel_button(1) + .default_button(2) + .build(); - let window = window.clone(); - dialog.connect_response(move |dialog, response| { - dialog.close(); - match response { - gtk::ResponseType::Accept => { + //dialog.show(Some(window)); + let cancellable = gio::Cancellable::current(); + let window_clone = window.clone(); + dialog.choose( + Some(window), + cancellable.as_ref(), + move |answer| match answer { + Ok(0) => { + if let Some(ref path) = current_file { + file_registry::unregister_file(path); + } + window_clone.destroy(); + } + Ok(2) => { if current_file.is_some() { sender.input(Action::Save { close_after: true }); } else { sender.input(Action::SaveAs { close_after: true }); } } - gtk::ResponseType::Reject => { - if let Some(ref path) = current_file { - file_registry::unregister_file(path); - } - window.destroy(); - } _ => {} - } - }); - dialog.show(); + }, + ); } /// Creates a file filter for an image format. @@ -194,20 +188,18 @@ pub fn show_export_dialog( current_file: Option<&PathBuf>, sender: ComponentSender<AppModel>, ) { - let dialog = gtk::FileChooserDialog::new( - Some(&tr!("dialog.export.title")), - Some(window), - gtk::FileChooserAction::Save, - &[ - (&tr!("dialog.action.cancel"), gtk::ResponseType::Cancel), - (&tr!("dialog.action.export"), gtk::ResponseType::Accept), - ], - ); - let png_filter = create_image_filter(ImageFormat::Png); let svg_filter = create_image_filter(ImageFormat::Svg); - dialog.add_filter(&png_filter); - dialog.add_filter(&svg_filter); + + let filters = gtk::gio::ListStore::new::<gtk::FileFilter>(); + filters.append(&png_filter); + filters.append(&svg_filter); + + let mut dialog_builder = gtk::FileDialog::builder() + .title(tr!("dialog.export.title")) + .modal(true) + .filters(&filters) + .default_filter(&png_filter); // Set default filename based on current file let base_name = current_file @@ -221,35 +213,19 @@ pub fn show_export_dialog( && let Some(parent) = current_path.parent() { let folder = gtk::gio::File::for_path(parent); - dialog.set_current_folder(Some(&folder)).ok(); + dialog_builder = dialog_builder.initial_folder(&folder); } - dialog.set_current_name(&format!("{base_name}.png")); - // Update extension when filter changes - { - let png_filter_ref = png_filter.clone(); - let base_name_clone = base_name.clone(); - dialog.connect_notify_local(Some("filter"), move |dialog, _| { - let ext = if dialog.filter().as_ref() == Some(&png_filter_ref) { - "png" - } else { - "svg" - }; - dialog.set_current_name(&format!("{base_name_clone}.{ext}")); - }); - } + dialog_builder = dialog_builder.initial_name(format!("{base_name}.png")); - let png_filter_clone = png_filter.clone(); - dialog.connect_response(move |dialog, response| { - dialog.close(); - if response == gtk::ResponseType::Accept - && let Some(file) = dialog.file() + let dialog = dialog_builder.build(); + let window_clone = window.clone(); + gtk::glib::spawn_future_local(async move { + if let Ok(file) = dialog.save_future(Some(&window_clone)).await && let Some(path) = file.path() { - // Determine format from selected filter or file extension - let format = if dialog.filter().as_ref() == Some(&png_filter_clone) { - ImageFormat::Png - } else if let Some(ext) = path.extension().and_then(|e| e.to_str()) { + // Determine format from file extension + let format = if let Some(ext) = path.extension().and_then(|e| e.to_str()) { match ext.to_lowercase().as_str() { "svg" => ImageFormat::Svg, _ => ImageFormat::Png, @@ -268,5 +244,4 @@ pub fn show_export_dialog( sender.input(Action::ExportToFile { path, format }); } }); - dialog.present(); } diff --git a/src/main.rs b/src/main.rs index 5aff910..41d3f68 100644 --- a/src/main.rs +++ b/src/main.rs @@ -43,7 +43,6 @@ use crate::icon_names::shipped::{ use cairo::ImageSurface; use gtk::gio; use gtk::glib; -use gtk::prelude::*; use relm4::{ Component, ComponentController, ComponentParts, ComponentSender, RelmApp, RelmWidgetExt, SimpleComponent, @@ -51,7 +50,7 @@ use relm4::{ gtk, }; use sourceview5::LanguageManager; -use sourceview5::prelude::{BufferExt, TextBufferExt, TextViewExt}; +use sourceview5::prelude::*; use stages::{ALL_STAGE_TYPES, LocalizedStageType}; use wmap_parser::{Map, parse}; use wmap_renderer::{Configuration, StageType, render_to_surface}; @@ -289,18 +288,18 @@ impl AppModel { let css = if self.preferences.use_custom_editor_font { self.source_view.set_monospace(false); format!( - "textview {{ font-family: \"{}\"; font-size: {}pt; }}", + ".map-editor {{ font-family: \"{}\"; font-size: {}pt; }}", self.preferences.custom_editor_font_name, self.preferences.editor_font_size as i32 ) } else { self.source_view.set_monospace(true); format!( - "textview {{ font-size: {}pt; }}", + ".map-editor {{ font-size: {}pt; }}", self.preferences.editor_font_size as i32 ) }; - self.editor_css_provider.load_from_data(&css); + self.editor_css_provider.load_from_string(&css); self.render_configuration.options.smart_label_positioning = self.preferences.use_smart_label_positioning; @@ -630,10 +629,14 @@ impl SimpleComponent for AppModel { source_view.set_monospace(true); let editor_css_provider = gtk::CssProvider::new(); - source_view.style_context().add_provider( - &editor_css_provider, - gtk::STYLE_PROVIDER_PRIORITY_APPLICATION, - ); + source_view.add_css_class("map-editor"); + if let Some(display) = gtk::gdk::Display::default() { + gtk::style_context_add_provider_for_display( + &display, + &editor_css_provider, + gtk::STYLE_PROVIDER_PRIORITY_APPLICATION, + ); + } let (drawing_area, map, render_configuration, stage_type, surface) = Self::init_drawing_area(&initial_content); @@ -755,7 +758,9 @@ impl SimpleComponent for AppModel { Action::SaveToFile { path, close_after } => { handlers::file::save_to_file(self, path, close_after); } - Action::CloseWindow => handlers::file::close_window(self, &sender), + Action::CloseWindow => { + handlers::file::close_window(self, &sender); + } Action::MarkInitialized => self.initialized = true, diff --git a/src/preferences/pages/editor.rs b/src/preferences/pages/editor.rs index 27b94da..c0e9b41 100644 --- a/src/preferences/pages/editor.rs +++ b/src/preferences/pages/editor.rs @@ -31,7 +31,7 @@ pub struct EditorPage { font_size_scale: gtk::Scale, soft_wrap_switch: gtk::Switch, custom_font_switch: gtk::Switch, - font_button: gtk::FontButton, + font_button: gtk::FontDialogButton, } impl EditorPage { @@ -124,21 +124,23 @@ impl EditorPage { (soft_wrap_row.1, custom_font_row.1) } - fn create_font_button(preferences: &UserPreferences) -> gtk::FontButton { - let button = gtk::FontButton::new(); + fn create_font_button(preferences: &UserPreferences) -> gtk::FontDialogButton { + let font_dialog = gtk::FontDialog::new(); + let font_button = gtk::FontDialogButton::new(Some(font_dialog)); #[allow(clippy::cast_possible_truncation)] - button.set_font(&format!( + let font_desc = gtk::pango::FontDescription::from_string(&format!( "{} {}", preferences.custom_editor_font_name, preferences.editor_font_size as i32 )); - button.set_sensitive(preferences.use_custom_editor_font); - button.set_hexpand(true); - button.set_use_font(true); - button.set_use_size(false); - button + font_button.set_font_desc(&font_desc); + font_button.set_sensitive(preferences.use_custom_editor_font); + font_button.set_hexpand(true); + font_button.set_use_font(true); + font_button.set_use_size(false); + font_button } - fn create_font_row(font_button: >k::FontButton) -> gtk::Box { + fn create_font_row(font_button: >k::FontDialogButton) -> gtk::Box { let row = gtk::Box::new(gtk::Orientation::Horizontal, 10); row.append(>k::Label::new(Some(&tr!("preferences.editor.font")))); row.append(font_button); @@ -150,7 +152,7 @@ impl EditorPage { font_size_scale: >k::Scale, soft_wrap_switch: >k::Switch, custom_font_switch: >k::Switch, - font_button: >k::FontButton, + font_button: >k::FontDialogButton, ) { let s = sender.clone(); let fb = font_button.clone(); @@ -167,15 +169,14 @@ impl EditorPage { let s = sender.clone(); let scale = font_size_scale.clone(); - font_button.connect_font_set(move |button| { - if let Some(font) = button.font() { - let font_desc = gtk::pango::FontDescription::from_string(&font); - if let Some(family) = font_desc.family() { + font_button.connect_font_desc_notify(move |button| { + if let Some(font) = button.font_desc() { + if let Some(family) = font.family() { s.emit(PreferencesInput::SetCustomEditorFontName( family.to_string(), )); } - let size = font_desc.size() / gtk::pango::SCALE; + let size = font.size() / gtk::pango::SCALE; if size > 0 { scale.set_value(f64::from(size)); } @@ -191,12 +192,11 @@ impl EditorPage { }); } - fn update_font_button_size(font_button: >k::FontButton, size: f64) { - if let Some(font) = font_button.font() { - let mut font_desc = gtk::pango::FontDescription::from_string(&font); + fn update_font_button_size(font_button: >k::FontDialogButton, size: f64) { + if let Some(mut font_desc) = font_button.font_desc() { #[allow(clippy::cast_possible_truncation)] font_desc.set_size(size as i32 * gtk::pango::SCALE); - font_button.set_font(&font_desc.to_string()); + font_button.set_font_desc(&font_desc); } } @@ -213,9 +213,10 @@ impl EditorPage { self.font_button .set_sensitive(preferences.use_custom_editor_font); #[allow(clippy::cast_possible_truncation)] - self.font_button.set_font(&format!( + let font_desc = gtk::pango::FontDescription::from_string(&format!( "{} {}", preferences.custom_editor_font_name, preferences.editor_font_size as i32 )); + self.font_button.set_font_desc(&font_desc); } } diff --git a/src/preferences/pages/general.rs b/src/preferences/pages/general.rs index ad15c82..d1596c4 100644 --- a/src/preferences/pages/general.rs +++ b/src/preferences/pages/general.rs @@ -97,31 +97,25 @@ pub fn show_export_dialog(sender: &Sender<PreferencesInput>, window: >k::Windo filter.add_pattern("*.json"); filter.set_name(Some(&tr!("dialog.file_type.json"))); - let dialog = gtk::FileChooserDialog::new( - Some(&tr!("dialog.export_preferences.title")), - Some(window), - gtk::FileChooserAction::Save, - &[ - (&tr!("dialog.action.cancel"), gtk::ResponseType::Cancel), - (&tr!("dialog.action.save"), gtk::ResponseType::Accept), - ], - ); - dialog.set_current_name("preferences.json"); - dialog.add_filter(&filter); - dialog.set_modal(true); + let filters = gtk::gio::ListStore::new::<gtk::FileFilter>(); + filters.append(&filter); + + let dialog = gtk::FileDialog::builder() + .title(tr!("dialog.export_preferences.title")) + .modal(true) + .filters(&filters) + .initial_name("preferences.json") + .build(); let sender = sender.clone(); - dialog.connect_response(move |dialog, response| { - if response == gtk::ResponseType::Accept - && let Some(file) = dialog.file() + let window = window.clone(); + gtk::glib::spawn_future_local(async move { + if let Ok(file) = dialog.save_future(Some(&window)).await && let Some(path) = file.path() { sender.emit(PreferencesInput::ExportToFile(path)); } - dialog.close(); }); - - dialog.present(); } pub fn show_import_dialog(sender: &Sender<PreferencesInput>, window: >k::Window) { @@ -129,28 +123,22 @@ pub fn show_import_dialog(sender: &Sender<PreferencesInput>, window: >k::Windo filter.add_pattern("*.json"); filter.set_name(Some(&tr!("dialog.file_type.json"))); - let dialog = gtk::FileChooserDialog::new( - Some(&tr!("dialog.import_preferences.title")), - Some(window), - gtk::FileChooserAction::Open, - &[ - (&tr!("dialog.action.cancel"), gtk::ResponseType::Cancel), - (&tr!("dialog.action.open"), gtk::ResponseType::Accept), - ], - ); - dialog.add_filter(&filter); - dialog.set_modal(true); + let filters = gtk::gio::ListStore::new::<gtk::FileFilter>(); + filters.append(&filter); + + let dialog = gtk::FileDialog::builder() + .title(tr!("dialog.import_preferences.title")) + .modal(true) + .filters(&filters) + .build(); let sender = sender.clone(); - dialog.connect_response(move |dialog, response| { - if response == gtk::ResponseType::Accept - && let Some(file) = dialog.file() + let window = window.clone(); + gtk::glib::spawn_future_local(async move { + if let Ok(file) = dialog.open_future(Some(&window)).await && let Some(path) = file.path() { sender.emit(PreferencesInput::ImportFromFile(path)); } - dialog.close(); }); - - dialog.present(); } diff --git a/src/preferences/pages/map.rs b/src/preferences/pages/map.rs index 4355c6f..5645a59 100644 --- a/src/preferences/pages/map.rs +++ b/src/preferences/pages/map.rs @@ -14,9 +14,9 @@ // 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 gtk::prelude::*; use relm4::Sender; use relm4::gtk; +use sourceview5::prelude::*; use crate::tr; use crate::ui_helpers::create_switch_row; @@ -29,7 +29,7 @@ pub struct MapPage { show_background_switch: gtk::Switch, smart_positioning_switch: gtk::Switch, custom_font_switch: gtk::Switch, - font_button: gtk::FontButton, + font_button: gtk::FontDialogButton, export_format_dropdown: gtk::DropDown, } @@ -61,7 +61,7 @@ impl MapPage { container: >k::Box, preferences: &UserPreferences, sender: &Sender<PreferencesInput>, - ) -> (gtk::Switch, gtk::Switch, gtk::Switch, gtk::FontButton) { + ) -> (gtk::Switch, gtk::Switch, gtk::Switch, gtk::FontDialogButton) { 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); @@ -109,19 +109,20 @@ impl MapPage { let font_row = gtk::Box::new(gtk::Orientation::Horizontal, 10); font_row.append(>k::Label::new(Some(&tr!("preferences.map.font")))); - let font_button = gtk::FontButton::new(); - font_button.set_font(&preferences.custom_font_name); + let font_dialog = gtk::FontDialog::new(); + let font_button = gtk::FontDialogButton::new(Some(font_dialog)); + let font_desc = gtk::pango::FontDescription::from_string(&preferences.custom_font_name); + font_button.set_font_desc(&font_desc); font_button.set_sensitive(preferences.use_custom_font); font_button.set_hexpand(true); font_button.set_use_font(true); { let sender = sender.clone(); - font_button.connect_font_set(move |button| { - if let Some(font) = button.font() { - let font_desc = gtk::pango::FontDescription::from_string(&font); - if let Some(family) = font_desc.family() { - sender.emit(PreferencesInput::SetCustomFontName(family.to_string())); - } + font_button.connect_font_desc_notify(move |button| { + if let Some(font) = button.font_desc() + && let Some(family) = font.family() + { + sender.emit(PreferencesInput::SetCustomFontName(family.to_string())); } }); } @@ -199,7 +200,8 @@ impl MapPage { self.custom_font_switch .set_active(preferences.use_custom_font); self.font_button.set_sensitive(preferences.use_custom_font); - self.font_button.set_font(&preferences.custom_font_name); + let font_desc = gtk::pango::FontDescription::from_string(&preferences.custom_font_name); + self.font_button.set_font_desc(&font_desc); let selected = match preferences.default_export_format.as_str() { "svg" => 1, |