From c56588920f359f4e7a14c7153dd796a7839e3c6b Mon Sep 17 00:00:00 2001 From: Nuno Cruces Date: Wed, 7 Feb 2024 10:50:58 +0000 Subject: [PATCH] Use IFileOpenDialog where available. --- file_windows.go | 96 ++++++++++++++++++++--------------------- internal/win/shell32.go | 18 ++++++++ 2 files changed, 65 insertions(+), 49 deletions(-) diff --git a/file_windows.go b/file_windows.go index c29d84b..c730cfd 100644 --- a/file_windows.go +++ b/file_windows.go @@ -13,9 +13,12 @@ import ( ) func selectFile(opts options) (string, error) { + name, _, shown, err := fileOpenDialog(opts, false) + if shown || opts.ctx != nil && opts.ctx.Err() != nil { + return name, err + } if opts.directory { - res, _, err := pickFolders(opts, false) - return res, err + return browseForFolder(opts) } var args win.OPENFILENAME @@ -38,12 +41,6 @@ func selectFile(opts options) (string, error) { args.MaxFile = uint32(len(res)) args.InitialDir, args.DefExt = initDirNameExt(opts.filename, res[:]) - uninit, err := coInitialize() - if err != nil { - return "", err - } - defer uninit() - defer setup(args.Owner)() unhook, err := hookDialog(opts.ctx, opts.windowIcon, nil, nil) if err != nil { @@ -62,9 +59,12 @@ func selectFile(opts options) (string, error) { } func selectFileMultiple(opts options) ([]string, error) { + _, list, shown, err := fileOpenDialog(opts, true) + if shown || opts.ctx != nil && opts.ctx.Err() != nil { + return list, err + } if opts.directory { - _, res, err := pickFolders(opts, true) - return res, err + return nil, fmt.Errorf("%w: multiple directory", ErrUnsupported) } var args win.OPENFILENAME @@ -87,12 +87,6 @@ func selectFileMultiple(opts options) ([]string, error) { args.MaxFile = uint32(len(res)) args.InitialDir, args.DefExt = initDirNameExt(opts.filename, res[:]) - uninit, err := coInitialize() - if err != nil { - return nil, err - } - defer uninit() - defer setup(args.Owner)() unhook, err := hookDialog(opts.ctx, opts.windowIcon, nil, nil) if err != nil { @@ -137,8 +131,7 @@ func selectFileMultiple(opts options) ([]string, error) { func selectFileSave(opts options) (string, error) { if opts.directory { - res, _, err := pickFolders(opts, false) - return res, err + return selectFile(opts) } var args win.OPENFILENAME @@ -167,12 +160,6 @@ func selectFileSave(opts options) (string, error) { args.MaxFile = uint32(len(res)) args.InitialDir, args.DefExt = initDirNameExt(opts.filename, res[:]) - uninit, err := coInitialize() - if err != nil { - return "", err - } - defer uninit() - defer setup(args.Owner)() unhook, err := hookDialog(opts.ctx, opts.windowIcon, nil, nil) if err != nil { @@ -190,10 +177,10 @@ func selectFileSave(opts options) (string, error) { return syscall.UTF16ToString(res[:]), nil } -func pickFolders(opts options, multi bool) (string, []string, error) { +func fileOpenDialog(opts options, multi bool) (string, []string, bool, error) { uninit, err := coInitialize() if err != nil { - return "", nil, err + return "", nil, false, err } defer uninit() @@ -205,27 +192,27 @@ func pickFolders(opts options, multi bool) (string, []string, error) { win.CLSID_FileOpenDialog, nil, win.CLSCTX_ALL, win.IID_IFileOpenDialog, unsafe.Pointer(&dialog)) if err != nil { - if multi { - return "", nil, fmt.Errorf("%w: multiple directory", ErrUnsupported) - } - return browseForFolder(opts) + return "", nil, false, err } defer dialog.Release() flgs, err := dialog.GetOptions() if err != nil { - return "", nil, err + return "", nil, false, err } - flgs |= win.FOS_NOCHANGEDIR | win.FOS_PICKFOLDERS | win.FOS_FORCEFILESYSTEM + flgs |= win.FOS_NOCHANGEDIR | win.FOS_FILEMUSTEXIST | win.FOS_FORCEFILESYSTEM if multi { flgs |= win.FOS_ALLOWMULTISELECT } + if opts.directory { + flgs |= win.FOS_PICKFOLDERS + } if opts.showHidden { flgs |= win.FOS_FORCESHOWHIDDEN } err = dialog.SetOptions(flgs) if err != nil { - return "", nil, err + return "", nil, false, err } if opts.title != nil { @@ -234,7 +221,12 @@ func pickFolders(opts options, multi bool) (string, []string, error) { if opts.filename != "" { var item *win.IShellItem - win.SHCreateItemFromParsingName(strptr(opts.filename), nil, win.IID_IShellItem, &item) + dir, name, _ := splitDirAndName(opts.filename) + dialog.SetFileName(strptr(name)) + if ext := filepath.Ext(name); len(ext) > 1 { + dialog.SetDefaultExtension(strptr(ext[1:])) + } + win.SHCreateItemFromParsingName(strptr(dir), nil, win.IID_IShellItem, &item) if item != nil { defer item.Release() dialog.SetFolder(item) @@ -243,48 +235,48 @@ func pickFolders(opts options, multi bool) (string, []string, error) { unhook, err := hookDialog(opts.ctx, opts.windowIcon, nil, nil) if err != nil { - return "", nil, err + return "", nil, false, err } defer unhook() err = dialog.Show(owner) if opts.ctx != nil && opts.ctx.Err() != nil { - return "", nil, opts.ctx.Err() + return "", nil, true, opts.ctx.Err() } if err == win.E_CANCELED { - return "", nil, ErrCanceled + return "", nil, true, ErrCanceled } if err != nil { - return "", nil, err + return "", nil, true, err } if multi { items, err := dialog.GetResults() if err != nil { - return "", nil, err + return "", nil, true, err } defer items.Release() count, err := items.GetCount() if err != nil { - return "", nil, err + return "", nil, true, err } var lst []string for i := uint32(0); i < count && err == nil; i++ { str, err := shellItemPath(items.GetItemAt(i)) if err != nil { - return "", nil, err + return "", nil, true, err } lst = append(lst, str) } - return "", lst, nil + return "", lst, true, nil } else { str, err := shellItemPath(dialog.GetResult()) if err != nil { - return "", nil, err + return "", nil, true, err } - return str, nil, nil + return str, nil, true, nil } } @@ -296,7 +288,13 @@ func shellItemPath(item *win.IShellItem, err error) (string, error) { return item.GetDisplayName(win.SIGDN_FILESYSPATH) } -func browseForFolder(opts options) (string, []string, error) { +func browseForFolder(opts options) (string, error) { + uninit, err := coInitialize() + if err != nil { + return "", err + } + defer uninit() + var args win.BROWSEINFO args.Owner, _ = opts.attach.(win.HWND) args.Flags = win.BIF_RETURNONLYFSDIRS @@ -311,16 +309,16 @@ func browseForFolder(opts options) (string, []string, error) { unhook, err := hookDialog(opts.ctx, opts.windowIcon, nil, nil) if err != nil { - return "", nil, err + return "", err } defer unhook() ptr := win.SHBrowseForFolder(&args) if opts.ctx != nil && opts.ctx.Err() != nil { - return "", nil, opts.ctx.Err() + return "", opts.ctx.Err() } if ptr == nil { - return "", nil, ErrCanceled + return "", ErrCanceled } defer win.CoTaskMemFree(unsafe.Pointer(ptr)) @@ -328,7 +326,7 @@ func browseForFolder(opts options) (string, []string, error) { win.SHGetPathFromIDListEx(ptr, &res[0], len(res), 0) str := syscall.UTF16ToString(res[:]) - return str, []string{str}, nil + return str, nil } func browseForFolderCallback(wnd win.HWND, msg uint32, lparam, data uintptr) uintptr { diff --git a/internal/win/shell32.go b/internal/win/shell32.go index ccfa0f0..04597a3 100644 --- a/internal/win/shell32.go +++ b/internal/win/shell32.go @@ -197,6 +197,15 @@ func (u *IFileDialog) SetFolder(item *IShellItem) (err error) { return } +func (u *IFileDialog) SetFileName(name *uint16) (err error) { + vtbl := *(**iFileDialogVtbl)(unsafe.Pointer(u)) + hr, _, _ := u.call(vtbl.SetFileName, uintptr(unsafe.Pointer(name))) + if hr != 0 { + err = syscall.Errno(hr) + } + return +} + func (u *IFileDialog) SetTitle(title *uint16) (err error) { vtbl := *(**iFileDialogVtbl)(unsafe.Pointer(u)) hr, _, _ := u.call(vtbl.SetTitle, uintptr(unsafe.Pointer(title))) @@ -206,6 +215,15 @@ func (u *IFileDialog) SetTitle(title *uint16) (err error) { return } +func (u *IFileDialog) SetDefaultExtension(extension *uint16) (err error) { + vtbl := *(**iFileDialogVtbl)(unsafe.Pointer(u)) + hr, _, _ := u.call(vtbl.SetDefaultExtension, uintptr(unsafe.Pointer(extension))) + if hr != 0 { + err = syscall.Errno(hr) + } + return +} + func (u *IFileDialog) GetResult() (item *IShellItem, err error) { vtbl := *(**iFileDialogVtbl)(unsafe.Pointer(u)) hr, _, _ := u.call(vtbl.GetResult, uintptr(unsafe.Pointer(&item)))