Save and replace profiles via system file dialog; buttons after Best; prefs unchanged on import. Co-authored-by: Cursor <cursoragent@cursor.com>
140 lines
3.4 KiB
Go
140 lines
3.4 KiB
Go
//go:build windows
|
|
|
|
package filedialog
|
|
|
|
import (
|
|
"path/filepath"
|
|
"strings"
|
|
"syscall"
|
|
"unsafe"
|
|
|
|
"golang.org/x/sys/windows"
|
|
)
|
|
|
|
var (
|
|
comdlg32 = windows.NewLazySystemDLL("comdlg32.dll")
|
|
procGetOpenFileName = comdlg32.NewProc("GetOpenFileNameW")
|
|
procGetSaveFileName = comdlg32.NewProc("GetSaveFileNameW")
|
|
)
|
|
|
|
const (
|
|
ofnExplorer = 0x00080000
|
|
ofnFileMustExist = 0x00001000
|
|
ofnPathMustExist = 0x00000800
|
|
ofnOverwritePrompt = 0x00000002
|
|
ofnHideReadOnly = 0x00000004
|
|
maxPath = 32768
|
|
)
|
|
|
|
type openFileNameW struct {
|
|
LStructSize uint32
|
|
HwndOwner windows.HWND
|
|
HInstance windows.Handle
|
|
LpstrFilter *uint16
|
|
LpstrCustomFilter *uint16
|
|
NMaxCustFilter uint32
|
|
NFilterIndex uint32
|
|
LpstrFile *uint16
|
|
NMaxFile uint32
|
|
LpstrFileTitle *uint16
|
|
NMaxFileTitle uint32
|
|
LpstrInitialDir *uint16
|
|
LpstrTitle *uint16
|
|
Flags uint32
|
|
NFileOffset uint16
|
|
NFileExtension uint16
|
|
LpstrDefExt *uint16
|
|
LCustData uintptr
|
|
LpfnHook uintptr
|
|
LpTemplateName *uint16
|
|
PvReserved unsafe.Pointer
|
|
DwReserved uint32
|
|
FlagsEx uint32
|
|
}
|
|
|
|
func jsonFilter() *uint16 {
|
|
// Double-null terminated pairs: label\0pattern\0label\0pattern\0\0
|
|
s := "JSON (*.json)\x00*.json\x00All files\x00*.*\x00"
|
|
return &utf16FromString(s)[0]
|
|
}
|
|
|
|
func utf16FromString(s string) []uint16 {
|
|
u, err := syscall.UTF16FromString(s)
|
|
if err != nil {
|
|
return []uint16{0}
|
|
}
|
|
return u
|
|
}
|
|
|
|
func ensureJSONExt(path string) string {
|
|
if strings.EqualFold(filepath.Ext(path), ".json") {
|
|
return path
|
|
}
|
|
return path + ".json"
|
|
}
|
|
|
|
// SaveJSON shows a Save As dialog for a .json file.
|
|
func SaveJSON(title, defaultName string) (string, error) {
|
|
if title == "" {
|
|
title = "Сохранить список серверов"
|
|
}
|
|
if defaultName == "" {
|
|
defaultName = "navis-servers.json"
|
|
}
|
|
buf := make([]uint16, maxPath)
|
|
copy(buf, utf16FromString(defaultName))
|
|
|
|
filter := jsonFilter()
|
|
titlePtr, _ := syscall.UTF16PtrFromString(title)
|
|
defExt, _ := syscall.UTF16PtrFromString("json")
|
|
|
|
ofn := openFileNameW{
|
|
LStructSize: uint32(unsafe.Sizeof(openFileNameW{})),
|
|
LpstrFilter: filter,
|
|
NFilterIndex: 1,
|
|
LpstrFile: &buf[0],
|
|
NMaxFile: uint32(len(buf)),
|
|
LpstrTitle: titlePtr,
|
|
Flags: ofnExplorer | ofnPathMustExist | ofnOverwritePrompt | ofnHideReadOnly,
|
|
LpstrDefExt: defExt,
|
|
}
|
|
r, _, _ := procGetSaveFileName.Call(uintptr(unsafe.Pointer(&ofn)))
|
|
if r == 0 {
|
|
return "", ErrCanceled
|
|
}
|
|
path := windows.UTF16ToString(buf)
|
|
if path == "" {
|
|
return "", ErrCanceled
|
|
}
|
|
return ensureJSONExt(path), nil
|
|
}
|
|
|
|
// OpenJSON shows an Open dialog for a .json file.
|
|
func OpenJSON(title string) (string, error) {
|
|
if title == "" {
|
|
title = "Загрузить список серверов"
|
|
}
|
|
buf := make([]uint16, maxPath)
|
|
filter := jsonFilter()
|
|
titlePtr, _ := syscall.UTF16PtrFromString(title)
|
|
|
|
ofn := openFileNameW{
|
|
LStructSize: uint32(unsafe.Sizeof(openFileNameW{})),
|
|
LpstrFilter: filter,
|
|
NFilterIndex: 1,
|
|
LpstrFile: &buf[0],
|
|
NMaxFile: uint32(len(buf)),
|
|
LpstrTitle: titlePtr,
|
|
Flags: ofnExplorer | ofnFileMustExist | ofnPathMustExist | ofnHideReadOnly,
|
|
}
|
|
r, _, _ := procGetOpenFileName.Call(uintptr(unsafe.Pointer(&ofn)))
|
|
if r == 0 {
|
|
return "", ErrCanceled
|
|
}
|
|
path := windows.UTF16ToString(buf)
|
|
if path == "" {
|
|
return "", ErrCanceled
|
|
}
|
|
return path, nil
|
|
}
|