#![allow(non_upper_case_globals)]
use std::convert::TryInto;
use std::ffi::OsString;
use std::ptr::null_mut;
use winapi::ctypes::c_void;
use winapi::shared::minwindef::*;
use winapi::shared::ntdef::LPWSTR;
use winapi::shared::windef::*;
use winapi::shared::wtypesbase::*;
use winapi::um::combaseapi::*;
use winapi::um::shobjidl::*;
use winapi::um::shobjidl_core::*;
use winapi::um::shtypes::COMDLG_FILTERSPEC;
use winapi::{Interface, DEFINE_GUID};
use wio::com::ComPtr;
use super::error::Error;
use super::util::{as_result, FromWide, ToWide};
use crate::dialog::{FileDialogOptions, FileDialogType, FileSpec};
DEFINE_GUID! {CLSID_FileOpenDialog,
0xDC1C_5A9C, 0xE88A, 0x4DDE, 0xA5, 0xA1, 0x60, 0xF8, 0x2A, 0x20, 0xAE, 0xF7}
DEFINE_GUID! {CLSID_FileSaveDialog,
0xC0B4_E2F3, 0xBA21, 0x4773, 0x8D, 0xBA, 0x33, 0x5E, 0xC9, 0x46, 0xEB, 0x8B}
unsafe fn make_wstrs(spec: &FileSpec) -> (Vec<u16>, Vec<u16>) {
let exts = spec
.extensions
.iter()
.map(|s| normalize_extension(s))
.collect::<Vec<_>>();
let name = format!("{} ({})", spec.name, exts.as_slice().join("; ")).to_wide();
let extensions = exts.as_slice().join(";").to_wide();
(name, extensions)
}
fn normalize_extension(ext: &str) -> String {
format!("*.{}", ext.trim_start_matches('*').trim_start_matches('.'))
}
pub(crate) unsafe fn get_file_dialog_path(
hwnd_owner: HWND,
ty: FileDialogType,
options: FileDialogOptions,
) -> Result<OsString, Error> {
let mut pfd: *mut IFileDialog = null_mut();
let (class, id) = match ty {
FileDialogType::Open => (&CLSID_FileOpenDialog, IFileOpenDialog::uuidof()),
FileDialogType::Save => (&CLSID_FileSaveDialog, IFileSaveDialog::uuidof()),
};
as_result(CoCreateInstance(
class,
null_mut(),
CLSCTX_INPROC_SERVER,
&id,
&mut pfd as *mut *mut IFileDialog as *mut LPVOID,
))?;
let file_dialog = ComPtr::from_raw(pfd);
let mut flags: DWORD = 0;
if options.show_hidden {
flags |= FOS_FORCESHOWHIDDEN;
}
if options.select_directories {
flags |= FOS_PICKFOLDERS;
}
if options.multi_selection {
flags |= FOS_ALLOWMULTISELECT;
}
let spec = options.allowed_types.as_ref().map(|allowed_types| {
allowed_types
.iter()
.map(|t| make_wstrs(t))
.collect::<Vec<_>>()
});
let raw_spec = spec.as_ref().map(|buf_pairs| {
buf_pairs
.iter()
.map(|(name, ext)| COMDLG_FILTERSPEC {
pszName: name.as_ptr(),
pszSpec: ext.as_ptr(),
})
.collect::<Vec<_>>()
});
if let Some(spec) = &raw_spec {
if !spec.is_empty() {
as_result(file_dialog.SetFileTypes(spec.len() as u32, spec.as_ptr()))?;
let index = match (&options.default_type, &options.allowed_types) {
(Some(typ), Some(typs)) => typs.iter().position(|t| t == typ).unwrap_or(0),
_ => 0,
};
let default_allowed_type = options.allowed_types.as_ref().unwrap().get(index).unwrap();
let default_extension = default_allowed_type.extensions.first().unwrap_or(&"");
as_result(file_dialog.SetFileTypeIndex((index + 1).try_into().unwrap()))?;
as_result(file_dialog.SetDefaultExtension(default_extension.to_wide().as_ptr()))?;
}
}
as_result(file_dialog.SetOptions(flags))?;
if let Some(path) = options.starting_directory {
let mut item: *mut IShellItem = null_mut();
if let Err(err) = as_result(SHCreateItemFromParsingName(
path.as_os_str().to_wide().as_ptr(),
null_mut(),
&IShellItem::uuidof(),
&mut item as *mut *mut IShellItem as *mut *mut c_void,
)) {
tracing::warn!("Failed to convert path: {}", err.to_string());
} else {
as_result(file_dialog.SetDefaultFolder(item))?;
}
}
as_result(file_dialog.Show(hwnd_owner))?;
let mut result_ptr: *mut IShellItem = null_mut();
as_result(file_dialog.GetResult(&mut result_ptr))?;
let shell_item = ComPtr::from_raw(result_ptr);
let mut display_name: LPWSTR = null_mut();
as_result(shell_item.GetDisplayName(SIGDN_FILESYSPATH, &mut display_name))?;
let filename = display_name.to_os_string();
CoTaskMemFree(display_name as LPVOID);
Ok(filename)
}