#![allow(non_upper_case_globals, clippy::upper_case_acronyms)]
use std::ffi::OsString;
use cocoa::appkit::NSView;
use cocoa::base::{id, nil, NO, YES};
use cocoa::foundation::{NSArray, NSAutoreleasePool, NSInteger, NSPoint, NSRect, NSSize, NSURL};
use objc::{class, msg_send, sel, sel_impl};
use super::util::{from_nsstring, make_nsstring};
use crate::dialog::{FileDialogOptions, FileDialogType};
use crate::{FileInfo, FileSpec};
pub(crate) type NSModalResponse = NSInteger;
const NSModalResponseOK: NSInteger = 1;
const NSModalResponseCancel: NSInteger = 0;
pub(crate) unsafe fn get_file_info(
panel: id,
options: FileDialogOptions,
result: NSModalResponse,
) -> Option<FileInfo> {
match result {
NSModalResponseOK => {
let url: id = msg_send![panel, URL];
let path: id = msg_send![url, path];
let (path, format) = rewritten_path(panel, path, options);
let path: OsString = from_nsstring(path).into();
Some(FileInfo {
path: path.into(),
format,
})
}
NSModalResponseCancel => None,
_ => unreachable!(),
}
}
#[allow(clippy::cognitive_complexity)]
pub(crate) unsafe fn build_panel(ty: FileDialogType, mut options: FileDialogOptions) -> id {
let panel: id = match ty {
FileDialogType::Open => msg_send![class!(NSOpenPanel), openPanel],
FileDialogType::Save => msg_send![class!(NSSavePanel), savePanel],
};
let () = msg_send![panel, setCanSelectHiddenExtension: YES];
let mut set_type_filter = true;
if let FileDialogType::Open = ty {
if options.select_directories {
let () = msg_send![panel, setCanChooseDirectories: YES];
let () = msg_send![panel, setCanChooseFiles: NO];
set_type_filter = !options.packages_as_directories;
}
if options.multi_selection {
let () = msg_send![panel, setAllowsMultipleSelection: YES];
}
}
if options.packages_as_directories {
let () = msg_send![panel, setTreatsFilePackagesAsDirectories: YES];
}
if options.show_hidden {
let () = msg_send![panel, setShowsHiddenFiles: YES];
}
if let Some(default_name) = &options.default_name {
let () = msg_send![panel, setNameFieldStringValue: make_nsstring(default_name)];
}
if let Some(name_label) = &options.name_label {
let () = msg_send![panel, setNameFieldLabel: make_nsstring(name_label)];
}
if let Some(title) = &options.title {
let () = msg_send![panel, setTitle: make_nsstring(title)];
}
if let Some(text) = &options.button_text {
let () = msg_send![panel, setPrompt: make_nsstring(text)];
}
if let Some(path) = &options.starting_directory {
if let Some(path) = path.to_str() {
let url = NSURL::alloc(nil)
.initFileURLWithPath_isDirectory_(make_nsstring(path), YES)
.autorelease();
let () = msg_send![panel, setDirectoryURL: url];
}
}
match (&options.allowed_types, ty) {
(Some(allowed_types), FileDialogType::Save) if !allowed_types.is_empty() => {
let accessory_view = allowed_types_accessory_view(allowed_types);
let _: () = msg_send![panel, setAccessoryView: accessory_view];
}
_ => (),
}
if set_type_filter {
if let Some(dt) = &options.default_type {
let mut present = false;
if let Some(allowed_types) = options.allowed_types.as_mut() {
if let Some(idx) = allowed_types.iter().position(|t| t == dt) {
present = true;
allowed_types.swap(idx, 0);
}
}
if !present {
tracing::warn!("The default type {:?} is not present in allowed types.", dt);
}
}
let allowed_types = options.allowed_types.as_ref().map(|specs| {
specs
.iter()
.flat_map(|spec| spec.extensions.iter().map(|s| make_nsstring(s)))
.collect::<Vec<_>>()
});
let nsarray_allowed_types = allowed_types
.as_ref()
.map(|types| NSArray::arrayWithObjects(nil, types.as_slice()));
if let Some(nsarray) = nsarray_allowed_types {
let () = msg_send![panel, setAllowedFileTypes: nsarray];
}
}
panel
}
unsafe fn allowed_types_accessory_view(allowed_types: &[crate::FileSpec]) -> id {
let total_frame = NSRect::new(
NSPoint { x: 0.0, y: 0.0 },
NSSize {
width: 320.0,
height: 30.0,
},
);
let padding = 10.0;
let (label, label_size) = file_format_label();
label.setFrameOrigin(NSPoint {
x: padding,
y: label_size.height / 2.0,
});
let popup_frame = NSRect::new(
NSPoint {
x: padding + label_size.width + padding,
y: 0.0,
},
NSSize {
width: total_frame.size.width - (padding * 3.0) - label_size.width,
height: total_frame.size.height,
},
);
let popup_button = file_format_popup_button(allowed_types, popup_frame);
let container_view: id = msg_send![class!(NSView), alloc];
let container_view: id = container_view.initWithFrame_(total_frame);
container_view.addSubview_(label);
container_view.addSubview_(popup_button);
container_view.autorelease()
}
const FileFormatPopoverTag: NSInteger = 10;
unsafe fn file_format_popup_button(allowed_types: &[crate::FileSpec], popup_frame: NSRect) -> id {
let popup_button: id = msg_send![class!(NSPopUpButton), alloc];
let _: () = msg_send![popup_button, initWithFrame:popup_frame pullsDown:false];
for allowed_type in allowed_types {
let title = make_nsstring(allowed_type.name);
msg_send![popup_button, addItemWithTitle: title]
}
let _: () = msg_send![popup_button, setTag: FileFormatPopoverTag];
popup_button.autorelease()
}
unsafe fn file_format_label() -> (id, NSSize) {
let label: id = msg_send![class!(NSTextField), new];
let _: () = msg_send![label, setBezeled:false];
let _: () = msg_send![label, setDrawsBackground:false];
let title = make_nsstring("File Format:");
let _: () = msg_send![label, setStringValue: title];
let _: () = msg_send![label, sizeToFit];
(label.autorelease(), label.frame().size)
}
unsafe fn rewritten_path(
panel: id,
path: id,
options: FileDialogOptions,
) -> (id, Option<FileSpec>) {
let allowed_types = match options.allowed_types {
Some(t) if !t.is_empty() => t,
_ => return (path, None),
};
let accessory: id = msg_send![panel, accessoryView];
if accessory == nil {
return (path, None);
}
let popup_button: id = msg_send![accessory, viewWithTag: FileFormatPopoverTag];
if popup_button == nil {
return (path, None);
}
let index: NSInteger = msg_send![popup_button, indexOfSelectedItem];
let file_spec = allowed_types[index as usize];
let extension = file_spec.extensions[0];
let path: id = msg_send![path, stringByDeletingPathExtension];
let path: id = msg_send![
path,
stringByAppendingPathExtension: make_nsstring(extension)
];
(path, Some(file_spec))
}