1use super::{
2 BclMountedVolumeInfoProvider, FerroDialogsInternalViewModelBase, ManagedFileChooserFilterViewModel,
3 ManagedFileChooserItemType, ManagedFileChooserItemViewModel, ManagedFileChooserSources,
4};
5use crate::ManagedFileDialogOptions;
6use ferroui_base::animation::TimeSpan;
7use ferroui_base::collections::FerroList;
8use ferroui_base::data::model::{Event, INotifyPropertyChanged};
9use ferroui_base::metadata::from_markup_value;
10use ferroui_base::platform::storage::file_io::path;
11use ferroui_base::platform::storage::{FilePickerOpenOptions, FilePickerSaveOptions, FolderPickerOpenOptions, IStorageFolder, IStorageItem};
12use ferroui_base::reactive::{CompositeDisposable, Disposable, IDisposable};
13use ferroui_base::threading::{Dispatcher, DispatcherPriority};
14use ferroui_base::utilities::{DateTime, DateTimeOffset, HandlerList, StringComparison};
15use ferroui_base::{ferro_markup_type, BoxedValue, FerroLocator, LocatorExtensions};
16use ferroui_controls::platform::IMountedVolumeInfoProvider;
17use ferroui_controls::primitives::SelectedItemsList;
18use ferroui_controls::SelectionMode;
19use std::cell::{Cell, RefCell};
20use std::rc::{Rc, Weak};
21
22pub struct ManagedFileChooserViewModel {
25 base: FerroDialogsInternalViewModelBase,
26 options: ManagedFileDialogOptions,
27 cancel_requested: Rc<HandlerList<dyn Fn()>>,
28 complete_requested: Rc<HandlerList<dyn Fn(&[String])>>,
29 overwrite_prompt: Rc<HandlerList<dyn Fn(&str)>>,
30
31 quick_links: FerroList<Rc<ManagedFileChooserItemViewModel>>,
32 items: FerroList<Rc<ManagedFileChooserItemViewModel>>,
33 filters: FerroList<Rc<ManagedFileChooserFilterViewModel>>,
34 selected_items: SelectedItemsList,
35
36 location: RefCell<Option<String>>,
37 file_name: RefCell<Option<String>>,
38 show_hidden_files: Cell<bool>,
39 selected_filter: RefCell<Option<Rc<ManagedFileChooserFilterViewModel>>>,
40 selecting_directory: Cell<bool>,
41 saving_file: Cell<bool>,
42 scheduled_selection_validation: Cell<bool>,
43 already_cancelled: Cell<bool>,
44 default_extension: RefCell<Option<String>>,
45 overwrite_prompt_enabled: Cell<bool>,
46 disposables: CompositeDisposable,
47
48 show_filters: Cell<bool>,
49 selection_mode: Cell<SelectionMode>,
50 title: RefCell<Option<String>>,
51}
52
53impl PartialEq for ManagedFileChooserViewModel {
54 fn eq(&self, other: &Self) -> bool {
55 std::ptr::eq(self, other)
56 }
57}
58
59impl INotifyPropertyChanged for ManagedFileChooserViewModel {
60 fn property_changed(&self) -> &Event<str> {
61 self.base.property_changed()
62 }
63}
64
65fn item_of(value: &Option<BoxedValue>) -> Option<Rc<ManagedFileChooserItemViewModel>> {
68 from_markup_value::<Rc<ManagedFileChooserItemViewModel>>(value)
69}
70
71fn boxed(item: &Rc<ManagedFileChooserItemViewModel>) -> Option<BoxedValue> {
72 Some(item.clone() as BoxedValue)
73}
74
75fn is_null_or_white_space(value: Option<&str>) -> bool {
76 value.is_none_or(|value| value.trim().is_empty())
77}
78
79fn combine(path1: &str, path2: &str) -> String {
82 if path1.is_empty() || std::path::Path::new(path2).has_root() || path2.starts_with('/') {
83 return path2.to_string();
84 }
85 if path1.ends_with(path::is_directory_separator) {
86 format!("{path1}{path2}")
87 } else {
88 format!("{path1}{}{path2}", std::path::MAIN_SEPARATOR)
89 }
90}
91
92fn get_current_directory() -> String {
94 std::env::current_dir().map(|path| path.to_string_lossy().into_owned()).unwrap_or_default()
95}
96
97fn directory_exists(path: Option<&str>) -> bool {
98 path.is_some_and(|path| !path.is_empty() && std::path::Path::new(path).is_dir())
99}
100
101fn file_exists(path: Option<&str>) -> bool {
102 path.is_some_and(|path| !path.is_empty() && std::path::Path::new(path).is_file())
103}
104
105fn last_write_time(metadata: &std::fs::Metadata) -> DateTime {
107 const NANOSECONDS_PER_TICK: u128 = 100;
108
109 let Ok(time) = metadata.modified() else {
110 return DateTime::default();
111 };
112 let ticks = match time.duration_since(std::time::SystemTime::UNIX_EPOCH) {
113 Ok(after) => (after.as_nanos() / NANOSECONDS_PER_TICK) as i64,
114 Err(before) => -((before.duration().as_nanos() / NANOSECONDS_PER_TICK) as i64),
115 };
116 DateTimeOffset::UNIX_EPOCH.add(TimeSpan::from_ticks(ticks)).local_date_time()
117}
118
119#[cfg(windows)]
122fn is_hidden(_name: &str, metadata: &std::fs::Metadata) -> bool {
123 use std::os::windows::fs::MetadataExt;
124
125 const FILE_ATTRIBUTE_HIDDEN: u32 = 0x2;
126 const FILE_ATTRIBUTE_SYSTEM: u32 = 0x4;
127 metadata.file_attributes() & (FILE_ATTRIBUTE_HIDDEN | FILE_ATTRIBUTE_SYSTEM) != 0
128}
129
130#[cfg(not(windows))]
131fn is_hidden(name: &str, _metadata: &std::fs::Metadata) -> bool {
132 name.starts_with('.')
133}
134
135struct FileSystemEntry {
137 name: String,
138 full_name: String,
139 is_directory: bool,
140 metadata: std::fs::Metadata,
141}
142
143fn enumerate_file_system_infos(directory: &str) -> std::io::Result<Vec<FileSystemEntry>> {
149 let mut entries = Vec::new();
150 for entry in std::fs::read_dir(directory)? {
151 let entry = entry?;
152 let full_path = entry.path();
153 let Ok(metadata) = std::fs::metadata(&full_path).or_else(|_| std::fs::symlink_metadata(&full_path)) else {
154 continue;
155 };
156 entries.push(FileSystemEntry {
157 name: entry.file_name().to_string_lossy().into_owned(),
158 full_name: full_path.to_string_lossy().into_owned(),
159 is_directory: metadata.is_dir(),
160 metadata,
161 });
162 }
163 Ok(entries)
164}
165
166impl ManagedFileChooserViewModel {
167 pub fn new(options: ManagedFileDialogOptions) -> Rc<Self> {
169 let this = Rc::new(Self {
170 base: FerroDialogsInternalViewModelBase::new(),
171 options,
172 cancel_requested: Rc::new(HandlerList::new()),
173 complete_requested: Rc::new(HandlerList::new()),
174 overwrite_prompt: Rc::new(HandlerList::new()),
175 quick_links: FerroList::new(),
176 items: FerroList::new(),
177 filters: FerroList::new(),
178 selected_items: SelectedItemsList::new(),
179 location: RefCell::new(None),
180 file_name: RefCell::new(None),
181 show_hidden_files: Cell::new(false),
182 selected_filter: RefCell::new(None),
183 selecting_directory: Cell::new(false),
184 saving_file: Cell::new(false),
185 scheduled_selection_validation: Cell::new(false),
186 already_cancelled: Cell::new(false),
187 default_extension: RefCell::new(None),
188 overwrite_prompt_enabled: Cell::new(false),
189 disposables: CompositeDisposable::new(),
190 show_filters: Cell::new(false),
191 selection_mode: Cell::new(SelectionMode::SINGLE),
192 title: RefCell::new(None),
193 });
194
195 let quick_sources =
196 FerroLocator::current().get_service::<ManagedFileChooserSources>().unwrap_or_else(|| Rc::new(ManagedFileChooserSources::new()));
197
198 let sub1 = FerroLocator::current()
199 .get_service::<dyn IMountedVolumeInfoProvider>()
200 .unwrap_or_else(|| Rc::new(BclMountedVolumeInfoProvider))
201 .listen(ManagedFileChooserSources::mounted_volumes());
202
203 let sub2 = {
206 let mounted_volumes = ManagedFileChooserSources::mounted_volumes();
207 let weak = Rc::downgrade(&this);
208 let quick_sources = quick_sources.clone();
209 let token = mounted_volumes.add_collection_changed(Rc::new(move |_| {
210 let weak = weak.clone();
211 let quick_sources = quick_sources.clone();
212 Dispatcher::ui_thread().post_local(
213 move || {
214 if let Some(this) = weak.upgrade() {
215 this.refresh_quick_links(&quick_sources);
216 }
217 },
218 DispatcherPriority::DEFAULT,
219 );
220 }));
221 Disposable::create(move || {
222 mounted_volumes.remove_collection_changed(token);
223 })
224 };
225
226 this.disposables.add(sub1);
227 this.disposables.add(sub2);
228
229 let weak = Rc::downgrade(&this);
230 this.complete_requested.add(Rc::new(move |_: &[String]| {
231 if let Some(this) = weak.upgrade() {
232 this.disposables.dispose();
233 }
234 }));
235 let weak = Rc::downgrade(&this);
236 this.cancel_requested.add(Rc::new(move || {
237 if let Some(this) = weak.upgrade() {
238 this.disposables.dispose();
239 }
240 }));
241
242 this.refresh_quick_links(&quick_sources);
243
244 let weak = Rc::downgrade(&this);
245 this.selected_items.add_collection_changed(Rc::new(move |_| {
246 if let Some(this) = weak.upgrade() {
247 this.on_selection_changed_async();
248 }
249 }));
250
251 this
252 }
253
254 pub fn new_open_file(file_picker_open: &FilePickerOpenOptions, options: ManagedFileDialogOptions) -> Rc<Self> {
256 let this = Self::new(options);
257 *this.title.borrow_mut() = Some(file_picker_open.title().unwrap_or("Open file").to_string());
258
259 if let Some(file_type_filter) = file_picker_open.file_type_filter().filter(|filter| !filter.is_empty()) {
260 this.filters.add_range(
261 file_type_filter
262 .iter()
263 .enumerate()
264 .map(|(i, f)| ManagedFileChooserFilterViewModel::new_with_index(f, i as i32)),
265 );
266 *this.selected_filter.borrow_mut() = Some(this.filters.get(0));
267 this.show_filters.set(true);
268 }
269
270 if file_picker_open.allow_multiple() {
271 this.selection_mode.set(SelectionMode::MULTIPLE);
272 }
273
274 this.navigate_folder(file_picker_open.suggested_start_location(), None);
275 this
276 }
277
278 pub fn new_save_file(file_picker_save: &FilePickerSaveOptions, options: ManagedFileDialogOptions) -> Rc<Self> {
280 let this = Self::new(options);
281 *this.title.borrow_mut() = Some(file_picker_save.title().unwrap_or("Save file").to_string());
282
283 if let Some(file_type_choices) = file_picker_save.file_type_choices().filter(|choices| !choices.is_empty()) {
284 this.filters.add_range(
285 file_type_choices
286 .iter()
287 .enumerate()
288 .map(|(i, f)| ManagedFileChooserFilterViewModel::new_with_index(f, i as i32)),
289 );
290 *this.selected_filter.borrow_mut() = Some(this.filters.get(0));
291 this.show_filters.set(true);
292 }
293
294 this.saving_file.set(true);
295 *this.default_extension.borrow_mut() = file_picker_save.default_extension().map(str::to_string);
296 this.overwrite_prompt_enabled.set(file_picker_save.show_overwrite_prompt().unwrap_or(true));
297 this.set_file_name(file_picker_save.suggested_file_name().map(str::to_string));
298
299 let file_name = this.file_name();
300 this.navigate_folder(file_picker_save.suggested_start_location(), file_name.as_deref());
301 this
302 }
303
304 pub fn new_open_folder(folder_picker_open: &FolderPickerOpenOptions, options: ManagedFileDialogOptions) -> Rc<Self> {
306 let this = Self::new(options);
307 *this.title.borrow_mut() = Some(folder_picker_open.title().unwrap_or("Select directory").to_string());
308
309 this.selecting_directory.set(true);
310
311 if folder_picker_open.allow_multiple() {
312 this.selection_mode.set(SelectionMode::MULTIPLE);
313 }
314
315 this.navigate_folder(folder_picker_open.suggested_start_location(), None);
316 this
317 }
318
319 pub fn cancel_requested(&self, handler: impl Fn() + 'static) -> Rc<dyn IDisposable> {
323 let token = self.cancel_requested.add(Rc::new(handler));
324 let handlers = self.cancel_requested.clone();
325 Disposable::create(move || {
326 handlers.remove(token);
327 })
328 }
329
330 pub fn complete_requested(&self, handler: impl Fn(&[String]) + 'static) -> Rc<dyn IDisposable> {
332 let token = self.complete_requested.add(Rc::new(handler));
333 let handlers = self.complete_requested.clone();
334 Disposable::create(move || {
335 handlers.remove(token);
336 })
337 }
338
339 pub fn overwrite_prompt(&self, handler: impl Fn(&str) + 'static) -> Rc<dyn IDisposable> {
342 let token = self.overwrite_prompt.add(Rc::new(handler));
343 let handlers = self.overwrite_prompt.clone();
344 Disposable::create(move || {
345 handlers.remove(token);
346 })
347 }
348
349 fn raise_cancel_requested(&self) {
350 for (_, handler) in self.cancel_requested.snapshot().iter() {
351 handler();
352 }
353 }
354
355 fn raise_complete_requested(&self, items: &[String]) {
356 for (_, handler) in self.complete_requested.snapshot().iter() {
357 handler(items);
358 }
359 }
360
361 fn raise_overwrite_prompt(&self, file_name: &str) {
362 for (_, handler) in self.overwrite_prompt.snapshot().iter() {
363 handler(file_name);
364 }
365 }
366
367 pub fn quick_links(&self) -> FerroList<Rc<ManagedFileChooserItemViewModel>> {
370 self.quick_links.clone()
371 }
372
373 pub fn items(&self) -> FerroList<Rc<ManagedFileChooserItemViewModel>> {
374 self.items.clone()
375 }
376
377 pub fn filters(&self) -> FerroList<Rc<ManagedFileChooserFilterViewModel>> {
378 self.filters.clone()
379 }
380
381 pub fn selected_items(&self) -> SelectedItemsList {
384 self.selected_items.clone()
385 }
386
387 pub fn selected_item_view_models(&self) -> Vec<Rc<ManagedFileChooserItemViewModel>> {
389 self.selected_items.to_vec().iter().filter_map(item_of).collect()
390 }
391
392 pub fn location(&self) -> Option<String> {
393 self.location.borrow().clone()
394 }
395
396 pub fn set_location(&self, value: Option<String>) {
397 self.base.raise_and_set_if_changed(&self.location, value, "Location");
398 }
399
400 pub fn file_name(&self) -> Option<String> {
401 self.file_name.borrow().clone()
402 }
403
404 pub fn set_file_name(&self, value: Option<String>) {
405 self.base.raise_and_set_if_changed(&self.file_name, value, "FileName");
406 }
407
408 pub fn selecting_folder(&self) -> bool {
409 self.selecting_directory.get()
410 }
411
412 pub fn show_filters(&self) -> bool {
413 self.show_filters.get()
414 }
415
416 pub fn selection_mode(&self) -> SelectionMode {
417 self.selection_mode.get()
418 }
419
420 pub fn title(&self) -> Option<String> {
421 self.title.borrow().clone()
422 }
423
424 pub fn quick_links_selected_index(&self) -> i32 {
426 let location = self.location();
427 for (index, i) in self.quick_links.snapshot().iter().enumerate() {
428 if i.path() == location {
429 return index as i32;
430 }
431 }
432
433 -1
434 }
435
436 pub fn set_quick_links_selected_index(&self, _value: i32) {
439 self.base.raise_property_changed("QuickLinksSelectedIndex");
440 }
441
442 pub fn selected_filter(&self) -> Option<Rc<ManagedFileChooserFilterViewModel>> {
443 self.selected_filter.borrow().clone()
444 }
445
446 pub fn set_selected_filter(&self, value: Option<Rc<ManagedFileChooserFilterViewModel>>) {
447 self.base.raise_and_set_if_changed(&self.selected_filter, value, "SelectedFilter");
448 self.refresh();
449 }
450
451 pub fn show_hidden_files(&self) -> bool {
452 self.show_hidden_files.get()
453 }
454
455 pub fn set_show_hidden_files(&self, value: bool) {
456 self.base.raise_and_set_if_changed_cell(&self.show_hidden_files, value, "ShowHiddenFiles");
457 self.refresh();
458 }
459
460 fn refresh_quick_links(&self, quick_sources: &ManagedFileChooserSources) {
461 self.quick_links.clear();
462 self.quick_links.add_range(
463 quick_sources.get_all_items().iter().map(ManagedFileChooserItemViewModel::from_navigation_item),
464 );
465 }
466
467 pub fn enter_pressed(&self) {
470 let location = self.location();
471 if directory_exists(location.as_deref()) {
472 self.navigate(location.as_deref(), None);
473 } else if file_exists(location.as_deref()) {
474 self.raise_complete_requested(&[location.unwrap_or_default()]);
475 }
476 }
477
478 fn on_selection_changed_async(self: &Rc<Self>) {
482 if self.scheduled_selection_validation.get() {
483 return;
484 }
485
486 self.scheduled_selection_validation.set(true);
487 let weak: Weak<Self> = Rc::downgrade(self);
488 Dispatcher::ui_thread().post_local(
489 move || {
490 let Some(this) = weak.upgrade() else { return };
491
492 struct Reset<'a>(&'a Cell<bool>);
493 impl Drop for Reset<'_> {
494 fn drop(&mut self) {
495 self.0.set(false);
496 }
497 }
498 let _reset = Reset(&this.scheduled_selection_validation);
499
500 if this.selecting_directory.get() {
501 this.selected_items.clear();
502 } else {
503 if !this.options.allow_directory_selection() {
504 let invalid_items: Vec<Option<BoxedValue>> = this
505 .selected_items
506 .to_vec()
507 .into_iter()
508 .filter(|i| item_of(i).is_some_and(|i| i.item_type() == ManagedFileChooserItemType::Folder))
509 .collect();
510 for item in invalid_items {
511 this.selected_items.remove(&item);
512 }
513 }
514
515 if !this.selecting_directory.get() {
516 let selected = this.selected_item_view_models();
517 if selected.len() > 1 {
518 let mut sb = String::new();
519
520 for (i, item) in selected.iter().enumerate() {
521 sb.push('"');
522 sb.push_str(item.display_name().as_deref().unwrap_or_default());
523 sb.push('"');
524 if i + 1 < selected.len() {
525 sb.push(' ');
526 }
527 }
528
529 this.set_file_name(Some(sb));
530 } else {
531 this.set_file_name(selected.first().and_then(|item| item.display_name()));
532 }
533 }
534 }
535 this.base.raise_property_changed("SelectedItems");
536 },
537 DispatcherPriority::DEFAULT,
538 );
539 }
540
541 fn navigate_root(&self, initial_selection_name: Option<&str>) -> std::io::Result<()> {
542 if cfg!(windows) {
543 let system = std::env::var("SystemRoot").unwrap_or_else(|_| "C:\\Windows".to_string());
545 let root = std::path::Path::new(&system)
546 .ancestors()
547 .last()
548 .map(|root| root.to_string_lossy().into_owned())
549 .unwrap_or(system);
550 self.try_navigate(Some(&root), initial_selection_name)
551 } else {
552 self.try_navigate(Some("/"), initial_selection_name)
553 }
554 }
555
556 pub fn refresh(&self) {
557 let location = self.location();
558 self.navigate(location.as_deref(), None);
559 }
560
561 pub fn navigate_folder(&self, path: Option<&Rc<dyn IStorageFolder>>, initial_selection_name: Option<&str>) {
563 let full_directory_path = path
564 .and_then(|path| {
565 let item: &dyn IStorageItem = &**path;
566 item.try_get_local_path()
567 })
568 .unwrap_or_else(get_current_directory);
569
570 self.navigate(Some(&full_directory_path), initial_selection_name);
571 }
572
573 pub fn navigate(&self, path: Option<&str>, initial_selection_name: Option<&str>) {
582 if let Err(error) = self.try_navigate(path, initial_selection_name) {
583 panic!("{}: {error}", path.unwrap_or_default());
584 }
585 }
586
587 pub fn try_navigate(&self, path: Option<&str>, initial_selection_name: Option<&str>) -> std::io::Result<()> {
591 if !directory_exists(path) {
592 return self.navigate_root(initial_selection_name);
593 } else {
594 let path = path.unwrap_or_default();
595 self.set_location(Some(path.to_string()));
596 self.items.clear();
597 self.selected_items.clear();
598
599 let mut infos = match enumerate_file_system_infos(path) {
600 Ok(infos) => infos,
601 Err(error) if error.kind() == std::io::ErrorKind::PermissionDenied => return Ok(()),
602 Err(error) => return Err(error),
603 };
604
605 if !self.show_hidden_files() {
606 infos.retain(|i| !is_hidden(&i.name, &i.metadata));
607 }
608
609 if let Some(selected_filter) = self.selected_filter() {
610 infos.retain(|i| i.is_directory || selected_filter.is_match(&i.name));
611 }
612
613 let selecting_directory = self.selecting_directory.get();
614 let mut items: Vec<Rc<ManagedFileChooserItemViewModel>> = infos
615 .into_iter()
616 .filter(|x| !selecting_directory || x.is_directory)
617 .map(|info| {
618 let item = ManagedFileChooserItemViewModel::new();
619 item.set_display_name(Some(info.name.clone()));
620 item.set_path(Some(info.full_name.clone()));
621 item.set_type(Some(if info.is_directory {
622 "File Folder".to_string()
623 } else {
624 path::get_extension(&info.name).to_string()
625 }));
626 item.set_item_type(if info.is_directory {
627 ManagedFileChooserItemType::Folder
628 } else {
629 ManagedFileChooserItemType::File
630 });
631 item.set_size(if info.is_directory { 0 } else { info.metadata.len() as i64 });
632 item.set_modified(last_write_time(&info.metadata));
633 item
634 })
635 .collect();
636 items.sort_by(|a, b| {
638 let a_folder = a.item_type() == ManagedFileChooserItemType::Folder;
639 let b_folder = b.item_type() == ManagedFileChooserItemType::Folder;
640 b_folder.cmp(&a_folder).then_with(|| {
641 StringComparison::InvariantCultureIgnoreCase.compare(
642 a.display_name().as_deref().unwrap_or_default(),
643 b.display_name().as_deref().unwrap_or_default(),
644 )
645 })
646 });
647 self.items.add_range(items);
648
649 if let Some(initial_selection_name) = initial_selection_name {
650 let sel = self.items.snapshot().iter().find(|i| {
651 i.item_type() == ManagedFileChooserItemType::File
652 && i.display_name().as_deref() == Some(initial_selection_name)
653 }).cloned();
654
655 if let Some(sel) = sel {
656 self.selected_items.add(boxed(&sel));
657 }
658 }
659
660 self.base.raise_property_changed("QuickLinksSelectedIndex");
661 }
662
663 Ok(())
664 }
665
666 pub fn go_up(&self) {
667 let location = self.location();
668 let parent = location.as_deref().and_then(path::get_directory_name);
669
670 if is_null_or_white_space(parent) {
671 return;
672 }
673
674 self.navigate(parent, None);
675 }
676
677 pub fn cancel(&self) {
678 if !self.already_cancelled.get() {
679 self.already_cancelled.set(true);
683 self.raise_cancel_requested();
684 }
685 }
686
687 pub fn can_ok(&self, _parameter: Option<BoxedValue>) -> bool {
689 (self.selecting_directory.get() && !is_null_or_white_space(self.location().as_deref()))
690 || (self.saving_file.get() && !is_null_or_white_space(self.file_name().as_deref()))
691 || (!self.selected_items.is_empty()
692 && self.selected_item_view_models().iter().any(|s| !is_null_or_white_space(s.path().as_deref())))
693 }
694
695 pub fn ok(&self) {
696 if self.selecting_directory.get() {
697 self.raise_complete_requested(&[self.location().unwrap_or_default()]);
698 } else if self.saving_file.get() {
699 if let Some(mut file_name) = self.file_name().filter(|name| !name.trim().is_empty()) {
700 let default_extension = self.default_extension.borrow().clone();
701 if !path::has_extension(&file_name) && !is_null_or_white_space(default_extension.as_deref()) {
702 file_name = path::change_extension(&file_name, default_extension.as_deref());
703 self.set_file_name(Some(file_name.clone()));
704 }
705
706 let full_name = combine(&self.location().unwrap_or_default(), &file_name);
707
708 if self.overwrite_prompt_enabled.get() && file_exists(Some(&full_name)) {
709 self.raise_overwrite_prompt(&full_name);
710 } else {
711 self.raise_complete_requested(&[full_name]);
712 }
713 }
714 } else {
715 let paths: Vec<String> =
716 self.selected_item_view_models().iter().map(|i| i.path().unwrap_or_default()).collect();
717 self.raise_complete_requested(&paths);
718 }
719 }
720
721 pub fn select_single_file(&self, item: &ManagedFileChooserItemViewModel) {
722 self.raise_complete_requested(&[item.path().unwrap_or_default()]);
723 }
724}
725
726ferro_markup_type!(class ManagedFileChooserViewModel {
727 this: Rc<ManagedFileChooserViewModel>,
728 handles: [ManagedFileChooserViewModel, Rc<ManagedFileChooserViewModel>, Option<Rc<ManagedFileChooserViewModel>>],
729 properties: [
730 QuickLinks: FerroList<Rc<ManagedFileChooserItemViewModel>> { get: ManagedFileChooserViewModel::quick_links },
731 Items: FerroList<Rc<ManagedFileChooserItemViewModel>> { get: ManagedFileChooserViewModel::items },
732 Filters: FerroList<Rc<ManagedFileChooserFilterViewModel>> { get: ManagedFileChooserViewModel::filters },
733 SelectedItems: SelectedItemsList { get: ManagedFileChooserViewModel::selected_items },
734 Location: Option<String> { get: ManagedFileChooserViewModel::location, set: ManagedFileChooserViewModel::set_location },
735 FileName: Option<String> { get: ManagedFileChooserViewModel::file_name, set: ManagedFileChooserViewModel::set_file_name },
736 SelectingFolder: bool { get: ManagedFileChooserViewModel::selecting_folder },
737 ShowFilters: bool { get: ManagedFileChooserViewModel::show_filters },
738 SelectionMode: SelectionMode { get: ManagedFileChooserViewModel::selection_mode },
739 Title: Option<String> { get: ManagedFileChooserViewModel::title },
740 QuickLinksSelectedIndex: i32 {
741 get: ManagedFileChooserViewModel::quick_links_selected_index,
742 set: ManagedFileChooserViewModel::set_quick_links_selected_index
743 },
744 SelectedFilter: Option<Rc<ManagedFileChooserFilterViewModel>> {
745 get: ManagedFileChooserViewModel::selected_filter,
746 set: ManagedFileChooserViewModel::set_selected_filter
747 },
748 ShowHiddenFiles: bool {
749 get: ManagedFileChooserViewModel::show_hidden_files,
750 set: ManagedFileChooserViewModel::set_show_hidden_files
751 },
752 ],
753 methods: [
754 fn EnterPressed() => ManagedFileChooserViewModel::enter_pressed,
755 fn Refresh() => ManagedFileChooserViewModel::refresh,
756 fn GoUp() => ManagedFileChooserViewModel::go_up,
757 fn Cancel() => ManagedFileChooserViewModel::cancel,
758 fn CanOk(Option<BoxedValue>) -> bool => ManagedFileChooserViewModel::can_ok
759 [DependsOn("FileName"), DependsOn("Location"), DependsOn("SelectedItems")],
760 fn Ok() => ManagedFileChooserViewModel::ok,
761 fn SelectSingleFile(Rc<ManagedFileChooserItemViewModel>) =>
762 (|this: &Rc<ManagedFileChooserViewModel>, item: Rc<ManagedFileChooserItemViewModel>| this.select_single_file(&item)),
763 ],
764 notify_property_changed: ManagedFileChooserViewModel,
765});