From 77da6e29f388c101bf782c3f67e569a112c51e92 Mon Sep 17 00:00:00 2001 From: Nuno Cruces Date: Wed, 7 Feb 2024 13:00:08 +0000 Subject: [PATCH] Use IFileSaveDialog where available. --- file_windows.go | 84 +++++++++++++++++++++++++++++++++++++++++ internal/win/shell32.go | 12 ++++++ 2 files changed, 96 insertions(+) diff --git a/file_windows.go b/file_windows.go index c730cfd..478b42a 100644 --- a/file_windows.go +++ b/file_windows.go @@ -130,6 +130,10 @@ func selectFileMultiple(opts options) ([]string, error) { } func selectFileSave(opts options) (string, error) { + name, shown, err := fileSaveDialog(opts) + if shown || opts.ctx != nil && opts.ctx.Err() != nil { + return name, err + } if opts.directory { return selectFile(opts) } @@ -280,6 +284,86 @@ func fileOpenDialog(opts options, multi bool) (string, []string, bool, error) { } } +func fileSaveDialog(opts options) (string, bool, error) { + uninit, err := coInitialize() + if err != nil { + return "", false, err + } + defer uninit() + + owner, _ := opts.attach.(win.HWND) + defer setup(owner)() + + var dialog *win.IFileSaveDialog + err = win.CoCreateInstance( + win.CLSID_FileSaveDialog, nil, win.CLSCTX_ALL, + win.IID_IFileSaveDialog, unsafe.Pointer(&dialog)) + if err != nil { + return "", false, err + } + defer dialog.Release() + + flgs, err := dialog.GetOptions() + if err != nil { + return "", false, err + } + flgs |= win.FOS_NOCHANGEDIR | win.FOS_PATHMUSTEXIST | win.FOS_NOREADONLYRETURN | win.FOS_FORCEFILESYSTEM + if opts.confirmOverwrite { + flgs |= win.FOS_OVERWRITEPROMPT + } + if opts.confirmCreate { + flgs |= win.FOS_CREATEPROMPT + } + if opts.showHidden { + flgs |= win.FOS_FORCESHOWHIDDEN + } + err = dialog.SetOptions(flgs) + if err != nil { + return "", false, err + } + + if opts.title != nil { + dialog.SetTitle(strptr(*opts.title)) + } + + if opts.filename != "" { + var item *win.IShellItem + 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) + } + } + + unhook, err := hookDialog(opts.ctx, opts.windowIcon, nil, nil) + if err != nil { + return "", false, err + } + defer unhook() + + err = dialog.Show(owner) + if opts.ctx != nil && opts.ctx.Err() != nil { + return "", true, opts.ctx.Err() + } + if err == win.E_CANCELED { + return "", true, ErrCanceled + } + if err != nil { + return "", true, err + } + + str, err := shellItemPath(dialog.GetResult()) + if err != nil { + return "", true, err + } + return str, true, nil +} + func shellItemPath(item *win.IShellItem, err error) (string, error) { if err != nil { return "", err diff --git a/internal/win/shell32.go b/internal/win/shell32.go index 04597a3..af187d7 100644 --- a/internal/win/shell32.go +++ b/internal/win/shell32.go @@ -123,7 +123,9 @@ type IDLIST struct{} var ( IID_IShellItem = guid("\x1e\x6d\x82\x43\x18\xe7\xee\x42\xbc\x55\xa1\xe2\x61\xc3\x7b\xfe") IID_IFileOpenDialog = guid("\x88\x72\x7c\xd5\xad\xd4\x68\x47\xbe\x02\x9d\x96\x95\x32\xd9\x60") + IID_IFileSaveDialog = guid("\x23\xcd\xbc\x84\xde\x5f\xdb\x4c\xae\xa4\xaf\x64\xb8\x3d\x78\xab") CLSID_FileOpenDialog = guid("\x9c\x5a\x1c\xdc\x8a\xe8\xde\x4d\xa5\xa1\x60\xf8\x2a\x20\xae\xf7") + CLSID_FileSaveDialog = guid("\xf3\xe2\xb4\xc0\x21\xba\x73\x47\x8d\xba\x33\x5e\xc9\x46\xeb\x8b") ) type IFileOpenDialog struct{ IFileDialog } @@ -142,6 +144,16 @@ func (u *IFileOpenDialog) GetResults() (res *IShellItemArray, err error) { return } +type IFileSaveDialog struct{ IFileDialog } +type IFileSaveDialogVtbl struct { + iFileDialogVtbl + SetSaveAsItem uintptr + SetProperties uintptr + SetCollectedProperties uintptr + GetProperties uintptr + ApplyProperties uintptr +} + type IFileDialog struct{ IModalWindow } type iFileDialogVtbl struct { iModalWindowVtbl