github.com/secoba/wails/v2@v2.6.4/internal/frontend/desktop/windows/dialog.go (about)

     1  //go:build windows
     2  // +build windows
     3  
     4  package windows
     5  
     6  import (
     7  	"path/filepath"
     8  	"strings"
     9  	"syscall"
    10  
    11  	"github.com/secoba/wails/v2/internal/frontend"
    12  	"github.com/secoba/wails/v2/internal/frontend/desktop/windows/winc/w32"
    13  	"github.com/secoba/wails/v2/internal/go-common-file-dialog/cfd"
    14  	"golang.org/x/sys/windows"
    15  )
    16  
    17  func (f *Frontend) getHandleForDialog() w32.HWND {
    18  	if f.mainWindow.IsVisible() {
    19  		return f.mainWindow.Handle()
    20  	}
    21  	return 0
    22  }
    23  
    24  func getDefaultFolder(folder string) (string, error) {
    25  	if folder == "" {
    26  		return "", nil
    27  	}
    28  	return filepath.Abs(folder)
    29  }
    30  
    31  // OpenDirectoryDialog prompts the user to select a directory
    32  func (f *Frontend) OpenDirectoryDialog(options frontend.OpenDialogOptions) (string, error) {
    33  
    34  	defaultFolder, err := getDefaultFolder(options.DefaultDirectory)
    35  	if err != nil {
    36  		return "", err
    37  	}
    38  
    39  	config := cfd.DialogConfig{
    40  		Title:  options.Title,
    41  		Role:   "PickFolder",
    42  		Folder: defaultFolder,
    43  	}
    44  
    45  	result, err := f.showCfdDialog(
    46  		func() (cfd.Dialog, error) {
    47  			return cfd.NewSelectFolderDialog(config)
    48  		}, false)
    49  
    50  	if err != nil && err != cfd.ErrCancelled {
    51  		return "", err
    52  	}
    53  	return result.(string), nil
    54  }
    55  
    56  // OpenFileDialog prompts the user to select a file
    57  func (f *Frontend) OpenFileDialog(options frontend.OpenDialogOptions) (string, error) {
    58  	defaultFolder, err := getDefaultFolder(options.DefaultDirectory)
    59  	if err != nil {
    60  		return "", err
    61  	}
    62  
    63  	config := cfd.DialogConfig{
    64  		Folder:      defaultFolder,
    65  		FileFilters: convertFilters(options.Filters),
    66  		FileName:    options.DefaultFilename,
    67  		Title:       options.Title,
    68  	}
    69  
    70  	result, err := f.showCfdDialog(
    71  		func() (cfd.Dialog, error) {
    72  			return cfd.NewOpenFileDialog(config)
    73  		}, false)
    74  
    75  	if err != nil && err != cfd.ErrCancelled {
    76  		return "", err
    77  	}
    78  	return result.(string), nil
    79  }
    80  
    81  // OpenMultipleFilesDialog prompts the user to select a file
    82  func (f *Frontend) OpenMultipleFilesDialog(options frontend.OpenDialogOptions) ([]string, error) {
    83  
    84  	defaultFolder, err := getDefaultFolder(options.DefaultDirectory)
    85  	if err != nil {
    86  		return nil, err
    87  	}
    88  
    89  	config := cfd.DialogConfig{
    90  		Title:       options.Title,
    91  		Role:        "OpenMultipleFiles",
    92  		FileFilters: convertFilters(options.Filters),
    93  		FileName:    options.DefaultFilename,
    94  		Folder:      defaultFolder,
    95  	}
    96  
    97  	result, err := f.showCfdDialog(
    98  		func() (cfd.Dialog, error) {
    99  			return cfd.NewOpenMultipleFilesDialog(config)
   100  		}, true)
   101  
   102  	if err != nil && err != cfd.ErrCancelled {
   103  		return nil, err
   104  	}
   105  	return result.([]string), nil
   106  }
   107  
   108  // SaveFileDialog prompts the user to select a file
   109  func (f *Frontend) SaveFileDialog(options frontend.SaveDialogOptions) (string, error) {
   110  
   111  	defaultFolder, err := getDefaultFolder(options.DefaultDirectory)
   112  	if err != nil {
   113  		return "", err
   114  	}
   115  
   116  	config := cfd.DialogConfig{
   117  		Title:       options.Title,
   118  		Role:        "SaveFile",
   119  		FileFilters: convertFilters(options.Filters),
   120  		FileName:    options.DefaultFilename,
   121  		Folder:      defaultFolder,
   122  	}
   123  
   124  	result, err := f.showCfdDialog(
   125  		func() (cfd.Dialog, error) {
   126  			return cfd.NewSaveFileDialog(config)
   127  		}, false)
   128  
   129  	if err != nil && err != cfd.ErrCancelled {
   130  		return "", err
   131  	}
   132  	return result.(string), nil
   133  }
   134  
   135  func (f *Frontend) showCfdDialog(newDlg func() (cfd.Dialog, error), isMultiSelect bool) (any, error) {
   136  	return invokeSync(f.mainWindow, func() (any, error) {
   137  		dlg, err := newDlg()
   138  		if err != nil {
   139  			return nil, err
   140  		}
   141  		defer func() {
   142  			err := dlg.Release()
   143  			if err != nil {
   144  				println("ERROR: Unable to release dialog:", err.Error())
   145  			}
   146  		}()
   147  
   148  		dlg.SetParentWindowHandle(f.getHandleForDialog())
   149  		if multi, _ := dlg.(cfd.OpenMultipleFilesDialog); multi != nil && isMultiSelect {
   150  			return multi.ShowAndGetResults()
   151  		}
   152  		return dlg.ShowAndGetResult()
   153  	})
   154  }
   155  
   156  func calculateMessageDialogFlags(options frontend.MessageDialogOptions) uint32 {
   157  	var flags uint32
   158  
   159  	switch options.Type {
   160  	case frontend.InfoDialog:
   161  		flags = windows.MB_OK | windows.MB_ICONINFORMATION
   162  	case frontend.ErrorDialog:
   163  		flags = windows.MB_ICONERROR | windows.MB_OK
   164  	case frontend.QuestionDialog:
   165  		flags = windows.MB_YESNO
   166  		if strings.TrimSpace(strings.ToLower(options.DefaultButton)) == "no" {
   167  			flags |= windows.MB_DEFBUTTON2
   168  		}
   169  	case frontend.WarningDialog:
   170  		flags = windows.MB_OK | windows.MB_ICONWARNING
   171  	}
   172  
   173  	return flags
   174  }
   175  
   176  // MessageDialog show a message dialog to the user
   177  func (f *Frontend) MessageDialog(options frontend.MessageDialogOptions) (string, error) {
   178  
   179  	title, err := syscall.UTF16PtrFromString(options.Title)
   180  	if err != nil {
   181  		return "", err
   182  	}
   183  	message, err := syscall.UTF16PtrFromString(options.Message)
   184  	if err != nil {
   185  		return "", err
   186  	}
   187  
   188  	flags := calculateMessageDialogFlags(options)
   189  
   190  	button, _ := windows.MessageBox(windows.HWND(f.getHandleForDialog()), message, title, flags|windows.MB_SYSTEMMODAL)
   191  	// This maps MessageBox return values to strings
   192  	responses := []string{"", "Ok", "Cancel", "Abort", "Retry", "Ignore", "Yes", "No", "", "", "Try Again", "Continue"}
   193  	result := "Error"
   194  	if int(button) < len(responses) {
   195  		result = responses[button]
   196  	}
   197  	return result, nil
   198  }
   199  
   200  func convertFilters(filters []frontend.FileFilter) []cfd.FileFilter {
   201  	var result []cfd.FileFilter
   202  	for _, filter := range filters {
   203  		result = append(result, cfd.FileFilter(filter))
   204  	}
   205  	return result
   206  }