Move go-common-file-dialog into project. Fix Title bug.

This commit is contained in:
Lea Anthony
2022-06-01 21:00:52 +10:00
parent 614f50d5d3
commit 6a07cbd505
27 changed files with 1125 additions and 26 deletions
@@ -53,8 +53,6 @@ github.com/leaanthony/debme v1.2.1 h1:9Tgwf+kjcrbMQ4WnPcEIUcQuIZYqdWftzZkBr+i/oO
github.com/leaanthony/debme v1.2.1/go.mod h1:3V+sCm5tYAgQymvSOfYQ5Xx2JCr+OXiD9Jkw3otUjiA=
github.com/leaanthony/go-ansi-parser v1.0.1 h1:97v6c5kYppVsbScf4r/VZdXyQ21KQIfeQOk2DgKxGG4=
github.com/leaanthony/go-ansi-parser v1.0.1/go.mod h1:7arTzgVI47srICYhvgUV4CGd063sGEeoSlych5yeSPM=
github.com/leaanthony/go-common-file-dialog v1.0.3 h1:O0uGjKnWtdEADGrkg+TyAAbZylykMwwx/MNEXn9fp+Y=
github.com/leaanthony/go-common-file-dialog v1.0.3/go.mod h1:TGhEc9eSJgRsupZ+iH1ZgAOnEo9zp05cRH2j08RPrF0=
github.com/leaanthony/gosod v1.0.3 h1:Fnt+/B6NjQOVuCWOKYRREZnjGyvg+mEhd1nkkA04aTQ=
github.com/leaanthony/gosod v1.0.3/go.mod h1:BJ2J+oHsQIyIQpnLPjnqFGTMnOZXDbvWtRCSG7jGxs4=
github.com/leaanthony/idgen v1.0.0/go.mod h1:4nBZnt8ml/f/ic/EVQuLxuj817RccT2fyrUaZFxrcVA=
@@ -12,7 +12,6 @@ module changeme
github.com/labstack/echo/v4 v4.7.2 // indirect
github.com/labstack/gommon v0.3.1 // indirect
github.com/leaanthony/go-ansi-parser v1.0.1 // indirect
github.com/leaanthony/go-common-file-dialog v1.0.3 // indirect
github.com/leaanthony/gosod v1.0.3 // indirect
github.com/leaanthony/slicer v1.5.0 // indirect
github.com/leaanthony/typescriptify-golang-structs v0.1.7 // indirect
@@ -18,7 +18,6 @@ github.com/jchv/go-winloader v0.0.0-20200815041850-dec1ee9a7fd5 // indirect
github.com/klauspost/compress v1.12.2 // indirect
github.com/leaanthony/debme v1.2.1 // indirect
github.com/leaanthony/go-ansi-parser v1.0.1 // indirect
github.com/leaanthony/go-common-file-dialog v1.0.3 // indirect
github.com/leaanthony/slicer v1.5.0 // indirect
github.com/leaanthony/typescriptify-golang-structs v0.1.7 // indirect
github.com/pkg/browser v0.0.0-20210706143420-7d21f8c997e2 // indirect
@@ -81,8 +81,6 @@ github.com/leaanthony/debme v1.2.1 h1:9Tgwf+kjcrbMQ4WnPcEIUcQuIZYqdWftzZkBr+i/oO
github.com/leaanthony/debme v1.2.1/go.mod h1:3V+sCm5tYAgQymvSOfYQ5Xx2JCr+OXiD9Jkw3otUjiA=
github.com/leaanthony/go-ansi-parser v1.0.1 h1:97v6c5kYppVsbScf4r/VZdXyQ21KQIfeQOk2DgKxGG4=
github.com/leaanthony/go-ansi-parser v1.0.1/go.mod h1:7arTzgVI47srICYhvgUV4CGd063sGEeoSlych5yeSPM=
github.com/leaanthony/go-common-file-dialog v1.0.3 h1:O0uGjKnWtdEADGrkg+TyAAbZylykMwwx/MNEXn9fp+Y=
github.com/leaanthony/go-common-file-dialog v1.0.3/go.mod h1:TGhEc9eSJgRsupZ+iH1ZgAOnEo9zp05cRH2j08RPrF0=
github.com/leaanthony/gosod v1.0.3/go.mod h1:BJ2J+oHsQIyIQpnLPjnqFGTMnOZXDbvWtRCSG7jGxs4=
github.com/leaanthony/idgen v1.0.0/go.mod h1:4nBZnt8ml/f/ic/EVQuLxuj817RccT2fyrUaZFxrcVA=
github.com/leaanthony/slicer v1.5.0 h1:aHYTN8xbCCLxJmkNKiLB6tgcMARl4eWmH9/F+S/0HtY=
@@ -18,7 +18,6 @@ github.com/jchv/go-winloader v0.0.0-20200815041850-dec1ee9a7fd5 // indirect
github.com/klauspost/compress v1.12.2 // indirect
github.com/leaanthony/debme v1.2.1 // indirect
github.com/leaanthony/go-ansi-parser v1.0.1 // indirect
github.com/leaanthony/go-common-file-dialog v1.0.3 // indirect
github.com/leaanthony/slicer v1.5.0 // indirect
github.com/leaanthony/typescriptify-golang-structs v0.1.7 // indirect
github.com/pkg/browser v0.0.0-20210706143420-7d21f8c997e2 // indirect
@@ -81,8 +81,6 @@ github.com/leaanthony/debme v1.2.1 h1:9Tgwf+kjcrbMQ4WnPcEIUcQuIZYqdWftzZkBr+i/oO
github.com/leaanthony/debme v1.2.1/go.mod h1:3V+sCm5tYAgQymvSOfYQ5Xx2JCr+OXiD9Jkw3otUjiA=
github.com/leaanthony/go-ansi-parser v1.0.1 h1:97v6c5kYppVsbScf4r/VZdXyQ21KQIfeQOk2DgKxGG4=
github.com/leaanthony/go-ansi-parser v1.0.1/go.mod h1:7arTzgVI47srICYhvgUV4CGd063sGEeoSlych5yeSPM=
github.com/leaanthony/go-common-file-dialog v1.0.3 h1:O0uGjKnWtdEADGrkg+TyAAbZylykMwwx/MNEXn9fp+Y=
github.com/leaanthony/go-common-file-dialog v1.0.3/go.mod h1:TGhEc9eSJgRsupZ+iH1ZgAOnEo9zp05cRH2j08RPrF0=
github.com/leaanthony/gosod v1.0.3/go.mod h1:BJ2J+oHsQIyIQpnLPjnqFGTMnOZXDbvWtRCSG7jGxs4=
github.com/leaanthony/idgen v1.0.0/go.mod h1:4nBZnt8ml/f/ic/EVQuLxuj817RccT2fyrUaZFxrcVA=
github.com/leaanthony/slicer v1.5.0 h1:aHYTN8xbCCLxJmkNKiLB6tgcMARl4eWmH9/F+S/0HtY=
-1
View File
@@ -17,7 +17,6 @@ require (
github.com/leaanthony/clir v1.0.4
github.com/leaanthony/debme v1.2.1
github.com/leaanthony/go-ansi-parser v1.0.1
github.com/leaanthony/go-common-file-dialog v1.0.3
github.com/leaanthony/gosod v1.0.3
github.com/leaanthony/idgen v1.0.0
github.com/leaanthony/slicer v1.5.0
-2
View File
@@ -79,8 +79,6 @@ github.com/leaanthony/debme v1.2.1 h1:9Tgwf+kjcrbMQ4WnPcEIUcQuIZYqdWftzZkBr+i/oO
github.com/leaanthony/debme v1.2.1/go.mod h1:3V+sCm5tYAgQymvSOfYQ5Xx2JCr+OXiD9Jkw3otUjiA=
github.com/leaanthony/go-ansi-parser v1.0.1 h1:97v6c5kYppVsbScf4r/VZdXyQ21KQIfeQOk2DgKxGG4=
github.com/leaanthony/go-ansi-parser v1.0.1/go.mod h1:7arTzgVI47srICYhvgUV4CGd063sGEeoSlych5yeSPM=
github.com/leaanthony/go-common-file-dialog v1.0.3 h1:O0uGjKnWtdEADGrkg+TyAAbZylykMwwx/MNEXn9fp+Y=
github.com/leaanthony/go-common-file-dialog v1.0.3/go.mod h1:TGhEc9eSJgRsupZ+iH1ZgAOnEo9zp05cRH2j08RPrF0=
github.com/leaanthony/gosod v1.0.3 h1:Fnt+/B6NjQOVuCWOKYRREZnjGyvg+mEhd1nkkA04aTQ=
github.com/leaanthony/gosod v1.0.3/go.mod h1:BJ2J+oHsQIyIQpnLPjnqFGTMnOZXDbvWtRCSG7jGxs4=
github.com/leaanthony/idgen v1.0.0 h1:IZreR+JGEzFV4yeVuBZA25gM0keUoFy+RDUldncQ+Jw=
@@ -14,7 +14,7 @@ import (
"strconv"
"syscall"
"github.com/leaanthony/go-common-file-dialog/cfd"
"github.com/wailsapp/wails/v2/internal/go-common-file-dialog/cfd"
"github.com/wailsapp/wails/v2/pkg/runtime"
"golang.org/x/sys/windows"
@@ -4,8 +4,8 @@
package windows
import (
"github.com/leaanthony/go-common-file-dialog/cfd"
"github.com/wailsapp/wails/v2/internal/frontend"
"github.com/wailsapp/wails/v2/internal/go-common-file-dialog/cfd"
"golang.org/x/sys/windows"
"syscall"
)
+21
View File
@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2019 Harry Phillips
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
@@ -0,0 +1,31 @@
# Common File Dialog bindings for Golang
[Project Home](https://github.com/harry1453/go-common-file-dialog)
This library contains bindings for Windows Vista and
newer's [Common File Dialogs](https://docs.microsoft.com/en-us/windows/win32/shell/common-file-dialog), which is the
standard system dialog for selecting files or folders to open or save.
The Common File Dialogs have to be accessed via
the [COM Interface](https://en.wikipedia.org/wiki/Component_Object_Model), normally via C++ or via bindings (like in C#)
.
This library contains bindings for Golang. **It does not require CGO**, and contains empty stubs for non-windows
platforms (so is safe to compile and run on platforms other than windows, but will just return errors at runtime).
This can be very useful if you want to quickly get a file selector in your Golang application. The `cfdutil` package
contains utility functions with a single call to open and configure a dialog, and then get the result from it. Examples
for this are in [`_examples/usingutil`](_examples/usingutil). Or, if you want finer control over the dialog's operation,
you can use the base package. Examples for this are in [`_examples/notusingutil`](_examples/notusingutil).
This library is available under the MIT license.
Currently supported features:
* Open File Dialog (to open a single file)
* Open Multiple Files Dialog (to open multiple files)
* Open Folder Dialog
* Save File Dialog
* Dialog "roles" to allow Windows to remember different "last locations" for different types of dialog
* Set dialog Title, Default Folder and Initial Folder
* Set dialog File Filters
@@ -0,0 +1,72 @@
// Cross-platform.
// Common File Dialogs
package cfd
type Dialog interface {
// Show the dialog to the user.
// Blocks until the user has closed the dialog.
Show() error
// Sets the dialog's parent window. Use 0 to set the dialog to have no parent window.
SetParentWindowHandle(hwnd uintptr)
// Show the dialog to the user.
// Blocks until the user has closed the dialog and returns their selection.
// Returns an error if the user cancelled the dialog.
// Do not use for the Open Multiple Files dialog. Use ShowAndGetResults instead.
ShowAndGetResult() (string, error)
// Sets the title of the dialog window.
SetTitle(title string) error
// Sets the "role" of the dialog. This is used to derive the dialog's GUID, which the
// OS will use to differentiate it from dialogs that are intended for other purposes.
// This means that, for example, a dialog with role "Import" will have a different
// previous location that it will open to than a dialog with role "Open". Can be any string.
SetRole(role string) error
// Sets the folder used as a default if there is not a recently used folder value available
SetDefaultFolder(defaultFolder string) error
// Sets the folder that the dialog always opens to.
// If this is set, it will override the "default folder" behaviour and the dialog will always open to this folder.
SetFolder(folder string) error
// Gets the selected file or folder path, as an absolute path eg. "C:\Folder\file.txt"
// Do not use for the Open Multiple Files dialog. Use GetResults instead.
GetResult() (string, error)
// Sets the file name, I.E. the contents of the file name text box.
// For Select Folder Dialog, sets folder name.
SetFileName(fileName string) error
// Release the resources allocated to this Dialog.
// Should be called when the dialog is finished with.
Release() error
}
type FileDialog interface {
Dialog
// Set the list of file filters that the user can select.
SetFileFilters(fileFilter []FileFilter) error
// Set the selected item from the list of file filters (set using SetFileFilters) by its index. Defaults to 0 (the first item in the list) if not called.
SetSelectedFileFilterIndex(index uint) error
// Sets the default extension applied when a user does not provide one as part of the file name.
// If the user selects a different file filter, the default extension will be automatically updated to match the new file filter.
// For Open / Open Multiple File Dialog, this only has an effect when the user specifies a file name with no extension and a file with the default extension exists.
// For Save File Dialog, this extension will be used whenever a user does not specify an extension.
SetDefaultExtension(defaultExtension string) error
}
type OpenFileDialog interface {
FileDialog
}
type OpenMultipleFilesDialog interface {
FileDialog
// Show the dialog to the user.
// Blocks until the user has closed the dialog and returns the selected files.
ShowAndGetResults() ([]string, error)
// Gets the selected file paths, as absolute paths eg. "C:\Folder\file.txt"
GetResults() ([]string, error)
}
type SelectFolderDialog interface {
Dialog
}
type SaveFileDialog interface { // TODO Properties
FileDialog
}
@@ -0,0 +1,28 @@
//go:build !windows
// +build !windows
package cfd
import "fmt"
var unsupportedError = fmt.Errorf("common file dialogs are only available on windows")
// TODO doc
func NewOpenFileDialog(config DialogConfig) (OpenFileDialog, error) {
return nil, unsupportedError
}
// TODO doc
func NewOpenMultipleFilesDialog(config DialogConfig) (OpenMultipleFilesDialog, error) {
return nil, unsupportedError
}
// TODO doc
func NewSelectFolderDialog(config DialogConfig) (SelectFolderDialog, error) {
return nil, unsupportedError
}
// TODO doc
func NewSaveFileDialog(config DialogConfig) (SaveFileDialog, error) {
return nil, unsupportedError
}
@@ -0,0 +1,79 @@
//go:build windows
// +build windows
package cfd
import "github.com/go-ole/go-ole"
func initialize() {
// Swallow error
_ = ole.CoInitializeEx(0, ole.COINIT_APARTMENTTHREADED|ole.COINIT_DISABLE_OLE1DDE)
}
// TODO doc
func NewOpenFileDialog(config DialogConfig) (OpenFileDialog, error) {
initialize()
openDialog, err := newIFileOpenDialog()
if err != nil {
return nil, err
}
err = config.apply(openDialog)
if err != nil {
return nil, err
}
return openDialog, nil
}
// TODO doc
func NewOpenMultipleFilesDialog(config DialogConfig) (OpenMultipleFilesDialog, error) {
initialize()
openDialog, err := newIFileOpenDialog()
if err != nil {
return nil, err
}
err = config.apply(openDialog)
if err != nil {
return nil, err
}
err = openDialog.setIsMultiselect(true)
if err != nil {
return nil, err
}
return openDialog, nil
}
// TODO doc
func NewSelectFolderDialog(config DialogConfig) (SelectFolderDialog, error) {
initialize()
openDialog, err := newIFileOpenDialog()
if err != nil {
return nil, err
}
err = config.apply(openDialog)
if err != nil {
return nil, err
}
err = openDialog.setPickFolders(true)
if err != nil {
return nil, err
}
return openDialog, nil
}
// TODO doc
func NewSaveFileDialog(config DialogConfig) (SaveFileDialog, error) {
initialize()
saveDialog, err := newIFileSaveDialog()
if err != nil {
return nil, err
}
err = config.apply(saveDialog)
if err != nil {
return nil, err
}
return saveDialog, nil
}
@@ -0,0 +1,120 @@
// Cross-platform.
package cfd
type FileFilter struct {
// The display name of the filter (That is shown to the user)
DisplayName string
// The filter pattern. Eg. "*.txt;*.png" to select all txt and png files, "*.*" to select any files, etc.
Pattern string
}
type DialogConfig struct {
// The title of the dialog
Title string
// The role of the dialog. This is used to derive the dialog's GUID, which the
// OS will use to differentiate it from dialogs that are intended for other purposes.
// This means that, for example, a dialog with role "Import" will have a different
// previous location that it will open to than a dialog with role "Open". Can be any string.
Role string
// The default folder - the folder that is used the first time the user opens it
// (after the first time their last used location is used).
DefaultFolder string
// The initial folder - the folder that the dialog always opens to if not empty.
// If this is not empty, it will override the "default folder" behaviour and
// the dialog will always open to this folder.
Folder string
// The file filters that restrict which types of files the dialog is able to choose.
// Ignored by Select Folder Dialog.
FileFilters []FileFilter
// Sets the initially selected file filter. This is an index of FileFilters.
// Ignored by Select Folder Dialog.
SelectedFileFilterIndex uint
// The initial name of the file (I.E. the text in the file name text box) when the user opens the dialog.
// For the Select Folder Dialog, this sets the initial folder name.
FileName string
// The default extension applied when a user does not provide one as part of the file name.
// If the user selects a different file filter, the default extension will be automatically updated to match the new file filter.
// For Open / Open Multiple File Dialog, this only has an effect when the user specifies a file name with no extension and a file with the default extension exists.
// For Save File Dialog, this extension will be used whenever a user does not specify an extension.
// Ignored by Select Folder Dialog.
DefaultExtension string
// ParentWindowHandle is the handle (HWND) to the parent window of the dialog.
// If left as 0 / nil, the dialog will have no parent window.
ParentWindowHandle uintptr
}
var defaultFilters = []FileFilter{
{
DisplayName: "All Files (*.*)",
Pattern: "*.*",
},
}
func (config *DialogConfig) apply(dialog Dialog) (err error) {
if config.Title != "" {
err = dialog.SetTitle(config.Title)
if err != nil {
return
}
}
if config.Role != "" {
err = dialog.SetRole(config.Role)
if err != nil {
return
}
}
if config.Folder != "" {
err = dialog.SetFolder(config.Folder)
if err != nil {
return
}
}
if config.DefaultFolder != "" {
err = dialog.SetDefaultFolder(config.DefaultFolder)
if err != nil {
return
}
}
if config.FileName != "" {
err = dialog.SetFileName(config.FileName)
if err != nil {
return
}
}
dialog.SetParentWindowHandle(config.ParentWindowHandle)
if dialog, ok := dialog.(FileDialog); ok {
var fileFilters []FileFilter
if config.FileFilters != nil && len(config.FileFilters) > 0 {
fileFilters = config.FileFilters
} else {
fileFilters = defaultFilters
}
err = dialog.SetFileFilters(fileFilters)
if err != nil {
return
}
if config.SelectedFileFilterIndex != 0 {
err = dialog.SetSelectedFileFilterIndex(config.SelectedFileFilterIndex)
if err != nil {
return
}
}
if config.DefaultExtension != "" {
err = dialog.SetDefaultExtension(config.DefaultExtension)
if err != nil {
return
}
}
}
return
}
@@ -0,0 +1,7 @@
package cfd
import "errors"
var (
ErrorCancelled = errors.New("cancelled by user")
)
@@ -0,0 +1,197 @@
//go:build windows
// +build windows
package cfd
import (
"github.com/go-ole/go-ole"
"github.com/wailsapp/wails/v2/internal/go-common-file-dialog/util"
"syscall"
"unsafe"
)
var (
fileOpenDialogCLSID = ole.NewGUID("{DC1C5A9C-E88A-4dde-A5A1-60F82A20AEF7}")
fileOpenDialogIID = ole.NewGUID("{d57c7288-d4ad-4768-be02-9d969532d960}")
)
type iFileOpenDialog struct {
vtbl *iFileOpenDialogVtbl
parentWindowHandle uintptr
}
type iFileOpenDialogVtbl struct {
iFileDialogVtbl
GetResults uintptr // func (ppenum **IShellItemArray) HRESULT
GetSelectedItems uintptr
}
func newIFileOpenDialog() (*iFileOpenDialog, error) {
if unknown, err := ole.CreateInstance(fileOpenDialogCLSID, fileOpenDialogIID); err == nil {
return (*iFileOpenDialog)(unsafe.Pointer(unknown)), nil
} else {
return nil, err
}
}
func (fileOpenDialog *iFileOpenDialog) Show() error {
return fileOpenDialog.vtbl.show(unsafe.Pointer(fileOpenDialog), fileOpenDialog.parentWindowHandle)
}
func (fileOpenDialog *iFileOpenDialog) SetParentWindowHandle(hwnd uintptr) {
fileOpenDialog.parentWindowHandle = hwnd
}
func (fileOpenDialog *iFileOpenDialog) ShowAndGetResult() (string, error) {
isMultiselect, err := fileOpenDialog.isMultiselect()
if err != nil {
return "", err
}
if isMultiselect {
// We should panic as this error is caused by the developer using the library
panic("use ShowAndGetResults for open multiple files dialog")
}
if err := fileOpenDialog.Show(); err != nil {
return "", err
}
return fileOpenDialog.GetResult()
}
func (fileOpenDialog *iFileOpenDialog) ShowAndGetResults() ([]string, error) {
isMultiselect, err := fileOpenDialog.isMultiselect()
if err != nil {
return nil, err
}
if !isMultiselect {
// We should panic as this error is caused by the developer using the library
panic("use ShowAndGetResult for open single file dialog")
}
if err := fileOpenDialog.Show(); err != nil {
return nil, err
}
return fileOpenDialog.GetResults()
}
func (fileOpenDialog *iFileOpenDialog) SetTitle(title string) error {
return fileOpenDialog.vtbl.setTitle(unsafe.Pointer(fileOpenDialog), title)
}
func (fileOpenDialog *iFileOpenDialog) GetResult() (string, error) {
isMultiselect, err := fileOpenDialog.isMultiselect()
if err != nil {
return "", err
}
if isMultiselect {
// We should panic as this error is caused by the developer using the library
panic("use GetResults for open multiple files dialog")
}
return fileOpenDialog.vtbl.getResultString(unsafe.Pointer(fileOpenDialog))
}
func (fileOpenDialog *iFileOpenDialog) Release() error {
return fileOpenDialog.vtbl.release(unsafe.Pointer(fileOpenDialog))
}
func (fileOpenDialog *iFileOpenDialog) SetDefaultFolder(defaultFolderPath string) error {
return fileOpenDialog.vtbl.setDefaultFolder(unsafe.Pointer(fileOpenDialog), defaultFolderPath)
}
func (fileOpenDialog *iFileOpenDialog) SetFolder(defaultFolderPath string) error {
return fileOpenDialog.vtbl.setFolder(unsafe.Pointer(fileOpenDialog), defaultFolderPath)
}
func (fileOpenDialog *iFileOpenDialog) SetFileFilters(filter []FileFilter) error {
return fileOpenDialog.vtbl.setFileTypes(unsafe.Pointer(fileOpenDialog), filter)
}
func (fileOpenDialog *iFileOpenDialog) SetRole(role string) error {
return fileOpenDialog.vtbl.setClientGuid(unsafe.Pointer(fileOpenDialog), util.StringToUUID(role))
}
// This should only be callable when the user asks for a multi select because
// otherwise they will be given the Dialog interface which does not expose this function.
func (fileOpenDialog *iFileOpenDialog) GetResults() ([]string, error) {
isMultiselect, err := fileOpenDialog.isMultiselect()
if err != nil {
return nil, err
}
if !isMultiselect {
// We should panic as this error is caused by the developer using the library
panic("use GetResult for open single file dialog")
}
return fileOpenDialog.vtbl.getResultsStrings(unsafe.Pointer(fileOpenDialog))
}
func (fileOpenDialog *iFileOpenDialog) SetDefaultExtension(defaultExtension string) error {
return fileOpenDialog.vtbl.setDefaultExtension(unsafe.Pointer(fileOpenDialog), defaultExtension)
}
func (fileOpenDialog *iFileOpenDialog) SetFileName(initialFileName string) error {
return fileOpenDialog.vtbl.setFileName(unsafe.Pointer(fileOpenDialog), initialFileName)
}
func (fileOpenDialog *iFileOpenDialog) SetSelectedFileFilterIndex(index uint) error {
return fileOpenDialog.vtbl.setSelectedFileFilterIndex(unsafe.Pointer(fileOpenDialog), index)
}
func (fileOpenDialog *iFileOpenDialog) setPickFolders(pickFolders bool) error {
const FosPickfolders = 0x20
if pickFolders {
return fileOpenDialog.vtbl.addOption(unsafe.Pointer(fileOpenDialog), FosPickfolders)
} else {
return fileOpenDialog.vtbl.removeOption(unsafe.Pointer(fileOpenDialog), FosPickfolders)
}
}
const FosAllowMultiselect = 0x200
func (fileOpenDialog *iFileOpenDialog) isMultiselect() (bool, error) {
options, err := fileOpenDialog.vtbl.getOptions(unsafe.Pointer(fileOpenDialog))
if err != nil {
return false, err
}
return options&FosAllowMultiselect != 0, nil
}
func (fileOpenDialog *iFileOpenDialog) setIsMultiselect(isMultiselect bool) error {
if isMultiselect {
return fileOpenDialog.vtbl.addOption(unsafe.Pointer(fileOpenDialog), FosAllowMultiselect)
} else {
return fileOpenDialog.vtbl.removeOption(unsafe.Pointer(fileOpenDialog), FosAllowMultiselect)
}
}
func (vtbl *iFileOpenDialogVtbl) getResults(objPtr unsafe.Pointer) (*iShellItemArray, error) {
var shellItemArray *iShellItemArray
ret, _, _ := syscall.Syscall(vtbl.GetResults,
1,
uintptr(objPtr),
uintptr(unsafe.Pointer(&shellItemArray)),
0)
return shellItemArray, hresultToError(ret)
}
func (vtbl *iFileOpenDialogVtbl) getResultsStrings(objPtr unsafe.Pointer) ([]string, error) {
shellItemArray, err := vtbl.getResults(objPtr)
if err != nil {
return nil, err
}
if shellItemArray == nil {
return nil, ErrorCancelled
}
defer shellItemArray.vtbl.release(unsafe.Pointer(shellItemArray))
count, err := shellItemArray.vtbl.getCount(unsafe.Pointer(shellItemArray))
if err != nil {
return nil, err
}
var results []string
for i := uintptr(0); i < count; i++ {
newItem, err := shellItemArray.vtbl.getItemAt(unsafe.Pointer(shellItemArray), i)
if err != nil {
return nil, err
}
results = append(results, newItem)
}
return results, nil
}
@@ -0,0 +1,93 @@
//go:build windows
// +build windows
package cfd
import (
"github.com/go-ole/go-ole"
"github.com/wailsapp/wails/v2/internal/go-common-file-dialog/util"
"unsafe"
)
var (
saveFileDialogCLSID = ole.NewGUID("{C0B4E2F3-BA21-4773-8DBA-335EC946EB8B}")
saveFileDialogIID = ole.NewGUID("{84bccd23-5fde-4cdb-aea4-af64b83d78ab}")
)
type iFileSaveDialog struct {
vtbl *iFileSaveDialogVtbl
parentWindowHandle uintptr
}
type iFileSaveDialogVtbl struct {
iFileDialogVtbl
SetSaveAsItem uintptr
SetProperties uintptr
SetCollectedProperties uintptr
GetProperties uintptr
ApplyProperties uintptr
}
func newIFileSaveDialog() (*iFileSaveDialog, error) {
if unknown, err := ole.CreateInstance(saveFileDialogCLSID, saveFileDialogIID); err == nil {
return (*iFileSaveDialog)(unsafe.Pointer(unknown)), nil
} else {
return nil, err
}
}
func (fileSaveDialog *iFileSaveDialog) Show() error {
return fileSaveDialog.vtbl.show(unsafe.Pointer(fileSaveDialog), fileSaveDialog.parentWindowHandle)
}
func (fileSaveDialog *iFileSaveDialog) SetParentWindowHandle(hwnd uintptr) {
fileSaveDialog.parentWindowHandle = hwnd
}
func (fileSaveDialog *iFileSaveDialog) ShowAndGetResult() (string, error) {
if err := fileSaveDialog.Show(); err != nil {
return "", err
}
return fileSaveDialog.GetResult()
}
func (fileSaveDialog *iFileSaveDialog) SetTitle(title string) error {
return fileSaveDialog.vtbl.setTitle(unsafe.Pointer(fileSaveDialog), title)
}
func (fileSaveDialog *iFileSaveDialog) GetResult() (string, error) {
return fileSaveDialog.vtbl.getResultString(unsafe.Pointer(fileSaveDialog))
}
func (fileSaveDialog *iFileSaveDialog) Release() error {
return fileSaveDialog.vtbl.release(unsafe.Pointer(fileSaveDialog))
}
func (fileSaveDialog *iFileSaveDialog) SetDefaultFolder(defaultFolderPath string) error {
return fileSaveDialog.vtbl.setDefaultFolder(unsafe.Pointer(fileSaveDialog), defaultFolderPath)
}
func (fileSaveDialog *iFileSaveDialog) SetFolder(defaultFolderPath string) error {
return fileSaveDialog.vtbl.setFolder(unsafe.Pointer(fileSaveDialog), defaultFolderPath)
}
func (fileSaveDialog *iFileSaveDialog) SetFileFilters(filter []FileFilter) error {
return fileSaveDialog.vtbl.setFileTypes(unsafe.Pointer(fileSaveDialog), filter)
}
func (fileSaveDialog *iFileSaveDialog) SetRole(role string) error {
return fileSaveDialog.vtbl.setClientGuid(unsafe.Pointer(fileSaveDialog), util.StringToUUID(role))
}
func (fileSaveDialog *iFileSaveDialog) SetDefaultExtension(defaultExtension string) error {
return fileSaveDialog.vtbl.setDefaultExtension(unsafe.Pointer(fileSaveDialog), defaultExtension)
}
func (fileSaveDialog *iFileSaveDialog) SetFileName(initialFileName string) error {
return fileSaveDialog.vtbl.setFileName(unsafe.Pointer(fileSaveDialog), initialFileName)
}
func (fileSaveDialog *iFileSaveDialog) SetSelectedFileFilterIndex(index uint) error {
return fileSaveDialog.vtbl.setSelectedFileFilterIndex(unsafe.Pointer(fileSaveDialog), index)
}
@@ -0,0 +1,53 @@
//go:build windows
// +build windows
package cfd
import (
"github.com/go-ole/go-ole"
"syscall"
"unsafe"
)
var (
procSHCreateItemFromParsingName = syscall.NewLazyDLL("Shell32.dll").NewProc("SHCreateItemFromParsingName")
iidShellItem = ole.NewGUID("43826d1e-e718-42ee-bc55-a1e261c37bfe")
)
type iShellItem struct {
vtbl *iShellItemVtbl
}
type iShellItemVtbl struct {
iUnknownVtbl
BindToHandler uintptr
GetParent uintptr
GetDisplayName uintptr // func (sigdnName SIGDN, ppszName *LPWSTR) HRESULT
GetAttributes uintptr
Compare uintptr
}
func newIShellItem(path string) (*iShellItem, error) {
var shellItem *iShellItem
pathPtr := ole.SysAllocString(path)
ret, _, _ := procSHCreateItemFromParsingName.Call(
uintptr(unsafe.Pointer(pathPtr)),
0,
uintptr(unsafe.Pointer(iidShellItem)),
uintptr(unsafe.Pointer(&shellItem)))
return shellItem, hresultToError(ret)
}
func (vtbl *iShellItemVtbl) getDisplayName(objPtr unsafe.Pointer) (string, error) {
var ptr *uint16
ret, _, _ := syscall.Syscall(vtbl.GetDisplayName,
2,
uintptr(objPtr),
0x80058000, // SIGDN_FILESYSPATH
uintptr(unsafe.Pointer(&ptr)))
if err := hresultToError(ret); err != nil {
return "", err
}
defer ole.CoTaskMemFree(uintptr(unsafe.Pointer(ptr)))
return ole.LpOleStrToString(ptr), nil
}

Some files were not shown because too many files have changed in this diff Show More