dlg_darwin.go 4.48 KB
Newer Older
1
2
package cocoa

3
// #cgo darwin LDFLAGS: -framework Cocoa -framework UniformTypeIdentifiers
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
// #include <stdlib.h>
// #include <sys/syslimits.h>
// #include "dlg.h"
import "C"

import (
	"bytes"
	"errors"
	"unsafe"
)

type AlertParams struct {
	p C.AlertDlgParams
}

func mkAlertParams(msg, title string, style C.AlertStyle) *AlertParams {
	a := AlertParams{C.AlertDlgParams{msg: C.CString(msg), style: style}}
	if title != "" {
		a.p.title = C.CString(title)
	}
	return &a
}

func (a *AlertParams) run() C.DlgResult {
	return C.alertDlg(&a.p)
}

func (a *AlertParams) free() {
	C.free(unsafe.Pointer(a.p.msg))
	if a.p.title != nil {
		C.free(unsafe.Pointer(a.p.title))
	}
}

func nsStr(s string) unsafe.Pointer {
	return C.NSStr(unsafe.Pointer(&[]byte(s)[0]), C.int(len(s)))
}

func YesNoDlg(msg, title string) bool {
	a := mkAlertParams(msg, title, C.MSG_YESNO)
	defer a.free()
	return a.run() == C.DLG_OK
}

func InfoDlg(msg, title string) {
	a := mkAlertParams(msg, title, C.MSG_INFO)
	defer a.free()
	a.run()
}

func ErrorDlg(msg, title string) {
	a := mkAlertParams(msg, title, C.MSG_ERROR)
	defer a.free()
	a.run()
}

const (
	BUFSIZE             = C.PATH_MAX
	MULTI_FILE_BUF_SIZE = 32768
)

// MultiFileDlg opens a file dialog that allows multiple file selection
func MultiFileDlg(title string, exts []string, relaxExt bool, startDir string, showHidden bool) ([]string, error) {
	return fileDlgWithOptions(C.LOADDLG, title, exts, relaxExt, startDir, "", showHidden, true)
}

// FileDlg opens a file dialog for single file selection (kept for compatibility)
func FileDlg(save bool, title string, exts []string, relaxExt bool, startDir string, filename string, showHidden bool) (string, error) {
	mode := C.LOADDLG
	if save {
		mode = C.SAVEDLG
	}
	files, err := fileDlgWithOptions(mode, title, exts, relaxExt, startDir, filename, showHidden, false)
	if err != nil {
		return "", err
	}
	if len(files) == 0 {
		return "", nil
	}
	return files[0], nil
}

func DirDlg(title string, startDir string, showHidden bool) (string, error) {
	files, err := fileDlgWithOptions(C.DIRDLG, title, nil, false, startDir, "", showHidden, false)
	if err != nil {
		return "", err
	}
	if len(files) == 0 {
		return "", nil
	}
	return files[0], nil
}

// fileDlgWithOptions is the unified file dialog function that handles both single and multiple selection
func fileDlgWithOptions(mode int, title string, exts []string, relaxExt bool, startDir, filename string, showHidden, allowMultiple bool) ([]string, error) {
	// Use larger buffer for multiple files, smaller for single
	bufSize := BUFSIZE
	if allowMultiple {
		bufSize = MULTI_FILE_BUF_SIZE
	}

	p := C.FileDlgParams{
		mode: C.int(mode),
		nbuf: C.int(bufSize),
	}

	if allowMultiple {
		p.allowMultiple = C.int(1) // Enable multiple selection //nolint:structcheck
	}
	if showHidden {
		p.showHidden = 1
	}

	p.buf = (*C.char)(C.malloc(C.size_t(bufSize)))
	defer C.free(unsafe.Pointer(p.buf))
	buf := (*(*[MULTI_FILE_BUF_SIZE]byte)(unsafe.Pointer(p.buf)))[:bufSize]

	if title != "" {
		p.title = C.CString(title)
		defer C.free(unsafe.Pointer(p.title))
	}
	if startDir != "" {
		p.startDir = C.CString(startDir)
		defer C.free(unsafe.Pointer(p.startDir))
	}
	if filename != "" {
		p.filename = C.CString(filename)
		defer C.free(unsafe.Pointer(p.filename))
	}

	if len(exts) > 0 {
		if len(exts) > 999 {
			panic("more than 999 extensions not supported")
		}
		ptrSize := int(unsafe.Sizeof(&title))
		p.exts = (*unsafe.Pointer)(C.malloc(C.size_t(ptrSize * len(exts))))
		defer C.free(unsafe.Pointer(p.exts))
		cext := (*(*[999]unsafe.Pointer)(unsafe.Pointer(p.exts)))[:]
		for i, ext := range exts {
			cext[i] = nsStr(ext)
			defer C.NSRelease(cext[i])
		}
		p.numext = C.int(len(exts))
		if relaxExt {
			p.relaxext = 1
		}
	}

	// Execute dialog and parse results
	switch C.fileDlg(&p) {
	case C.DLG_OK:
		if allowMultiple {
			// Parse multiple null-terminated strings from buffer
			var files []string
			start := 0
			for i := range len(buf) - 1 {
				if buf[i] == 0 {
					if i > start {
						files = append(files, string(buf[start:i]))
					}
					start = i + 1
					// Check for double null (end of list)
					if i+1 < len(buf) && buf[i+1] == 0 {
						break
					}
				}
			}
			return files, nil
		} else {
			// Single file - return as array for consistency
			filename := string(buf[:bytes.Index(buf, []byte{0})])
			return []string{filename}, nil
		}
	case C.DLG_CANCEL:
		return nil, nil
	case C.DLG_URLFAIL:
		return nil, errors.New("failed to get file-system representation for selected URL")
	}
	panic("unhandled case")
}