comdlg32.odin 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194
  1. // +build windows
  2. package win32
  3. foreign import "system:comdlg32.lib"
  4. import "core:strings"
  5. OFN_Hook_Proc :: #type proc "stdcall" (hdlg: Hwnd, msg: u32, wparam: Wparam, lparam: Lparam) -> Uint_Ptr
  6. Open_File_Name_A :: struct {
  7. struct_size: u32,
  8. hwnd_owner: Hwnd,
  9. instance: Hinstance,
  10. filter: cstring,
  11. custom_filter: cstring,
  12. max_cust_filter: u32,
  13. filter_index: u32,
  14. file: cstring,
  15. max_file: u32,
  16. file_title: cstring,
  17. max_file_title: u32,
  18. initial_dir: cstring,
  19. title: cstring,
  20. flags: u32,
  21. file_offset: u16,
  22. file_extension: u16,
  23. def_ext: cstring,
  24. cust_data: Lparam,
  25. hook: OFN_Hook_Proc,
  26. template_name: cstring,
  27. pv_reserved: rawptr,
  28. dw_reserved: u32,
  29. flags_ex: u32,
  30. }
  31. Open_File_Name_W :: struct {
  32. struct_size: u32,
  33. hwnd_owner: Hwnd,
  34. instance: Hinstance,
  35. filter: Wstring,
  36. custom_filter: Wstring,
  37. max_cust_filter: u32,
  38. filter_index: u32,
  39. file: Wstring,
  40. max_file: u32,
  41. file_title: Wstring,
  42. max_file_title: u32,
  43. initial_dir: Wstring,
  44. title: Wstring,
  45. flags: u32,
  46. file_offset: u16,
  47. file_extension: u16,
  48. def_ext: Wstring,
  49. cust_data: Lparam,
  50. hook: OFN_Hook_Proc,
  51. template_name: Wstring,
  52. pv_reserved: rawptr,
  53. dw_reserved: u32,
  54. flags_ex: u32,
  55. }
  56. @(default_calling_convention = "c")
  57. foreign comdlg32 {
  58. @(link_name="GetOpenFileNameA") get_open_file_name_a :: proc(arg1: ^Open_File_Name_A) -> Bool ---
  59. @(link_name="GetOpenFileNameW") get_open_file_name_w :: proc(arg1: ^Open_File_Name_W) -> Bool ---
  60. @(link_name="GetSaveFileNameA") get_save_file_name_a :: proc(arg1: ^Open_File_Name_A) -> Bool ---
  61. @(link_name="GetSaveFileNameW") get_save_file_name_w :: proc(arg1: ^Open_File_Name_W) -> Bool ---
  62. @(link_name="CommDlgExtendedError") comm_dlg_extended_error :: proc() -> u32 ---
  63. }
  64. OPEN_TITLE :: "Select file to open"
  65. OPEN_FLAGS :: u32(OFN_PATHMUSTEXIST | OFN_FILEMUSTEXIST)
  66. OPEN_FLAGS_MULTI :: OPEN_FLAGS | OFN_ALLOWMULTISELECT | OFN_EXPLORER
  67. SAVE_TITLE :: "Select file to save"
  68. SAVE_FLAGS :: u32(OFN_OVERWRITEPROMPT | OFN_EXPLORER)
  69. SAVE_EXT :: "txt"
  70. Open_Save_Mode :: enum {
  71. Open = 0,
  72. Save = 1,
  73. }
  74. _open_file_dialog :: proc(title: string, dir: string,
  75. filters: []string, default_filter: u32,
  76. flags: u32, default_ext: string,
  77. mode: Open_Save_Mode, allocator := context.temp_allocator) -> (path: string, ok: bool = true) {
  78. context.allocator = allocator
  79. file_buf := make([]u16, MAX_PATH_WIDE)
  80. defer if !ok {
  81. delete(file_buf)
  82. }
  83. // Filters need to be passed as a pair of strings (title, filter)
  84. filter_len := u32(len(filters))
  85. if filter_len % 2 != 0 {
  86. return "", false
  87. }
  88. filter: string
  89. filter = strings.join(filters, "\u0000", context.temp_allocator)
  90. filter = strings.concatenate({filter, "\u0000"}, context.temp_allocator)
  91. ofn := Open_File_Name_W{
  92. struct_size = size_of(Open_File_Name_W),
  93. file = Wstring(&file_buf[0]),
  94. max_file = MAX_PATH_WIDE,
  95. title = utf8_to_wstring(title, context.temp_allocator),
  96. filter = utf8_to_wstring(filter, context.temp_allocator),
  97. initial_dir = utf8_to_wstring(dir, context.temp_allocator),
  98. filter_index = u32(clamp(default_filter, 1, filter_len / 2)),
  99. def_ext = utf8_to_wstring(default_ext, context.temp_allocator),
  100. flags = u32(flags),
  101. }
  102. switch mode {
  103. case .Open:
  104. ok = bool(get_open_file_name_w(&ofn))
  105. case .Save:
  106. ok = bool(get_save_file_name_w(&ofn))
  107. case:
  108. ok = false
  109. }
  110. if !ok {
  111. return
  112. }
  113. file_name := utf16_to_utf8(file_buf[:], allocator)
  114. path = strings.trim_right_null(file_name)
  115. return
  116. }
  117. select_file_to_open :: proc(title := OPEN_TITLE, dir := ".",
  118. filters := []string{"All Files", "*.*"}, default_filter := u32(1),
  119. flags := OPEN_FLAGS, allocator := context.temp_allocator) -> (path: string, ok: bool) {
  120. path, ok = _open_file_dialog(title, dir, filters, default_filter, flags, "", Open_Save_Mode.Open, allocator)
  121. return
  122. }
  123. select_file_to_save :: proc(title := SAVE_TITLE, dir := ".",
  124. filters := []string{"All Files", "*.*"}, default_filter := u32(1),
  125. flags := SAVE_FLAGS, default_ext := SAVE_EXT,
  126. allocator := context.temp_allocator) -> (path: string, ok: bool) {
  127. path, ok = _open_file_dialog(title, dir, filters, default_filter, flags, default_ext, Open_Save_Mode.Save, allocator)
  128. return
  129. }
  130. // TODO: Implement convenience function for select_file_to_open with ALLOW_MULTI_SELECT that takes
  131. // it output of the form "path\u0000\file1u\0000file2" and turns it into []string with the path + file pre-concatenated for you.
  132. OFN_ALLOWMULTISELECT :: 0x00000200 // NOTE(Jeroen): Without OFN_EXPLORER it uses the Win3 dialog.
  133. OFN_CREATEPROMPT :: 0x00002000
  134. OFN_DONTADDTORECENT :: 0x02000000
  135. OFN_ENABLEHOOK :: 0x00000020
  136. OFN_ENABLEINCLUDENOTIFY :: 0x00400000
  137. OFN_ENABLESIZING :: 0x00800000
  138. OFN_ENABLETEMPLATE :: 0x00000040
  139. OFN_ENABLETEMPLATEHANDLE :: 0x00000080
  140. OFN_EXPLORER :: 0x00080000
  141. OFN_EXTENSIONDIFFERENT :: 0x00000400
  142. OFN_FILEMUSTEXIST :: 0x00001000
  143. OFN_FORCESHOWHIDDEN :: 0x10000000
  144. OFN_HIDEREADONLY :: 0x00000004
  145. OFN_LONGNAMES :: 0x00200000
  146. OFN_NOCHANGEDIR :: 0x00000008
  147. OFN_NODEREFERENCELINKS :: 0x00100000
  148. OFN_NOLONGNAMES :: 0x00040000
  149. OFN_NONETWORKBUTTON :: 0x00020000
  150. OFN_NOREADONLYRETURN :: 0x00008000
  151. OFN_NOTESTFILECREATE :: 0x00010000
  152. OFN_NOVALIDATE :: 0x00000100
  153. OFN_OVERWRITEPROMPT :: 0x00000002
  154. OFN_PATHMUSTEXIST :: 0x00000800
  155. OFN_READONLY :: 0x00000001
  156. OFN_SHAREAWARE :: 0x00004000
  157. OFN_SHOWHELP :: 0x00000010
  158. CDERR_DIALOGFAILURE :: 0x0000FFFF
  159. CDERR_GENERALCODES :: 0x00000000
  160. CDERR_STRUCTSIZE :: 0x00000001
  161. CDERR_INITIALIZATION :: 0x00000002
  162. CDERR_NOTEMPLATE :: 0x00000003
  163. CDERR_NOHINSTANCE :: 0x00000004
  164. CDERR_LOADSTRFAILURE :: 0x00000005
  165. CDERR_FINDRESFAILURE :: 0x00000006
  166. CDERR_LOADRESFAILURE :: 0x00000007
  167. CDERR_LOCKRESFAILURE :: 0x00000008
  168. CDERR_MEMALLOCFAILURE :: 0x00000009
  169. CDERR_MEMLOCKFAILURE :: 0x0000000A
  170. CDERR_NOHOOK :: 0x0000000B
  171. CDERR_REGISTERMSGFAIL :: 0x0000000C