Skip to content

Commit

Permalink
New windows file dialogs (#23)
Browse files Browse the repository at this point in the history
  • Loading branch information
richardwilkes authored Dec 18, 2022
1 parent c41dadd commit 9f7055d
Show file tree
Hide file tree
Showing 12 changed files with 732 additions and 169 deletions.
91 changes: 0 additions & 91 deletions internal/w32/comdlg32_windows.go

This file was deleted.

133 changes: 133 additions & 0 deletions internal/w32/file_dialog_windows.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
// Copyright ©2021-2022 by Richard A. Wilkes. All rights reserved.
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, version 2.0. If a copy of the MPL was not distributed with
// this file, You can obtain one at http://mozilla.org/MPL/2.0/.
//
// This Source Code Form is "Incompatible With Secondary Licenses", as
// defined by the Mozilla Public License, version 2.0.

package w32

import (
"strings"
"syscall"
"unsafe"
)

const (
FOSOverwritePrompt = 0x00000002
FOSStrictFileTypes = 0x00000004
FOSNoChangeDir = 0x00000008
FOSPickFolders = 0x00000020
FOSForceFileSystem = 0x00000040
FOSAllNonStorageItems = 0x00000080
FOSNoValidate = 0x00000100
FOSAllowMultiSelect = 0x00000200
FOSPathMustExist = 0x00000800
FOSFileMustExist = 0x00001000
FOSCreatePrompt = 0x00002000
FOSShareAware = 0x00004000
FOSNoReadOnlyReturn = 0x00008000
FOSNoTestFileCreate = 0x00010000
FOSHideMRUPlaces = 0x00020000
FOSHidePinnedPlaces = 0x00040000
FOSNoDereferenceLinks = 0x00100000
FOSOKBUttonNeedsInteraction = 0x00200000
FOSDontAddToRecent = 0x02000000
FOSForceShowHidden = 0x10000000
FOSDefaultNoMiniMode = 0x20000000
FOSForcePreviewPaneOn = 0x40000000
FOSSupportsStreamableItems = 0x80000000
)

type FileFilter struct {
Name string
Pattern string
}

type filterSpec struct {
name *int16
pattern *int16
}

type FileDialog struct {
ModalWindow
}

type vmtFileDialog struct {
vmtModalWindow
SetFileTypes uintptr
SetFileTypeIndex uintptr
GetFileTypeIndex uintptr
Advise uintptr
Unadvise uintptr
SetOptions uintptr
GetOptions uintptr
SetDefaultFolder uintptr
SetFolder uintptr
GetFolder uintptr
GetCurrentSelection uintptr
SetFileName uintptr
GetFileName uintptr
SetTitle uintptr
SetOkButtonLabel uintptr
SetFileNameLabel uintptr
GetResult uintptr
AddPlace uintptr
SetDefaultExtension uintptr
Close uintptr
SetClientGuid uintptr
ClearClientData uintptr
SetFilter uintptr
}

func (obj *FileDialog) vmt() *vmtFileDialog {
return (*vmtFileDialog)(obj.UnsafeVirtualMethodTable)
}

func (obj *FileDialog) SetFolder(path string) {
if item := NewShellItem(path); item != nil {
defer item.Release()
syscall.SyscallN(obj.vmt().SetFolder, uintptr(unsafe.Pointer(obj)), uintptr(unsafe.Pointer(item)))
}
}

func (obj *FileDialog) SetOptions(options int) {
syscall.SyscallN(obj.vmt().SetOptions, uintptr(unsafe.Pointer(obj)), uintptr(options))
}

func (obj *FileDialog) SetFileTypes(filters []FileFilter) {
if len(filters) == 0 {
return
}
specs := make([]filterSpec, len(filters))
for i, one := range filters {
specs[i] = filterSpec{
name: SysAllocString(one.Name),
pattern: SysAllocString(one.Pattern),
}
}
syscall.SyscallN(obj.vmt().SetFileTypes, uintptr(unsafe.Pointer(obj)), uintptr(len(specs)),
uintptr(unsafe.Pointer(&specs[0])))
}

func (obj *FileDialog) SetDefaultExtension(ext string) {
syscall.SyscallN(obj.vmt().SetDefaultExtension, uintptr(unsafe.Pointer(obj)),
uintptr(unsafe.Pointer(SysAllocString(strings.TrimPrefix(ext, ".")))))
}

func (obj *FileDialog) SetFileName(fileName string) {
syscall.SyscallN(obj.vmt().SetFileName, uintptr(unsafe.Pointer(obj)),
uintptr(unsafe.Pointer(SysAllocString(fileName))))
}

func (obj *FileDialog) GetResult() string {
var item *ShellItem
r1, _, _ := syscall.SyscallN(obj.vmt().GetResult, uintptr(unsafe.Pointer(obj)), uintptr(unsafe.Pointer(&item)))
if r1 != 0 || item == nil {
return ""
}
defer item.Release()
return item.DisplayName()
}
155 changes: 155 additions & 0 deletions internal/w32/guid_windows.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
// Copyright ©2021-2022 by Richard A. Wilkes. All rights reserved.
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, version 2.0. If a copy of the MPL was not distributed with
// this file, You can obtain one at http://mozilla.org/MPL/2.0/.
//
// This Source Code Form is "Incompatible With Secondary Licenses", as
// defined by the Mozilla Public License, version 2.0.

package w32

const hexTable = "0123456789ABCDEF"

var NullGUID GUID

// GUID holds a Windows universal ID.
type GUID struct {
Data1 uint32
Data2 uint16
Data3 uint16
Data4 [8]byte
}

// NewGUID creates a GUID from a string. The string may be in one of these formats:
//
// {XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX}
// XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX
// XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
func NewGUID(guid string) GUID {
d := []byte(guid)
var d1, d2, d3, d4a, d4b []byte
switch len(d) {
case 38:
if d[0] != '{' || d[37] != '}' {
return NullGUID
}
d = d[1:37]
fallthrough
case 36:
if d[8] != '-' || d[13] != '-' || d[18] != '-' || d[23] != '-' {
return NullGUID
}
d1 = d[0:8]
d2 = d[9:13]
d3 = d[14:18]
d4a = d[19:23]
d4b = d[24:36]
case 32:
d1 = d[0:8]
d2 = d[8:12]
d3 = d[12:16]
d4a = d[16:20]
d4b = d[20:32]
default:
return NullGUID
}
var g GUID
var ok1, ok2, ok3, ok4 bool
g.Data1, ok1 = decodeHexUint32(d1)
g.Data2, ok2 = decodeHexUint16(d2)
g.Data3, ok3 = decodeHexUint16(d3)
g.Data4, ok4 = decodeHexByte64(d4a, d4b)
if ok1 && ok2 && ok3 && ok4 {
return g
}
return NullGUID
}

// String returns the string representation of the GUID in its canonical format of
// {XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX}.
func (guid GUID) String() string {
var c [38]byte
c[0] = '{'
putUint32Hex(c[1:9], guid.Data1)
c[9] = '-'
putUint16Hex(c[10:14], guid.Data2)
c[14] = '-'
putUint16Hex(c[15:19], guid.Data3)
c[19] = '-'
putByteHex(c[20:24], guid.Data4[0:2])
c[24] = '-'
putByteHex(c[25:37], guid.Data4[2:8])
c[37] = '}'
return string(c[:])
}

func decodeHexUint32(src []byte) (uint32, bool) {
b1, ok1 := decodeHexByte(src[0], src[1])
b2, ok2 := decodeHexByte(src[2], src[3])
b3, ok3 := decodeHexByte(src[4], src[5])
b4, ok4 := decodeHexByte(src[6], src[7])
return (uint32(b1) << 24) | (uint32(b2) << 16) | (uint32(b3) << 8) | uint32(b4), ok1 && ok2 && ok3 && ok4
}

func decodeHexUint16(src []byte) (uint16, bool) {
b1, ok1 := decodeHexByte(src[0], src[1])
b2, ok2 := decodeHexByte(src[2], src[3])
return (uint16(b1) << 8) | uint16(b2), ok1 && ok2
}

func decodeHexByte64(s1 []byte, s2 []byte) (value [8]byte, ok bool) {
var ok1, ok2, ok3, ok4, ok5, ok6, ok7, ok8 bool
value[0], ok1 = decodeHexByte(s1[0], s1[1])
value[1], ok2 = decodeHexByte(s1[2], s1[3])
value[2], ok3 = decodeHexByte(s2[0], s2[1])
value[3], ok4 = decodeHexByte(s2[2], s2[3])
value[4], ok5 = decodeHexByte(s2[4], s2[5])
value[5], ok6 = decodeHexByte(s2[6], s2[7])
value[6], ok7 = decodeHexByte(s2[8], s2[9])
value[7], ok8 = decodeHexByte(s2[10], s2[11])
return value, ok1 && ok2 && ok3 && ok4 && ok5 && ok6 && ok7 && ok8
}

func decodeHexByte(c1, c2 byte) (byte, bool) {
n1, ok1 := decodeHexChar(c1)
n2, ok2 := decodeHexChar(c2)
return (n1 << 4) | n2, ok1 && ok2
}

func decodeHexChar(c byte) (byte, bool) {
switch {
case '0' <= c && c <= '9':
return c - '0', true
case 'a' <= c && c <= 'f':
return c - 'a' + 10, true
case 'A' <= c && c <= 'F':
return c - 'A' + 10, true
}
return 0, false
}

func putUint32Hex(b []byte, v uint32) {
b[0] = hexTable[byte(v>>24)>>4]
b[1] = hexTable[byte(v>>24)&0x0f]
b[2] = hexTable[byte(v>>16)>>4]
b[3] = hexTable[byte(v>>16)&0x0f]
b[4] = hexTable[byte(v>>8)>>4]
b[5] = hexTable[byte(v>>8)&0x0f]
b[6] = hexTable[byte(v)>>4]
b[7] = hexTable[byte(v)&0x0f]
}

func putUint16Hex(b []byte, v uint16) {
b[0] = hexTable[byte(v>>8)>>4]
b[1] = hexTable[byte(v>>8)&0x0f]
b[2] = hexTable[byte(v)>>4]
b[3] = hexTable[byte(v)&0x0f]
}

func putByteHex(dst, src []byte) {
for i := 0; i < len(src); i++ {
dst[i*2] = hexTable[src[i]>>4]
dst[i*2+1] = hexTable[src[i]&0x0f]
}
}
Loading

0 comments on commit 9f7055d

Please sign in to comment.