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.
268 lines
8.2 KiB
Plaintext
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",
|
|
}
|
|
}
|
|
|