You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
notepad_squared/file_dialog/windows.odin

268 lines
8.2 KiB
Plaintext

#+build windows
package file_dialog
import "core:strings"
import "core:c"
import "core:fmt"
foreign import comdlg32 "system:comdlg32.lib"
foreign import ole32 "system:ole32.lib"
foreign import shell32 "system:shell32.lib"
OPENFILENAME :: struct {
lStructSize: c.ulong,
hwndOwner: rawptr,
hInstance: rawptr,
lpstrFilter: cstring,
lpstrCustomFilter: cstring,
nMaxCustFilter: c.ulong,
nFilterIndex: c.ulong,
lpstrFile: cstring,
nMaxFile: c.ulong,
lpstrFileTitle: cstring,
nMaxFileTitle: c.ulong,
lpstrInitialDir: cstring,
lpstrTitle: cstring,
Flags: c.ulong,
nFileOffset: c.ushort,
nFileExtension: c.ushort,
lpstrDefExt: cstring,
lCustData: c.ulong_ptr,
lpfnHook: rawptr,
lpTemplateName: cstring,
pvReserved: rawptr,
dwReserved: c.ulong,
FlagsEx: c.ulong,
}
OFN_PATHMUSTEXIST :: 0x00000800
OFN_FILEMUSTEXIST :: 0x00001000
OFN_HIDEREADONLY :: 0x00000004
OFN_ALLOWMULTISELECT :: 0x00000200
@(default_calling_convention="stdcall")
foreign comdlg32 {
GetOpenFileNameA :: proc(lpofn: ^OPENFILENAME) -> c.int ---
GetSaveFileNameA :: proc(lpofn: ^OPENFILENAME) -> c.int ---
}
show_open_file_dialog_windows :: proc(title: string, filters: []File_Filter, allow_multiple: bool) -> File_Dialog_Result {
MAX_PATH :: 260
BUFFER_SIZE :: 32768 // Larger buffer for multiple files
file_buffer: [BUFFER_SIZE]c.char
// Build filter string (format: "Description\0*.ext\0Description2\0*.ext2\0\0")
filter_str := strings.builder_make()
defer strings.builder_destroy(&filter_str)
if len(filters) > 0 {
for filter in filters {
strings.write_string(&filter_str, filter.name)
strings.write_byte(&filter_str, 0)
strings.write_string(&filter_str, filter.pattern)
strings.write_byte(&filter_str, 0)
}
} else {
strings.write_string(&filter_str, "All Files")
strings.write_byte(&filter_str, 0)
strings.write_string(&filter_str, "*.*")
strings.write_byte(&filter_str, 0)
}
strings.write_byte(&filter_str, 0) // Double null terminator
filter_cstr := strings.clone_to_cstring(strings.to_string(filter_str))
defer delete(filter_cstr)
title_cstr := strings.clone_to_cstring(title)
defer delete(title_cstr)
flags := OFN_PATHMUSTEXIST | OFN_FILEMUSTEXIST | OFN_HIDEREADONLY
if allow_multiple {
flags |= OFN_ALLOWMULTISELECT
}
ofn := OPENFILENAME{
lStructSize = size_of(OPENFILENAME),
lpstrFile = &file_buffer[0],
nMaxFile = BUFFER_SIZE,
lpstrFilter = filter_cstr,
nFilterIndex = 1,
lpstrTitle = title_cstr,
Flags = flags,
}
if GetOpenFileNameA(&ofn) != 0 {
buffer_str := string(cstring(&file_buffer[0]))
if allow_multiple {
// Parse multiple files format: "directory\0file1\0file2\0\0"
parts := strings.split(buffer_str, "\x00")
defer delete(parts)
if len(parts) > 1 && parts[1] != "" {
// Multiple files selected
directory := parts[0]
file_paths := make([]string, len(parts) - 1)
for i := 1; i < len(parts); i+=1 {
if parts[i] != "" {
full_path := strings.concatenate({directory, "\\", parts[i]})
file_paths[i-1] = full_path
}
}
return {
success = true,
file_path = file_paths[0], // First file for compatibility
file_paths = file_paths,
}
} else {
// Single file selected (even with multiple selection enabled)
return {
success = true,
file_path = strings.clone(buffer_str),
file_paths = {strings.clone(buffer_str)},
}
}
} else {
// Single file selection
return {
success = true,
file_path = strings.clone(buffer_str),
file_paths = {strings.clone(buffer_str)},
}
}
}
return {
success = false,
error_message = "User cancelled or dialog failed",
}
}
show_save_file_dialog_macos :: proc(title: string, filters: []File_Filter, default_filename: string = "", default_directory: string = "") -> File_Dialog_Result {
panel := NSSavePanel_savePanel()
title_nsstr := NSStringFromCString(strings.clone_to_cstring(title))
NSSavePanel_setTitle(panel, title_nsstr)
// Set default filename if provided
if default_filename != "" {
filename_nsstr := NSStringFromCString(strings.clone_to_cstring(default_filename))
NSSavePanel_setNameFieldStringValue(panel, filename_nsstr)
}
// Set default directory if provided
if default_directory != "" {
dir_nsstr := NSStringFromCString(strings.clone_to_cstring(default_directory))
dir_url := NSURL_fileURLWithPath(dir_nsstr)
if dir_url != nil {
NSSavePanel_setDirectoryURL(panel, dir_url)
}
}
// Note: File filters in NSSavePanel require more complex setup
// This is a simplified version - full implementation would use UTType
response := NSSavePanel_runModal(panel)
if response == NSModalResponseOK {
url := NSSavePanel_URL(panel)
if url != nil {
path_nsstr := NSURL_path(url)
path_cstr := NSString_UTF8String(path_nsstr)
file_path := strings.clone(string(path_cstr))
return {
success = true,
file_path = file_path,
file_paths = {file_path},
}
}
}
return {
success = false,
error_message = "User cancelled or dialog failed",
}
}
show_save_file_dialog_windows :: proc(
title: string,
filters: []File_Filter,
default_filename: string = "",
default_directory: string = ""
) -> File_Dialog_Result {
MAX_PATH :: 260
file_buffer: [MAX_PATH]c.char
// Set default filename if provided
if default_filename != "" {
filename_cstr := strings.clone_to_cstring(default_filename)
defer delete(filename_cstr)
// Copy to buffer (ensure null termination)
filename_len := min(len(default_filename), MAX_PATH - 1)
for i in 0..<filename_len {
file_buffer[i] = c.char(default_filename[i])
}
file_buffer[filename_len] = 0
}
// Build filter string
filter_str := strings.builder_make()
defer strings.builder_destroy(&filter_str)
if len(filters) > 0 {
for filter in filters {
strings.write_string(&filter_str, filter.name)
strings.write_byte(&filter_str, 0)
strings.write_string(&filter_str, filter.pattern)
strings.write_byte(&filter_str, 0)
}
} else {
strings.write_string(&filter_str, "All Files")
strings.write_byte(&filter_str, 0)
strings.write_string(&filter_str, "*.*")
strings.write_byte(&filter_str, 0)
}
strings.write_byte(&filter_str, 0) // Double null terminator
filter_cstr := strings.clone_to_cstring(strings.to_string(filter_str))
defer delete(filter_cstr)
title_cstr := strings.clone_to_cstring(title)
defer delete(title_cstr)
// Default directory (optional)
dir_cstr: cstring = nil
if default_directory != "" {
dir_cstr = strings.clone_to_cstring(default_directory)
defer delete(dir_cstr)
}
ofn := OPENFILENAME{
lStructSize = size_of(OPENFILENAME),
lpstrFile = &file_buffer[0],
nMaxFile = MAX_PATH,
lpstrFilter = filter_cstr,
nFilterIndex = 1,
lpstrTitle = title_cstr,
lpstrInitialDir = dir_cstr,
Flags = OFN_PATHMUSTEXIST | OFN_HIDEREADONLY,
}
if GetSaveFileNameA(&ofn) != 0 {
return {
success = true,
file_path = strings.clone(string(cstring(&file_buffer[0]))),
file_paths = {strings.clone(string(cstring(&file_buffer[0])))},
}
}
return {
success = false,
error_message = "User cancelled or dialog failed",
}
}