Skip to main content

ferroui_dialogs/
managed_file_dialog_extensions.rs

1use crate::{ManagedFileDialogOptions, ManagedStorageProvider};
2use ferroui_base::platform::storage::IStorageProvider;
3use ferroui_base::{FerroLocator, LocatorExtensions, ObjectType, Ref, Upcast};
4use ferroui_controls::platform::{IMountedVolumeInfoProvider, IStorageProviderFactory};
5use ferroui_controls::{AppBuilder, ContentControl, TopLevel, Window};
6use std::rc::Rc;
7
8/// The factory of the managed storage providers: one per top level, with
9/// the options the application registered.
10pub(crate) struct ManagedStorageProviderFactory {
11    options: Option<ManagedFileDialogOptions>,
12}
13
14impl ManagedStorageProviderFactory {
15    pub(crate) fn new(options: Option<ManagedFileDialogOptions>) -> Self {
16        Self { options }
17    }
18}
19
20impl IStorageProviderFactory for ManagedStorageProviderFactory {
21    fn create_provider(&self, top_level: &Ref<TopLevel>) -> Rc<dyn IStorageProvider> {
22        Rc::new(ManagedStorageProvider::new(Some(top_level), self.options.clone()))
23    }
24}
25
26/// The managed dialogs extension of the application builder: replaces the
27/// file pickers of the platform with the managed file chooser.
28pub trait ManagedFileDialogExtensions {
29    /// Uses the managed file chooser for the file and folder pickers of
30    /// every top level, shown in a window (or in a popup over the content
31    /// of a top level that is not a window).
32    fn use_managed_system_dialogs(&self) -> AppBuilder;
33
34    /// Uses the managed file chooser for the file and folder pickers of
35    /// every top level, shown in a new `TWindow`.
36    ///
37    /// The class model states the `Window` constraint of the original; its
38    /// `new()` constraint is checked when the extension is used.
39    ///
40    /// # Panics
41    /// Panics when `TWindow` has no parameterless constructor.
42    fn use_managed_system_dialogs_with<TWindow: ObjectType + Upcast<Window>>(&self) -> AppBuilder;
43}
44
45/// The options registered with the locator, with `custom_root_factory` as
46/// their content root factory when both are given.
47fn prepare_options(
48    options_override: Option<ManagedFileDialogOptions>,
49    custom_root_factory: Option<Rc<dyn Fn() -> Ref<ContentControl>>>,
50) -> Option<ManagedFileDialogOptions> {
51    let mut options =
52        options_override.or_else(|| FerroLocator::current().get_service::<ManagedFileDialogOptions>().map(|o| (*o).clone()));
53    if let (Some(options), Some(custom_root_factory)) = (options.as_mut(), custom_root_factory) {
54        options.set_content_root_factory(Some(custom_root_factory));
55    }
56
57    options
58}
59
60fn use_managed_system_dialogs(builder: &AppBuilder, custom_factory: Option<Rc<dyn Fn() -> Ref<ContentControl>>>) -> AppBuilder {
61    builder.after_setup(move |_| {
62        let options = prepare_options(None, custom_factory.clone());
63        FerroLocator::current_mutable()
64            .bind::<dyn IStorageProviderFactory>()
65            .to_constant(Rc::new(ManagedStorageProviderFactory::new(options.clone())));
66        if let Some(custom_volume_info_provider) = options.as_ref().and_then(|options| options.custom_volume_info_provider()) {
67            FerroLocator::current_mutable()
68                .bind::<dyn IMountedVolumeInfoProvider>()
69                .to_constant(custom_volume_info_provider.clone());
70        }
71    });
72    builder.clone()
73}
74
75impl ManagedFileDialogExtensions for AppBuilder {
76    fn use_managed_system_dialogs(&self) -> AppBuilder {
77        use_managed_system_dialogs(self, None)
78    }
79
80    fn use_managed_system_dialogs_with<TWindow: ObjectType + Upcast<Window>>(&self) -> AppBuilder {
81        let constructor = TWindow::TYPE
82            .default_constructor()
83            .unwrap_or_else(|| panic!("{} has no parameterless constructor.", TWindow::TYPE.name()));
84        use_managed_system_dialogs(
85            self,
86            Some(Rc::new(move || {
87                constructor().cast::<Window>().expect("the window type derives from Window").upcast::<ContentControl>()
88            })),
89        )
90    }
91}
92
93#[cfg(test)]
94mod tests {
95    // Not from upstream: the upstream project has no tests.
96    use super::*;
97    use ferroui_controls::platform::MountedVolumeInfo;
98    use ferroui_base::collections::FerroList;
99    use ferroui_base::reactive::{Disposable, IDisposable};
100
101    struct NoVolumes;
102
103    impl IMountedVolumeInfoProvider for NoVolumes {
104        fn listen(&self, _mounted_drives: FerroList<MountedVolumeInfo>) -> Rc<dyn IDisposable> {
105            Disposable::empty()
106        }
107    }
108
109    /// Isolates the dispatcher and the service locator.
110    struct Scope {
111        locator: Rc<dyn IDisposable>,
112        _dispatcher: ferroui_base::threading::UnitTestDispatcherScope,
113    }
114
115    fn scope() -> Scope {
116        let dispatcher = ferroui_base::threading::Dispatcher::unit_test_scope();
117        Scope { locator: FerroLocator::enter_scope(), _dispatcher: dispatcher }
118    }
119
120    impl Drop for Scope {
121        fn drop(&mut self) {
122            self.locator.dispose();
123        }
124    }
125
126    #[test]
127    fn prepare_options_prefers_the_override_and_applies_the_root_factory() {
128        let _scope = scope();
129        let mut registered = ManagedFileDialogOptions::new();
130        registered.set_allow_directory_selection(true);
131        let factory: Rc<dyn Fn() -> Ref<ContentControl>> = Rc::new(ContentControl::new);
132
133        let prepared = prepare_options(Some(registered.clone()), Some(factory.clone())).unwrap();
134        assert!(prepared.allow_directory_selection());
135        assert!(std::ptr::addr_eq(Rc::as_ptr(prepared.content_root_factory().unwrap()), Rc::as_ptr(&factory)));
136
137        // Without options, the root factory alone gives none.
138        assert!(prepare_options(None, Some(factory.clone())).is_none());
139
140        // The options registered with the locator are the default.
141        FerroLocator::current_mutable().bind::<ManagedFileDialogOptions>().to_constant(Rc::new(registered));
142        let prepared = prepare_options(None, Some(factory)).unwrap();
143        assert!(prepared.allow_directory_selection());
144        assert!(prepared.content_root_factory().is_some());
145    }
146
147    #[test]
148    fn use_managed_system_dialogs_registers_the_factory_and_the_volume_provider_after_setup() {
149        let _scope = scope();
150        let mut options = ManagedFileDialogOptions::new();
151        let provider: Rc<dyn IMountedVolumeInfoProvider> = Rc::new(NoVolumes);
152        options.set_custom_volume_info_provider(Some(provider.clone()));
153        FerroLocator::current_mutable().bind::<ManagedFileDialogOptions>().to_constant(Rc::new(options));
154
155        let builder = AppBuilder::configure::<ferroui_controls::Application>().use_managed_system_dialogs();
156        assert!(FerroLocator::current().get_service::<dyn IStorageProviderFactory>().is_none());
157        (builder.after_setup_callback())(&builder);
158
159        assert!(FerroLocator::current().get_service::<dyn IStorageProviderFactory>().is_some());
160        let registered = FerroLocator::current().get_service::<dyn IMountedVolumeInfoProvider>().unwrap();
161        assert!(std::ptr::addr_eq(Rc::as_ptr(&registered), Rc::as_ptr(&provider)));
162    }
163}