use windows::core::{PCWSTR, PWSTR};
use windows::Win32::UI::Controls::Dialogs::{
GetOpenFileNameW, GetSaveFileNameW, OPENFILENAMEW, OFN_ALLOWMULTISELECT, OFN_EXPLORER,
OFN_FILEMUSTEXIST, OFN_NOCHANGEDIR, OFN_OVERWRITEPROMPT, OFN_PATHMUSTEXIST,
};
use super::*;
pub fn pick_file(p: &FileDialog<'_>) -> Option<PathBuf> {
pick_files_impl(p, false).and_then(|paths| paths.into_iter().next())
}
pub fn pick_files(p: &FileDialog<'_>) -> Option<Vec<PathBuf>> {
pick_files_impl(p, true)
}
pub fn save_file(p: &FileDialog<'_>) -> Option<PathBuf> {
let title = utf16cs(p.title);
let filter = build_windows_filter(p.filter);
let path = utils::abspath(p.path);
let mut file_buffer = initial_file_buffer(path.as_deref());
let mut open_file_name = OPENFILENAMEW::default();
open_file_name.lStructSize = std::mem::size_of::<OPENFILENAMEW>() as u32;
open_file_name.lpstrTitle = PCWSTR(title.as_ptr());
if let Some(filter) = &filter {
open_file_name.lpstrFilter = PCWSTR(filter.as_ptr());
}
open_file_name.hwndOwner = hwnd(p.owner).unwrap_or_default();
open_file_name.lpstrFile = PWSTR(file_buffer.as_mut_ptr());
open_file_name.nMaxFile = file_buffer.len() as u32;
open_file_name.Flags = OFN_EXPLORER | OFN_NOCHANGEDIR | OFN_PATHMUSTEXIST | OFN_OVERWRITEPROMPT;
let selected = unsafe { GetSaveFileNameW(&mut open_file_name).as_bool() };
if !selected {
return None;
}
wide_to_string_until_nul(&file_buffer)
}
fn pick_files_impl(p: &FileDialog<'_>, allow_multiple_selects: bool) -> Option<Vec<PathBuf>> {
let title = utf16cs(p.title);
let filter = build_windows_filter(p.filter);
let path = utils::abspath(p.path);
let mut file_buffer = initial_file_buffer(path.as_deref());
let mut open_file_name = OPENFILENAMEW::default();
open_file_name.lStructSize = std::mem::size_of::<OPENFILENAMEW>() as u32;
open_file_name.lpstrTitle = PCWSTR(title.as_ptr());
if let Some(filter) = &filter {
open_file_name.lpstrFilter = PCWSTR(filter.as_ptr());
}
open_file_name.hwndOwner = hwnd(p.owner).unwrap_or_default();
open_file_name.lpstrFile = PWSTR(file_buffer.as_mut_ptr());
open_file_name.nMaxFile = file_buffer.len() as u32;
open_file_name.Flags = OFN_EXPLORER | OFN_NOCHANGEDIR | OFN_PATHMUSTEXIST | OFN_FILEMUSTEXIST;
if allow_multiple_selects {
open_file_name.Flags |= OFN_ALLOWMULTISELECT;
}
let selected = unsafe { GetOpenFileNameW(&mut open_file_name).as_bool() };
if !selected {
return None;
}
parse_open_file_buffer(&file_buffer)
}
fn initial_file_buffer(file: Option<&Path>) -> Vec<u16> {
let mut buffer = vec![0u16; 16 * 1024];
let Some(file) = file else {
return buffer;
};
if file.as_os_str().is_empty() {
return buffer;
}
let encoded = utf16cs(&file.to_string_lossy());
let copy_len = encoded.len().min(buffer.len());
buffer[..copy_len].copy_from_slice(&encoded[..copy_len]);
buffer
}
fn build_windows_filter(filter: Option<&[FileFilter<'_>]>) -> Option<Vec<u16>> {
let filter = filter?;
if filter.is_empty() {
return None;
}
let mut spec = String::new();
for entry in filter {
_ = write!(spec, "{}\0{}\0", entry.desc, utils::PrintJoin { parts: entry.patterns, separator: ";" });
}
spec.push_str("All Files\0*.*\0\0");
Some(spec.encode_utf16().collect())
}
fn wide_to_string_until_nul(input: &[u16]) -> Option<PathBuf> {
let length = input.iter().position(|value| *value == 0)?;
if length == 0 {
return None;
}
Some(PathBuf::from(String::from_utf16_lossy(&input[..length])))
}
fn parse_open_file_buffer(input: &[u16]) -> Option<Vec<PathBuf>> {
let mut segments = Vec::new();
let mut start = 0usize;
for index in 0..input.len() {
if input[index] != 0 {
continue;
}
if index == start {
break;
}
segments.push(PathBuf::from(String::from_utf16_lossy(&input[start..index])));
start = index + 1;
}
if segments.is_empty() {
return None;
}
if segments.len() == 1 {
return Some(segments);
}
let mut segments = segments.into_iter();
let directory = segments.next().unwrap();
let full_paths = segments.map(|file_name| directory.join(file_name)).collect();
Some(full_paths)
}