package dialog // #cgo pkg-config: gtk+-3.0 // #cgo LDFLAGS: -lX11 // #include // #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" var initSuccess bool func init() { C.XInitThreads() initSuccess = (C.gtk_init_check(nil, nil) == C.TRUE) } func checkStatus() { if !initSuccess { panic("gtk initialisation failed; presumably no X server is available") } } 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 { checkStatus() 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) { checkStatus() 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) } if b.StartFile != "" { cfile := C.CString(b.StartFile) defer C.free(unsafe.Pointer(cfile)) C.gtk_file_chooser_set_current_name(fdlg, cfile) } 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}) }