github.com/AlpineAIO/wails/v2@v2.0.0-beta.32.0.20240505041856-1047a8fa5fef/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/AlpineAIO/wails/v2/internal/frontend"
    12  	"github.com/AlpineAIO/wails/v2/internal/frontend/desktop/windows/winc/w32"
    13  	"github.com/AlpineAIO/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  	if len(options.Filters) > 0 {
   125  		config.DefaultExtension = strings.TrimPrefix(strings.Split(options.Filters[0].Pattern, ";")[0], "*")
   126  	}
   127  
   128  	result, err := f.showCfdDialog(
   129  		func() (cfd.Dialog, error) {
   130  			return cfd.NewSaveFileDialog(config)
   131  		}, false)
   132  
   133  	if err != nil && err != cfd.ErrCancelled {
   134  		return "", err
   135  	}
   136  	return result.(string), nil
   137  }
   138  
   139  func (f *Frontend) showCfdDialog(newDlg func() (cfd.Dialog, error), isMultiSelect bool) (any, error) {
   140  	return invokeSync(f.mainWindow, func() (any, error) {
   141  		dlg, err := newDlg()
   142  		if err != nil {
   143  			return nil, err
   144  		}
   145  		defer func() {
   146  			err := dlg.Release()
   147  			if err != nil {
   148  				println("ERROR: Unable to release dialog:", err.Error())
   149  			}
   150  		}()
   151  
   152  		dlg.SetParentWindowHandle(f.getHandleForDialog())
   153  		if multi, _ := dlg.(cfd.OpenMultipleFilesDialog); multi != nil && isMultiSelect {
   154  			return multi.ShowAndGetResults()
   155  		}
   156  		return dlg.ShowAndGetResult()
   157  	})
   158  }
   159  
   160  func calculateMessageDialogFlags(options frontend.MessageDialogOptions) uint32 {
   161  	var flags uint32
   162  
   163  	switch options.Type {
   164  	case frontend.InfoDialog:
   165  		flags = windows.MB_OK | windows.MB_ICONINFORMATION
   166  	case frontend.ErrorDialog:
   167  		flags = windows.MB_ICONERROR | windows.MB_OK
   168  	case frontend.QuestionDialog:
   169  		flags = windows.MB_YESNO
   170  		if strings.TrimSpace(strings.ToLower(options.DefaultButton)) == "no" {
   171  			flags |= windows.MB_DEFBUTTON2
   172  		}
   173  	case frontend.WarningDialog:
   174  		flags = windows.MB_OK | windows.MB_ICONWARNING
   175  	}
   176  
   177  	return flags
   178  }
   179  
   180  // MessageDialog show a message dialog to the user
   181  func (f *Frontend) MessageDialog(options frontend.MessageDialogOptions) (string, error) {
   182  
   183  	title, err := syscall.UTF16PtrFromString(options.Title)
   184  	if err != nil {
   185  		return "", err
   186  	}
   187  	message, err := syscall.UTF16PtrFromString(options.Message)
   188  	if err != nil {
   189  		return "", err
   190  	}
   191  
   192  	flags := calculateMessageDialogFlags(options)
   193  
   194  	button, _ := windows.MessageBox(windows.HWND(f.getHandleForDialog()), message, title, flags|windows.MB_SYSTEMMODAL)
   195  	// This maps MessageBox return values to strings
   196  	responses := []string{"", "Ok", "Cancel", "Abort", "Retry", "Ignore", "Yes", "No", "", "", "Try Again", "Continue"}
   197  	result := "Error"
   198  	if int(button) < len(responses) {
   199  		result = responses[button]
   200  	}
   201  	return result, nil
   202  }
   203  
   204  func convertFilters(filters []frontend.FileFilter) []cfd.FileFilter {
   205  	var result []cfd.FileFilter
   206  	for _, filter := range filters {
   207  		result = append(result, cfd.FileFilter(filter))
   208  	}
   209  	return result
   210  }