From 23070fd3ba0fc3c7e7dc3f59010a32e971862180 Mon Sep 17 00:00:00 2001 From: Evan Su <48808396+HACKERALERT@users.noreply.github.com> Date: Thu, 3 Jun 2021 14:12:31 -0400 Subject: [PATCH] Add files via upload --- src/dialog/cocoa/dlg.h | 39 +++++++++ src/dialog/cocoa/dlg.m | 130 +++++++++++++++++++++++++++++ src/dialog/cocoa/dlg_darwin.go | 114 +++++++++++++++++++++++++ src/dialog/dlgs.go | 146 +++++++++++++++++++++++++++++++++ src/dialog/dlgs_darwin.go | 60 ++++++++++++++ src/dialog/dlgs_linux.go | 103 +++++++++++++++++++++++ src/dialog/dlgs_windows.go | 143 ++++++++++++++++++++++++++++++++ src/dialog/go.mod | 5 ++ src/dialog/go.sum | 2 + src/dialog/util.go | 10 +++ 10 files changed, 752 insertions(+) create mode 100644 src/dialog/cocoa/dlg.h create mode 100644 src/dialog/cocoa/dlg.m create mode 100644 src/dialog/cocoa/dlg_darwin.go create mode 100644 src/dialog/dlgs.go create mode 100644 src/dialog/dlgs_darwin.go create mode 100644 src/dialog/dlgs_linux.go create mode 100644 src/dialog/dlgs_windows.go create mode 100644 src/dialog/go.mod create mode 100644 src/dialog/go.sum create mode 100644 src/dialog/util.go diff --git a/src/dialog/cocoa/dlg.h b/src/dialog/cocoa/dlg.h new file mode 100644 index 0000000..466b929 --- /dev/null +++ b/src/dialog/cocoa/dlg.h @@ -0,0 +1,39 @@ +#include + +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); diff --git a/src/dialog/cocoa/dlg.m b/src/dialog/cocoa/dlg.m new file mode 100644 index 0000000..2963619 --- /dev/null +++ b/src/dialog/cocoa/dlg.m @@ -0,0 +1,130 @@ +#import +#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 \ No newline at end of file diff --git a/src/dialog/cocoa/dlg_darwin.go b/src/dialog/cocoa/dlg_darwin.go new file mode 100644 index 0000000..b6fe7bd --- /dev/null +++ b/src/dialog/cocoa/dlg_darwin.go @@ -0,0 +1,114 @@ +package cocoa + +// #cgo darwin LDFLAGS: -framework Cocoa +// #include +// #include +// #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") +} diff --git a/src/dialog/dlgs.go b/src/dialog/dlgs.go new file mode 100644 index 0000000..ad6693b --- /dev/null +++ b/src/dialog/dlgs.go @@ -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 +} diff --git a/src/dialog/dlgs_darwin.go b/src/dialog/dlgs_darwin.go new file mode 100644 index 0000000..07c7ee2 --- /dev/null +++ b/src/dialog/dlgs_darwin.go @@ -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 +} diff --git a/src/dialog/dlgs_linux.go b/src/dialog/dlgs_linux.go new file mode 100644 index 0000000..524964c --- /dev/null +++ b/src/dialog/dlgs_linux.go @@ -0,0 +1,103 @@ +package dialog + +// #cgo pkg-config: gtk+-3.0 +// #include +// #include +// 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}) +} diff --git a/src/dialog/dlgs_windows.go b/src/dialog/dlgs_windows.go new file mode 100644 index 0000000..7cea754 --- /dev/null +++ b/src/dialog/dlgs_windows.go @@ -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 +} diff --git a/src/dialog/go.mod b/src/dialog/go.mod new file mode 100644 index 0000000..174e50d --- /dev/null +++ b/src/dialog/go.mod @@ -0,0 +1,5 @@ +module github.com/HACKERALERT/Picocrypt/src/dialog + +require ( + github.com/TheTitanrain/w32 v0.0.0-20180517000239-4f5cfb03fabf +) diff --git a/src/dialog/go.sum b/src/dialog/go.sum new file mode 100644 index 0000000..842aef2 --- /dev/null +++ b/src/dialog/go.sum @@ -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= diff --git a/src/dialog/util.go b/src/dialog/util.go new file mode 100644 index 0000000..28126ee --- /dev/null +++ b/src/dialog/util.go @@ -0,0 +1,10 @@ +package dialog + +func firstOf(args ...string) string { + for _, arg := range args { + if arg != "" { + return arg + } + } + return "" +}