Add files via upload
This commit is contained in:
parent
59410bd407
commit
23070fd3ba
|
@ -0,0 +1,39 @@
|
||||||
|
#include <objc/NSObjCRuntime.h>
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
MSG_YESNO,
|
||||||
|
MSG_ERROR,
|
||||||
|
MSG_INFO,
|
||||||
|
} AlertStyle;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
char* msg;
|
||||||
|
char* title;
|
||||||
|
AlertStyle style;
|
||||||
|
} AlertDlgParams;
|
||||||
|
|
||||||
|
#define LOADDLG 0
|
||||||
|
#define SAVEDLG 1
|
||||||
|
#define DIRDLG 2 // browse for directory
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
int mode; /* which dialog style to invoke (see earlier defines) */
|
||||||
|
char* buf; /* buffer to store selected file */
|
||||||
|
int nbuf; /* number of bytes allocated at buf */
|
||||||
|
char* title; /* title for dialog box (can be nil) */
|
||||||
|
void** exts; /* list of valid extensions (elements actual type is NSString*) */
|
||||||
|
int numext; /* number of items in exts */
|
||||||
|
int relaxext; /* allow other extensions? */
|
||||||
|
} FileDlgParams;
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
DLG_OK,
|
||||||
|
DLG_CANCEL,
|
||||||
|
DLG_URLFAIL,
|
||||||
|
} DlgResult;
|
||||||
|
|
||||||
|
DlgResult alertDlg(AlertDlgParams*);
|
||||||
|
DlgResult fileDlg(FileDlgParams*);
|
||||||
|
|
||||||
|
void* NSStr(void* buf, int len);
|
||||||
|
void NSRelease(void* obj);
|
|
@ -0,0 +1,130 @@
|
||||||
|
#import <Cocoa/Cocoa.h>
|
||||||
|
#include "dlg.h"
|
||||||
|
|
||||||
|
void* NSStr(void* buf, int len) {
|
||||||
|
return (void*)[[NSString alloc] initWithBytes:buf length:len encoding:NSUTF8StringEncoding];
|
||||||
|
}
|
||||||
|
|
||||||
|
void NSRelease(void* obj) {
|
||||||
|
[(NSObject*)obj release];
|
||||||
|
}
|
||||||
|
|
||||||
|
@interface AlertDlg : NSObject {
|
||||||
|
AlertDlgParams* params;
|
||||||
|
DlgResult result;
|
||||||
|
}
|
||||||
|
+ (AlertDlg*)init:(AlertDlgParams*)params;
|
||||||
|
- (DlgResult)run;
|
||||||
|
@end
|
||||||
|
|
||||||
|
DlgResult alertDlg(AlertDlgParams* params) {
|
||||||
|
return [[AlertDlg init:params] run];
|
||||||
|
}
|
||||||
|
|
||||||
|
@implementation AlertDlg
|
||||||
|
+ (AlertDlg*)init:(AlertDlgParams*)params {
|
||||||
|
AlertDlg* d = [AlertDlg alloc];
|
||||||
|
d->params = params;
|
||||||
|
return d;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (DlgResult)run {
|
||||||
|
if(![NSThread isMainThread]) {
|
||||||
|
[self performSelectorOnMainThread:@selector(run) withObject:nil waitUntilDone:YES];
|
||||||
|
return self->result;
|
||||||
|
}
|
||||||
|
NSAlert* alert = [[NSAlert alloc] init];
|
||||||
|
if(self->params->title != nil) {
|
||||||
|
[[alert window] setTitle:[[NSString alloc] initWithUTF8String:self->params->title]];
|
||||||
|
}
|
||||||
|
[alert setMessageText:[[NSString alloc] initWithUTF8String:self->params->msg]];
|
||||||
|
switch (self->params->style) {
|
||||||
|
case MSG_YESNO:
|
||||||
|
[alert addButtonWithTitle:@"Yes"];
|
||||||
|
[alert addButtonWithTitle:@"No"];
|
||||||
|
break;
|
||||||
|
case MSG_ERROR:
|
||||||
|
[alert setIcon:[NSImage imageNamed:NSImageNameCaution]];
|
||||||
|
[alert addButtonWithTitle:@"OK"];
|
||||||
|
break;
|
||||||
|
case MSG_INFO:
|
||||||
|
[alert setIcon:[NSImage imageNamed:NSImageNameInfo]];
|
||||||
|
[alert addButtonWithTitle:@"OK"];
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
self->result = [alert runModal] == NSAlertFirstButtonReturn ? DLG_OK : DLG_CANCEL;
|
||||||
|
return self->result;
|
||||||
|
}
|
||||||
|
@end
|
||||||
|
|
||||||
|
@interface FileDlg : NSObject {
|
||||||
|
FileDlgParams* params;
|
||||||
|
DlgResult result;
|
||||||
|
}
|
||||||
|
+ (FileDlg*)init:(FileDlgParams*)params;
|
||||||
|
- (DlgResult)run;
|
||||||
|
@end
|
||||||
|
|
||||||
|
DlgResult fileDlg(FileDlgParams* params) {
|
||||||
|
return [[FileDlg init:params] run];
|
||||||
|
}
|
||||||
|
|
||||||
|
@implementation FileDlg
|
||||||
|
+ (FileDlg*)init:(FileDlgParams*)params {
|
||||||
|
FileDlg* d = [FileDlg alloc];
|
||||||
|
d->params = params;
|
||||||
|
return d;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (DlgResult)run {
|
||||||
|
if(![NSThread isMainThread]) {
|
||||||
|
[self performSelectorOnMainThread:@selector(run) withObject:nil waitUntilDone:YES];
|
||||||
|
} else if(self->params->mode == SAVEDLG) {
|
||||||
|
self->result = [self save];
|
||||||
|
} else {
|
||||||
|
self->result = [self load];
|
||||||
|
}
|
||||||
|
return self->result;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (NSInteger)runPanel:(NSSavePanel*)panel {
|
||||||
|
[panel setFloatingPanel:YES];
|
||||||
|
if(self->params->title != nil) {
|
||||||
|
[panel setTitle:[[NSString alloc] initWithUTF8String:self->params->title]];
|
||||||
|
}
|
||||||
|
if(self->params->numext > 0) {
|
||||||
|
[panel setAllowedFileTypes:[NSArray arrayWithObjects:(NSString**)self->params->exts count:self->params->numext]];
|
||||||
|
}
|
||||||
|
if(self->params->relaxext) {
|
||||||
|
[panel setAllowsOtherFileTypes:YES];
|
||||||
|
}
|
||||||
|
return [panel runModal];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (DlgResult)save {
|
||||||
|
NSSavePanel* panel = [NSSavePanel savePanel];
|
||||||
|
if(![self runPanel:panel]) {
|
||||||
|
return DLG_CANCEL;
|
||||||
|
} else if(![[panel URL] getFileSystemRepresentation:self->params->buf maxLength:self->params->nbuf]) {
|
||||||
|
return DLG_URLFAIL;
|
||||||
|
}
|
||||||
|
return DLG_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (DlgResult)load {
|
||||||
|
NSOpenPanel* panel = [NSOpenPanel openPanel];
|
||||||
|
if(self->params->mode == DIRDLG) {
|
||||||
|
[panel setCanChooseDirectories:YES];
|
||||||
|
[panel setCanChooseFiles:NO];
|
||||||
|
}
|
||||||
|
if(![self runPanel:panel]) {
|
||||||
|
return DLG_CANCEL;
|
||||||
|
}
|
||||||
|
NSURL* url = [[panel URLs] objectAtIndex:0];
|
||||||
|
if(![url getFileSystemRepresentation:self->params->buf maxLength:self->params->nbuf]) {
|
||||||
|
return DLG_URLFAIL;
|
||||||
|
}
|
||||||
|
return DLG_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
@end
|
|
@ -0,0 +1,114 @@
|
||||||
|
package cocoa
|
||||||
|
|
||||||
|
// #cgo darwin LDFLAGS: -framework Cocoa
|
||||||
|
// #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
|
||||||
|
|
||||||
|
func FileDlg(save bool, title string, exts []string, relaxExt bool) (string, error) {
|
||||||
|
mode := C.LOADDLG
|
||||||
|
if save {
|
||||||
|
mode = C.SAVEDLG
|
||||||
|
}
|
||||||
|
return fileDlg(mode, title, exts, relaxExt)
|
||||||
|
}
|
||||||
|
|
||||||
|
func DirDlg(title string) (string, error) {
|
||||||
|
return fileDlg(C.DIRDLG, title, nil, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
func fileDlg(mode int, title string, exts []string, relaxExt bool) (string, error) {
|
||||||
|
p := C.FileDlgParams{
|
||||||
|
mode: C.int(mode),
|
||||||
|
nbuf: BUFSIZE,
|
||||||
|
}
|
||||||
|
p.buf = (*C.char)(C.malloc(BUFSIZE))
|
||||||
|
defer C.free(unsafe.Pointer(p.buf))
|
||||||
|
buf := (*(*[BUFSIZE]byte)(unsafe.Pointer(p.buf)))[:]
|
||||||
|
if title != "" {
|
||||||
|
p.title = C.CString(title)
|
||||||
|
defer C.free(unsafe.Pointer(p.title))
|
||||||
|
}
|
||||||
|
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 {
|
||||||
|
i := i
|
||||||
|
cext[i] = nsStr(ext)
|
||||||
|
defer C.NSRelease(cext[i])
|
||||||
|
}
|
||||||
|
p.numext = C.int(len(exts))
|
||||||
|
if relaxExt {
|
||||||
|
p.relaxext = 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
switch C.fileDlg(&p) {
|
||||||
|
case C.DLG_OK:
|
||||||
|
// casting to string copies the [about-to-be-freed] bytes
|
||||||
|
return string(buf[:bytes.Index(buf, []byte{0})]), nil
|
||||||
|
case C.DLG_CANCEL:
|
||||||
|
return "", nil
|
||||||
|
case C.DLG_URLFAIL:
|
||||||
|
return "", errors.New("failed to get file-system representation for selected URL")
|
||||||
|
}
|
||||||
|
panic("unhandled case")
|
||||||
|
}
|
|
@ -0,0 +1,146 @@
|
||||||
|
// Package dialog provides a simple cross-platform common dialog API.
|
||||||
|
// Eg. to prompt the user with a yes/no dialog:
|
||||||
|
//
|
||||||
|
// if dialog.MsgDlg("%s", "Do you want to continue?").YesNo() {
|
||||||
|
// // user pressed Yes
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// The general usage pattern is to call one of the toplevel *Dlg functions
|
||||||
|
// which return a *Builder structure. From here you can optionally call
|
||||||
|
// configuration functions (eg. Title) to customise the dialog, before
|
||||||
|
// using a launcher function to run the dialog.
|
||||||
|
package dialog
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ErrCancelled is an error returned when a user cancels/closes a dialog.
|
||||||
|
var ErrCancelled = errors.New("Cancelled")
|
||||||
|
|
||||||
|
// Cancelled refers to ErrCancelled.
|
||||||
|
// Deprecated: Use ErrCancelled instead.
|
||||||
|
var Cancelled = ErrCancelled
|
||||||
|
|
||||||
|
// Dlg is the common type for dialogs.
|
||||||
|
type Dlg struct {
|
||||||
|
Title string
|
||||||
|
}
|
||||||
|
|
||||||
|
// MsgBuilder is used for creating message boxes.
|
||||||
|
type MsgBuilder struct {
|
||||||
|
Dlg
|
||||||
|
Msg string
|
||||||
|
}
|
||||||
|
|
||||||
|
// Message initialises a MsgBuilder with the provided message.
|
||||||
|
func Message(format string, args ...interface{}) *MsgBuilder {
|
||||||
|
return &MsgBuilder{Msg: fmt.Sprintf(format, args...)}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Title specifies what the title of the message dialog will be.
|
||||||
|
func (b *MsgBuilder) Title(title string) *MsgBuilder {
|
||||||
|
b.Dlg.Title = title
|
||||||
|
return b
|
||||||
|
}
|
||||||
|
|
||||||
|
// YesNo spawns the message dialog with two buttons, "Yes" and "No".
|
||||||
|
// Returns true iff the user selected "Yes".
|
||||||
|
func (b *MsgBuilder) YesNo() bool {
|
||||||
|
return b.yesNo()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Info spawns the message dialog with an information icon and single button, "Ok".
|
||||||
|
func (b *MsgBuilder) Info() {
|
||||||
|
b.info()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Error spawns the message dialog with an error icon and single button, "Ok".
|
||||||
|
func (b *MsgBuilder) Error() {
|
||||||
|
b.error()
|
||||||
|
}
|
||||||
|
|
||||||
|
// FileFilter represents a category of files (eg. audio files, spreadsheets).
|
||||||
|
type FileFilter struct {
|
||||||
|
Desc string
|
||||||
|
Extensions []string
|
||||||
|
}
|
||||||
|
|
||||||
|
// FileBuilder is used for creating file browsing dialogs.
|
||||||
|
type FileBuilder struct {
|
||||||
|
Dlg
|
||||||
|
StartDir string
|
||||||
|
Filters []FileFilter
|
||||||
|
}
|
||||||
|
|
||||||
|
// File initialises a FileBuilder using the default configuration.
|
||||||
|
func File() *FileBuilder {
|
||||||
|
return &FileBuilder{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Title specifies the title to be used for the dialog.
|
||||||
|
func (b *FileBuilder) Title(title string) *FileBuilder {
|
||||||
|
b.Dlg.Title = title
|
||||||
|
return b
|
||||||
|
}
|
||||||
|
|
||||||
|
// Filter adds a category of files to the types allowed by the dialog. Multiple
|
||||||
|
// calls to Filter are cumulative - any of the provided categories will be allowed.
|
||||||
|
// By default all files can be selected.
|
||||||
|
//
|
||||||
|
// The special extension '*' allows all files to be selected when the Filter is active.
|
||||||
|
func (b *FileBuilder) Filter(desc string, extensions ...string) *FileBuilder {
|
||||||
|
filt := FileFilter{desc, extensions}
|
||||||
|
if len(filt.Extensions) == 0 {
|
||||||
|
filt.Extensions = append(filt.Extensions, "*")
|
||||||
|
}
|
||||||
|
b.Filters = append(b.Filters, filt)
|
||||||
|
return b
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetStartDir specifies the initial directory of the dialog.
|
||||||
|
func (b *FileBuilder) SetStartDir(startDir string) *FileBuilder {
|
||||||
|
b.StartDir = startDir
|
||||||
|
return b
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load spawns the file selection dialog using the configured settings,
|
||||||
|
// asking the user to select a single file. Returns ErrCancelled as the error
|
||||||
|
// if the user cancels or closes the dialog.
|
||||||
|
func (b *FileBuilder) Load() (string, error) {
|
||||||
|
return b.load()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Save spawns the file selection dialog using the configured settings,
|
||||||
|
// asking the user for a filename to save as. If the chosen file exists, the
|
||||||
|
// user is prompted whether they want to overwrite the file. Returns
|
||||||
|
// ErrCancelled as the error if the user cancels/closes the dialog, or selects
|
||||||
|
// not to overwrite the file.
|
||||||
|
func (b *FileBuilder) Save() (string, error) {
|
||||||
|
return b.save()
|
||||||
|
}
|
||||||
|
|
||||||
|
// DirectoryBuilder is used for directory browse dialogs.
|
||||||
|
type DirectoryBuilder struct {
|
||||||
|
Dlg
|
||||||
|
StartDir string
|
||||||
|
}
|
||||||
|
|
||||||
|
// Directory initialises a DirectoryBuilder using the default configuration.
|
||||||
|
func Directory() *DirectoryBuilder {
|
||||||
|
return &DirectoryBuilder{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Browse spawns the directory selection dialog using the configured settings,
|
||||||
|
// asking the user to select a single folder. Returns ErrCancelled as the error
|
||||||
|
// if the user cancels or closes the dialog.
|
||||||
|
func (b *DirectoryBuilder) Browse() (string, error) {
|
||||||
|
return b.browse()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Title specifies the title to be used for the dialog.
|
||||||
|
func (b *DirectoryBuilder) Title(title string) *DirectoryBuilder {
|
||||||
|
b.Dlg.Title = title
|
||||||
|
return b
|
||||||
|
}
|
|
@ -0,0 +1,60 @@
|
||||||
|
package dialog
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/OpenDiablo2/dialog/cocoa"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Init() {}
|
||||||
|
|
||||||
|
func (b *MsgBuilder) yesNo() bool {
|
||||||
|
return cocoa.YesNoDlg(b.Msg, b.Dlg.Title)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *MsgBuilder) info() {
|
||||||
|
cocoa.InfoDlg(b.Msg, b.Dlg.Title)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *MsgBuilder) error() {
|
||||||
|
cocoa.ErrorDlg(b.Msg, b.Dlg.Title)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *FileBuilder) load() (string, error) {
|
||||||
|
return b.run(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *FileBuilder) save() (string, error) {
|
||||||
|
return b.run(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *FileBuilder) run(save bool) (string, error) {
|
||||||
|
star := false
|
||||||
|
var exts []string
|
||||||
|
for _, filt := range b.Filters {
|
||||||
|
for _, ext := range filt.Extensions {
|
||||||
|
if ext == "*" {
|
||||||
|
star = true
|
||||||
|
} else {
|
||||||
|
exts = append(exts, ext)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if star && save {
|
||||||
|
/* OSX doesn't allow the user to switch visible file types/extensions. Also
|
||||||
|
** NSSavePanel's allowsOtherFileTypes property has no effect for an open
|
||||||
|
** dialog, so if "*" is a possible extension we must always show all files. */
|
||||||
|
exts = nil
|
||||||
|
}
|
||||||
|
f, err := cocoa.FileDlg(save, b.Dlg.Title, exts, star)
|
||||||
|
if f == "" && err == nil {
|
||||||
|
return "", ErrCancelled
|
||||||
|
}
|
||||||
|
return f, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *DirectoryBuilder) browse() (string, error) {
|
||||||
|
f, err := cocoa.DirDlg(b.Dlg.Title)
|
||||||
|
if f == "" && err == nil {
|
||||||
|
return "", ErrCancelled
|
||||||
|
}
|
||||||
|
return f, err
|
||||||
|
}
|
|
@ -0,0 +1,103 @@
|
||||||
|
package dialog
|
||||||
|
|
||||||
|
// #cgo pkg-config: gtk+-3.0
|
||||||
|
// #include <gtk/gtk.h>
|
||||||
|
// #include <stdlib.h>
|
||||||
|
// static GtkWidget* msgdlg(GtkWindow *parent, GtkDialogFlags flags, GtkMessageType type, GtkButtonsType buttons, char *msg) {
|
||||||
|
// return gtk_message_dialog_new(parent, flags, type, buttons, "%s", msg);
|
||||||
|
// }
|
||||||
|
// static GtkWidget* filedlg(char *title, GtkWindow *parent, GtkFileChooserAction action, char* acceptText) {
|
||||||
|
// return gtk_file_chooser_dialog_new(title, parent, action, "Cancel", GTK_RESPONSE_CANCEL, acceptText, GTK_RESPONSE_ACCEPT, NULL);
|
||||||
|
// }
|
||||||
|
import "C"
|
||||||
|
import "unsafe"
|
||||||
|
|
||||||
|
func Init() {
|
||||||
|
C.gtk_init(nil, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
func closeDialog(dlg *C.GtkWidget) {
|
||||||
|
C.gtk_widget_destroy(dlg)
|
||||||
|
/* The Destroy call itself isn't enough to remove the dialog from the screen; apparently
|
||||||
|
** that happens once the GTK main loop processes some further events. But if we're
|
||||||
|
** in a non-GTK app the main loop isn't running, so we empty the event queue before
|
||||||
|
** returning from the dialog functions.
|
||||||
|
** Not sure how this interacts with an actual GTK app... */
|
||||||
|
for C.gtk_events_pending() != 0 {
|
||||||
|
C.gtk_main_iteration()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func runMsgDlg(defaultTitle string, flags C.GtkDialogFlags, msgtype C.GtkMessageType, buttons C.GtkButtonsType, b *MsgBuilder) C.gint {
|
||||||
|
cmsg := C.CString(b.Msg)
|
||||||
|
defer C.free(unsafe.Pointer(cmsg))
|
||||||
|
dlg := C.msgdlg(nil, flags, msgtype, buttons, cmsg)
|
||||||
|
ctitle := C.CString(firstOf(b.Dlg.Title, defaultTitle))
|
||||||
|
defer C.free(unsafe.Pointer(ctitle))
|
||||||
|
C.gtk_window_set_title((*C.GtkWindow)(unsafe.Pointer(dlg)), ctitle)
|
||||||
|
defer closeDialog(dlg)
|
||||||
|
return C.gtk_dialog_run((*C.GtkDialog)(unsafe.Pointer(dlg)))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *MsgBuilder) yesNo() bool {
|
||||||
|
return runMsgDlg("Confirm?", 0, C.GTK_MESSAGE_QUESTION, C.GTK_BUTTONS_YES_NO, b) == C.GTK_RESPONSE_YES
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *MsgBuilder) info() {
|
||||||
|
runMsgDlg("Information", 0, C.GTK_MESSAGE_INFO, C.GTK_BUTTONS_OK, b)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *MsgBuilder) error() {
|
||||||
|
runMsgDlg("Error", 0, C.GTK_MESSAGE_ERROR, C.GTK_BUTTONS_OK, b)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *FileBuilder) load() (string, error) {
|
||||||
|
return chooseFile("Open File", "Open", C.GTK_FILE_CHOOSER_ACTION_OPEN, b)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *FileBuilder) save() (string, error) {
|
||||||
|
f, err := chooseFile("Save File", "Save", C.GTK_FILE_CHOOSER_ACTION_SAVE, b)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return f, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func chooseFile(title string, buttonText string, action C.GtkFileChooserAction, b *FileBuilder) (string, error) {
|
||||||
|
ctitle := C.CString(title)
|
||||||
|
defer C.free(unsafe.Pointer(ctitle))
|
||||||
|
cbuttonText := C.CString(buttonText)
|
||||||
|
defer C.free(unsafe.Pointer(cbuttonText))
|
||||||
|
dlg := C.filedlg(ctitle, nil, action, cbuttonText)
|
||||||
|
fdlg := (*C.GtkFileChooser)(unsafe.Pointer(dlg))
|
||||||
|
|
||||||
|
for _, filt := range b.Filters {
|
||||||
|
filter := C.gtk_file_filter_new()
|
||||||
|
cdesc := C.CString(filt.Desc)
|
||||||
|
defer C.free(unsafe.Pointer(cdesc))
|
||||||
|
C.gtk_file_filter_set_name(filter, cdesc)
|
||||||
|
|
||||||
|
for _, ext := range filt.Extensions {
|
||||||
|
cpattern := C.CString("*." + ext)
|
||||||
|
defer C.free(unsafe.Pointer(cpattern))
|
||||||
|
C.gtk_file_filter_add_pattern(filter, cpattern)
|
||||||
|
}
|
||||||
|
C.gtk_file_chooser_add_filter(fdlg, filter)
|
||||||
|
}
|
||||||
|
if b.StartDir != "" {
|
||||||
|
cdir := C.CString(b.StartDir)
|
||||||
|
defer C.free(unsafe.Pointer(cdir))
|
||||||
|
C.gtk_file_chooser_set_current_folder(fdlg, cdir)
|
||||||
|
}
|
||||||
|
C.gtk_file_chooser_set_do_overwrite_confirmation(fdlg, C.TRUE)
|
||||||
|
r := C.gtk_dialog_run((*C.GtkDialog)(unsafe.Pointer(dlg)))
|
||||||
|
defer closeDialog(dlg)
|
||||||
|
if r == C.GTK_RESPONSE_ACCEPT {
|
||||||
|
return C.GoString(C.gtk_file_chooser_get_filename(fdlg)), nil
|
||||||
|
}
|
||||||
|
return "", ErrCancelled
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *DirectoryBuilder) browse() (string, error) {
|
||||||
|
return chooseFile("Open Folder", "Open", C.GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER, &FileBuilder{Dlg: b.Dlg})
|
||||||
|
}
|
|
@ -0,0 +1,143 @@
|
||||||
|
package dialog
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"reflect"
|
||||||
|
"syscall"
|
||||||
|
"unicode/utf16"
|
||||||
|
"unsafe"
|
||||||
|
|
||||||
|
"github.com/TheTitanrain/w32"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Init() {}
|
||||||
|
|
||||||
|
type WinDlgError int
|
||||||
|
|
||||||
|
func (e WinDlgError) Error() string {
|
||||||
|
return fmt.Sprintf("CommDlgExtendedError: %#x", e)
|
||||||
|
}
|
||||||
|
|
||||||
|
func err() error {
|
||||||
|
e := w32.CommDlgExtendedError()
|
||||||
|
if e == 0 {
|
||||||
|
return ErrCancelled
|
||||||
|
}
|
||||||
|
return WinDlgError(e)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *MsgBuilder) yesNo() bool {
|
||||||
|
r := w32.MessageBox(w32.HWND(0), b.Msg, firstOf(b.Dlg.Title, "Confirm?"), w32.MB_YESNO)
|
||||||
|
return r == w32.IDYES
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *MsgBuilder) info() {
|
||||||
|
w32.MessageBox(w32.HWND(0), b.Msg, firstOf(b.Dlg.Title, "Information"), w32.MB_OK|w32.MB_ICONINFORMATION)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *MsgBuilder) error() {
|
||||||
|
w32.MessageBox(w32.HWND(0), b.Msg, firstOf(b.Dlg.Title, "Error"), w32.MB_OK|w32.MB_ICONERROR)
|
||||||
|
}
|
||||||
|
|
||||||
|
type filedlg struct {
|
||||||
|
buf []uint16
|
||||||
|
filters []uint16
|
||||||
|
opf *w32.OPENFILENAME
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d filedlg) Filename() string {
|
||||||
|
i := 0
|
||||||
|
for i < len(d.buf) && d.buf[i] != 0 {
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
return string(utf16.Decode(d.buf[:i]))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *FileBuilder) load() (string, error) {
|
||||||
|
d := openfile(w32.OFN_FILEMUSTEXIST, b)
|
||||||
|
if w32.GetOpenFileName(d.opf) {
|
||||||
|
return d.Filename(), nil
|
||||||
|
}
|
||||||
|
return "", err()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *FileBuilder) save() (string, error) {
|
||||||
|
d := openfile(w32.OFN_OVERWRITEPROMPT, b)
|
||||||
|
if w32.GetSaveFileName(d.opf) {
|
||||||
|
return d.Filename(), nil
|
||||||
|
}
|
||||||
|
return "", err()
|
||||||
|
}
|
||||||
|
|
||||||
|
/* syscall.UTF16PtrFromString not sufficient because we need to encode embedded NUL bytes */
|
||||||
|
func utf16ptr(utf16 []uint16) *uint16 {
|
||||||
|
if utf16[len(utf16)-1] != 0 {
|
||||||
|
panic("refusing to make ptr to non-NUL terminated utf16 slice")
|
||||||
|
}
|
||||||
|
h := (*reflect.SliceHeader)(unsafe.Pointer(&utf16))
|
||||||
|
return (*uint16)(unsafe.Pointer(h.Data))
|
||||||
|
}
|
||||||
|
|
||||||
|
func utf16slice(ptr *uint16) []uint16 {
|
||||||
|
hdr := reflect.SliceHeader{Data: uintptr(unsafe.Pointer(ptr)), Len: 1, Cap: 1}
|
||||||
|
slice := *((*[]uint16)(unsafe.Pointer(&hdr)))
|
||||||
|
i := 0
|
||||||
|
for slice[len(slice)-1] != 0 {
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
hdr.Len = i
|
||||||
|
slice = *((*[]uint16)(unsafe.Pointer(&hdr)))
|
||||||
|
return slice
|
||||||
|
}
|
||||||
|
|
||||||
|
func openfile(flags uint32, b *FileBuilder) (d filedlg) {
|
||||||
|
d.buf = make([]uint16, w32.MAX_PATH)
|
||||||
|
d.opf = &w32.OPENFILENAME{
|
||||||
|
File: utf16ptr(d.buf),
|
||||||
|
MaxFile: uint32(len(d.buf)),
|
||||||
|
Flags: flags,
|
||||||
|
}
|
||||||
|
d.opf.StructSize = uint32(unsafe.Sizeof(*d.opf))
|
||||||
|
if b.StartDir != "" {
|
||||||
|
d.opf.InitialDir, _ = syscall.UTF16PtrFromString(b.StartDir)
|
||||||
|
}
|
||||||
|
if b.Dlg.Title != "" {
|
||||||
|
d.opf.Title, _ = syscall.UTF16PtrFromString(b.Dlg.Title)
|
||||||
|
}
|
||||||
|
for _, filt := range b.Filters {
|
||||||
|
/* build utf16 string of form "Music File\0*.mp3;*.ogg;*.wav;\0" */
|
||||||
|
d.filters = append(d.filters, utf16.Encode([]rune(filt.Desc))...)
|
||||||
|
d.filters = append(d.filters, 0)
|
||||||
|
for _, ext := range filt.Extensions {
|
||||||
|
s := fmt.Sprintf("*.%s;", ext)
|
||||||
|
d.filters = append(d.filters, utf16.Encode([]rune(s))...)
|
||||||
|
}
|
||||||
|
d.filters = append(d.filters, 0)
|
||||||
|
}
|
||||||
|
if d.filters != nil {
|
||||||
|
d.filters = append(d.filters, 0, 0) // two extra NUL chars to terminate the list
|
||||||
|
d.opf.Filter = utf16ptr(d.filters)
|
||||||
|
}
|
||||||
|
return d
|
||||||
|
}
|
||||||
|
|
||||||
|
type dirdlg struct {
|
||||||
|
bi *w32.BROWSEINFO
|
||||||
|
}
|
||||||
|
|
||||||
|
func selectdir(b *DirectoryBuilder) (d dirdlg) {
|
||||||
|
d.bi = &w32.BROWSEINFO{Flags: w32.BIF_RETURNONLYFSDIRS | w32.BIF_NEWDIALOGSTYLE}
|
||||||
|
if b.Dlg.Title != "" {
|
||||||
|
d.bi.Title, _ = syscall.UTF16PtrFromString(b.Dlg.Title)
|
||||||
|
}
|
||||||
|
return d
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *DirectoryBuilder) browse() (string, error) {
|
||||||
|
d := selectdir(b)
|
||||||
|
res := w32.SHBrowseForFolder(d.bi)
|
||||||
|
if res == 0 {
|
||||||
|
return "", ErrCancelled
|
||||||
|
}
|
||||||
|
return w32.SHGetPathFromIDList(res), nil
|
||||||
|
}
|
|
@ -0,0 +1,5 @@
|
||||||
|
module github.com/HACKERALERT/Picocrypt/src/dialog
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/TheTitanrain/w32 v0.0.0-20180517000239-4f5cfb03fabf
|
||||||
|
)
|
|
@ -0,0 +1,2 @@
|
||||||
|
github.com/TheTitanrain/w32 v0.0.0-20180517000239-4f5cfb03fabf h1:FPsprx82rdrX2jiKyS17BH6IrTmUBYqZa/CXT4uvb+I=
|
||||||
|
github.com/TheTitanrain/w32 v0.0.0-20180517000239-4f5cfb03fabf/go.mod h1:peYoMncQljjNS6tZwI9WVyQB3qZS6u79/N3mBOcnd3I=
|
|
@ -0,0 +1,10 @@
|
||||||
|
package dialog
|
||||||
|
|
||||||
|
func firstOf(args ...string) string {
|
||||||
|
for _, arg := range args {
|
||||||
|
if arg != "" {
|
||||||
|
return arg
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
Loading…
Reference in New Issue