230 lines
7.9 KiB
Racket
230 lines
7.9 KiB
Racket
#lang racket/base
|
|
(require ffi/unsafe
|
|
racket/class
|
|
racket/string
|
|
"utils.rkt"
|
|
"types.rkt"
|
|
"const.rkt"
|
|
"wndclass.rkt"
|
|
"../../lock.rkt")
|
|
|
|
(provide
|
|
(protect-out file-selector))
|
|
|
|
(define-cstruct _OPENFILENAME
|
|
([lStructSize _DWORD]
|
|
[hwndOwner _HWND]
|
|
[hInstance _HINSTANCE]
|
|
[lpstrFilter _permanent-string/utf-16]
|
|
[lpstrCustomFilter _permanent-string/utf-16]
|
|
[nMaxCustFilter _DWORD]
|
|
[nFilterIndex _DWORD]
|
|
[lpstrFile _pointer]
|
|
[nMaxFile _DWORD]
|
|
[lpstrFileTitle _pointer]
|
|
[nMaxFileTitle _DWORD]
|
|
[lpstrInitialDir _permanent-string/utf-16]
|
|
[lpstrTitle _permanent-string/utf-16]
|
|
[Flags _DWORD]
|
|
[nFileOffset _WORD]
|
|
[nFileExtension _WORD]
|
|
[lpstrDefExt _permanent-string/utf-16]
|
|
[lCustData _LPARAM]
|
|
[lpfnHook _fpointer]
|
|
[lpTemplateName _permanent-string/utf-16]
|
|
[pvReserved _pointer]
|
|
[dwReserved _DWORD]
|
|
[FlagsEx _DWORD]))
|
|
|
|
(define-comdlg32 GetSaveFileNameW (_wfun _OPENFILENAME-pointer -> _BOOL))
|
|
(define-comdlg32 GetOpenFileNameW (_wfun _OPENFILENAME-pointer -> _BOOL))
|
|
|
|
(define OFN_READONLY #x00000001)
|
|
(define OFN_OVERWRITEPROMPT #x00000002)
|
|
(define OFN_HIDEREADONLY #x00000004)
|
|
(define OFN_NOCHANGEDIR #x00000008)
|
|
(define OFN_SHOWHELP #x00000010)
|
|
(define OFN_ENABLEHOOK #x00000020)
|
|
(define OFN_ENABLETEMPLATE #x00000040)
|
|
(define OFN_ENABLETEMPLATEHANDLE #x00000080)
|
|
(define OFN_ALLOWMULTISELECT #x00000200)
|
|
(define OFN_EXTENSIONDIFFERENT #x00000400)
|
|
(define OFN_PATHMUSTEXIST #x00000800)
|
|
(define OFN_FILEMUSTEXIST #x00001000)
|
|
(define OFN_NOREADONLYRETURN #x00008000)
|
|
(define OFN_EXPLORER #x00080000)
|
|
|
|
(define BUFFER-LEN 4096)
|
|
|
|
(define-cstruct _BROWSEINFO
|
|
([hwndOwner _HWND]
|
|
[pidlRoot _pointer]
|
|
[pszDisplayName _pointer]
|
|
[lpszTitle _permanent-string/utf-16]
|
|
[ulFlags _UINT]
|
|
[lpfn _pointer]
|
|
[lParam _LPARAM]
|
|
[iImage _int]))
|
|
|
|
(define BIF_RETURNONLYFSDIRS #x00000001)
|
|
(define BIF_NEWDIALOGSTYLE #x00000040)
|
|
|
|
(define-cstruct _IUnknownVtbl
|
|
([QueryInterface _fpointer]
|
|
[AddRef _fpointer]
|
|
[Release (_wfun _pointer -> _ULONG)]))
|
|
|
|
(define-cstruct (_IMallocVtbl _IUnknownVtbl)
|
|
([Alloc _fpointer]
|
|
[Realloc _fpointer]
|
|
[Free (_wfun _pointer _pointer -> _void)]
|
|
[GetSize _fpointer]
|
|
[DidAlloc _fpointer]
|
|
[HeapMinimize _fpointer]))
|
|
|
|
(define-cstruct _IMalloc
|
|
([vtbl _IMallocVtbl-pointer]))
|
|
|
|
(define (IMalloc-Free im p)
|
|
((IMallocVtbl-Free (IMalloc-vtbl im)) im p))
|
|
(define (IMalloc-Release im)
|
|
((IUnknownVtbl-Release (IMalloc-vtbl im)) im))
|
|
|
|
(define-shell32 SHBrowseForFolderW (_wfun _BROWSEINFO-pointer -> _pointer))
|
|
(define-shell32 SHGetPathFromIDListW (_wfun _pointer _pointer -> _BOOL))
|
|
(define-shell32 SHGetMalloc (_wfun (p : (_ptr o _IMalloc-pointer)) -> (r : _HRESULT)
|
|
-> (if (negative? r)
|
|
(error 'SHGetMalloc "failed: ~s" (bitwise-and #xFFFF r))
|
|
p)))
|
|
|
|
(define (file-selector message directory filename
|
|
extension
|
|
filters style parent)
|
|
(if (memq 'dir style)
|
|
(dialog-selector message directory
|
|
style parent)
|
|
(do-file-selector message directory filename
|
|
extension
|
|
filters style parent)))
|
|
|
|
(define (do-file-selector message directory filename
|
|
extension
|
|
filters style parent)
|
|
(atomically
|
|
(let* ([pre-ofn
|
|
(make-OPENFILENAME
|
|
(ctype-sizeof _OPENFILENAME)
|
|
(and parent
|
|
(send parent get-hwnd))
|
|
hInstance
|
|
(string-append
|
|
(string-join
|
|
(for/list ([f (in-list filters)])
|
|
(format "~a\0~a" (car f) (cadr f)))
|
|
"\0")
|
|
"\0")
|
|
#f
|
|
0
|
|
0 ; nFilterIndex
|
|
(malloc 'raw (* BUFFER-LEN (ctype-sizeof _short)))
|
|
BUFFER-LEN
|
|
#f
|
|
0
|
|
(and directory
|
|
(path->string (simplify-path directory #f)))
|
|
message
|
|
(bitwise-ior
|
|
OFN_HIDEREADONLY
|
|
(if (memq 'put style) OFN_OVERWRITEPROMPT 0)
|
|
(if (memq 'multi style) (bitwise-ior OFN_ALLOWMULTISELECT OFN_EXPLORER) 0)
|
|
(if directory OFN_NOCHANGEDIR 0))
|
|
0
|
|
0
|
|
extension
|
|
0
|
|
#f
|
|
#f
|
|
#f
|
|
0
|
|
0)]
|
|
[ofn (malloc 'raw (ctype-sizeof _OPENFILENAME))])
|
|
(set-cpointer-tag! ofn OPENFILENAME-tag)
|
|
(memcpy ofn pre-ofn 1 _OPENFILENAME)
|
|
(if filename
|
|
(let* ([filename (path->string (simplify-path filename #f))]
|
|
[len (utf-16-length filename)])
|
|
(memcpy (OPENFILENAME-lpstrFile ofn)
|
|
(cast filename _string/utf-16 _gcpointer)
|
|
(+ len 1)
|
|
_uint16))
|
|
(ptr-set! (OPENFILENAME-lpstrFile ofn) _uint16 0))
|
|
(let ([r (if (memq 'put style)
|
|
(GetSaveFileNameW ofn)
|
|
(GetOpenFileNameW ofn))])
|
|
(begin0
|
|
(and r
|
|
(if (memq 'multi style)
|
|
(let ([strs
|
|
(let ([p (OPENFILENAME-lpstrFile ofn)])
|
|
(let loop ([pos 0])
|
|
(cond
|
|
[(and (zero? (ptr-ref p _byte pos))
|
|
(zero? (ptr-ref p _byte (add1 pos))))
|
|
null]
|
|
[else (let ([end-pos
|
|
(let loop ([pos (+ pos 2)])
|
|
(cond
|
|
[(and (zero? (ptr-ref p _byte pos))
|
|
(zero? (ptr-ref p _byte (add1 pos))))
|
|
pos]
|
|
[else (loop (+ pos 2))]))])
|
|
(cons
|
|
(cast (ptr-add p pos) _pointer _string/utf-16)
|
|
(loop (+ end-pos 2))))])))])
|
|
(let ([len (length strs)])
|
|
(cond
|
|
[(len . < . 1) #f]
|
|
[(= len 1) (list (string->path (car strs)))]
|
|
[else
|
|
(map (lambda (p) (build-path (car strs) p))
|
|
(cdr strs))])))
|
|
(string->path (cast (OPENFILENAME-lpstrFile ofn) _pointer _string/utf-16))))
|
|
(when directory
|
|
(free (OPENFILENAME-lpstrInitialDir ofn)))
|
|
(when message
|
|
(free (OPENFILENAME-lpstrTitle ofn)))
|
|
(free (OPENFILENAME-lpstrFilter ofn))
|
|
(free (OPENFILENAME-lpstrFile ofn)))))))
|
|
|
|
(define MAX_PATH 4096)
|
|
|
|
(define (dialog-selector message directory
|
|
style parent)
|
|
(atomically
|
|
(let ([pre-bi (make-BROWSEINFO
|
|
(and parent
|
|
(send parent get-hwnd))
|
|
#f
|
|
(malloc 'raw MAX_PATH _uint16)
|
|
message
|
|
(bitwise-ior BIF_NEWDIALOGSTYLE BIF_RETURNONLYFSDIRS)
|
|
#f
|
|
0
|
|
0)]
|
|
[bi (malloc 'raw (ctype-sizeof _BROWSEINFO))])
|
|
(set-cpointer-tag! bi BROWSEINFO-tag)
|
|
(memcpy bi pre-bi 1 _BROWSEINFO)
|
|
(let ([r (SHBrowseForFolderW bi)])
|
|
(begin0
|
|
(and r
|
|
(let ([ok (SHGetPathFromIDListW r (BROWSEINFO-pszDisplayName bi))])
|
|
(and ok
|
|
(let ([mi (SHGetMalloc)])
|
|
(IMalloc-Free mi r)
|
|
(IMalloc-Release mi))
|
|
(string->path
|
|
(cast (BROWSEINFO-pszDisplayName bi) _pointer _string/utf-16)))))
|
|
(free (BROWSEINFO-pszDisplayName bi))
|
|
(when message
|
|
(free (BROWSEINFO-lpszTitle bi))))))))
|