Fixed modality issues on the GTK+ MsgBox() changes. The system introduced here has the advantage of scaling out to all other types of dialogs.

This commit is contained in:
Pietro Gagliardi 2014-06-08 11:50:11 -04:00
parent 54966a3823
commit 10f55564d0
3 changed files with 106 additions and 49 deletions

View File

@ -67,6 +67,12 @@ func g_signal_connect(obj *C.GtkWidget, sig string, callback C.GCallback, sysDat
C.gSignalConnect(obj, csig, callback, unsafe.Pointer(sysData))
}
func g_signal_connect_pointer(obj *C.GtkWidget, sig string, callback C.GCallback, p unsafe.Pointer) {
csig := C.CString(sig)
defer C.free(unsafe.Pointer(csig))
C.gSignalConnect(obj, csig, callback, p)
}
// there are two issues we solve here:
// 1) we need to make sure the uitask request gets garbage collected when we're done so as to not waste memory, but only when we're done so as to not have craziness happen
// 2) we need to make sure one idle function runs and finishes running before we start the next; otherwise we could wind up with weird things like the ret channel being closed early

View File

@ -9,9 +9,10 @@ import (
)
// #include "gtk_unix.h"
// extern void our_dialog_response_callback(GtkDialog *, gint, gpointer);
// /* because cgo seems to choke on ... */
// /* parent will be NULL if there is no parent, so this is fine */
// GtkWidget *gtkNewMsgBox(GtkWindow *parent, GtkMessageType type, GtkButtonsType buttons, char *title, char *text)
// static inline GtkWidget *gtkNewMsgBox(GtkWindow *parent, GtkMessageType type, GtkButtonsType buttons, char *title, char *text)
// {
// GtkWidget *k;
//
@ -22,57 +23,105 @@ import (
// }
import "C"
// dialogg performs the bookkeeping involved for having a GtkDialog behave the way we want.
type dialog struct {
parent *Window
pwin *C.GtkWindow
hadgroup C.gboolean
prevgroup *C.GtkWindowGroup
newgroup *C.GtkWindowGroup
result chan int
}
func mkdialog(parent *Window) *dialog {
return &dialog{
parent: parent,
result: make(chan int),
}
}
func (d *dialog) prepare() {
// to implement parent, we need to put the GtkMessageDialog into a new window group along with parent
// a GtkWindow can only be part of one group
// so we use this to save the parent window group (if there is one) and store the new window group
// after showing the message box, we restore the previous window group, so future parent == dialogWindow can work properly
// thanks to pbor and mclasen in irc.gimp.net/#gtk+
if d.parent != dialogWindow {
d.pwin = togtkwindow(d.parent.sysData.widget)
d.hadgroup = C.gtk_window_has_group(d.pwin)
// we can't remove a window from the "default window group"; otherwise this throws up Gtk-CRITICAL warnings
if d.hadgroup != C.FALSE {
d.prevgroup = C.gtk_window_get_group(d.pwin)
C.gtk_window_group_remove_window(d.prevgroup, d.pwin)
}
d.newgroup = C.gtk_window_group_new()
C.gtk_window_group_add_window(d.newgroup, d.pwin)
}
}
func (d *dialog) run(mk func() *C.GtkWidget) {
d.prepare()
box := mk()
if d.parent == dialogWindow {
go func() {
res := make(chan C.gint)
defer close(res)
uitask <- func() {
r := C.gtk_dialog_run((*C.GtkDialog)(unsafe.Pointer(box)))
d.cleanup(box)
res <- r
}
d.send(<-res)
}()
return
}
// otherwise just connect the delete signal
g_signal_connect_pointer(box, "response", dialog_response_callback, unsafe.Pointer(d))
C.gtk_widget_show_all(box)
}
//export our_dialog_response_callback
func our_dialog_response_callback(box *C.GtkDialog, res C.gint, data C.gpointer) {
d := (*dialog)(unsafe.Pointer(data))
d.cleanup((*C.GtkWidget)(unsafe.Pointer(box)))
go d.send(res) // send on another goroutine, like everything else
}
var dialog_response_callback = C.GCallback(C.our_dialog_response_callback)
func (d *dialog) cleanup(box *C.GtkWidget) {
// have to explicitly close the dialog box, otherwise wacky things will happen
C.gtk_widget_destroy(box)
if d.parent != dialogWindow {
C.gtk_window_group_remove_window(d.newgroup, d.pwin)
C.g_object_unref(C.gpointer(unsafe.Pointer(d.newgroup))) // free the group
if d.prevgroup != nil {
C.gtk_window_group_add_window(d.prevgroup, d.pwin)
} // otherwise it'll go back into the default group on its own
}
}
func (d *dialog) send(res C.gint) {
// this is where processing would go
d.result <- int(res)
}
func _msgBox(parent *Window, primarytext string, secondarytext string, msgtype C.GtkMessageType, buttons C.GtkButtonsType) (result chan int) {
result = make(chan int)
go func() {
ret := make(chan C.gint)
defer close(ret)
uitask <- func() {
var pwin *C.GtkWindow = nil
// to implement parent, we need to put the GtkMessageDialog into a new window group along with parent
// a GtkWindow can only be part of one group
// so we use this to save the parent window group (if there is one) and store the new window group
// after showing the message box, we restore the previous window group, so future parent == dialogWindow can work properly
// thanks to pbor and mclasen in irc.gimp.net/#gtk+
var prevgroup *C.GtkWindowGroup = nil
var newgroup *C.GtkWindowGroup
if parent != dialogWindow {
pwin = togtkwindow(parent.sysData.widget)
// we can't remove a window from the "default window group"; otherwise this throws up Gtk-CRITICAL warnings
if C.gtk_window_has_group(pwin) != C.FALSE {
prevgroup = C.gtk_window_get_group(pwin)
C.gtk_window_group_remove_window(prevgroup, pwin)
}
newgroup = C.gtk_window_group_new()
C.gtk_window_group_add_window(newgroup, pwin)
}
cprimarytext := C.CString(primarytext)
defer C.free(unsafe.Pointer(cprimarytext))
csecondarytext := (*C.char)(nil)
if secondarytext != "" {
csecondarytext = C.CString(secondarytext)
defer C.free(unsafe.Pointer(csecondarytext))
}
box := C.gtkNewMsgBox(pwin, msgtype, buttons, cprimarytext, csecondarytext)
response := C.gtk_dialog_run((*C.GtkDialog)(unsafe.Pointer(box)))
C.gtk_widget_destroy(box)
if parent != dialogWindow {
C.gtk_window_group_remove_window(newgroup, pwin)
C.g_object_unref(C.gpointer(unsafe.Pointer(newgroup))) // free the group
if prevgroup != nil {
C.gtk_window_group_add_window(prevgroup, pwin)
} // otherwise it'll go back into the default group on its own
}
ret <- response
d := mkdialog(parent)
uitask <- func() {
cprimarytext := C.CString(primarytext)
defer C.free(unsafe.Pointer(cprimarytext))
csecondarytext := (*C.char)(nil)
if secondarytext != "" {
csecondarytext = C.CString(secondarytext)
defer C.free(unsafe.Pointer(csecondarytext))
}
result <- int(<-ret)
}()
return result
d.run(func() *C.GtkWidget {
return C.gtkNewMsgBox(d.pwin, msgtype, buttons, cprimarytext, csecondarytext)
})
}
return d.result
}
func (w *Window) msgBox(primarytext string, secondarytext string) (done chan struct{}) {

View File

@ -20,6 +20,8 @@ UNIX:
- figure out why Page Up/Page Down does tab stops
ALL PLATFORMS:
- explain that if a local and global dialog are both opened at once, whetehr or not the local dialog is modal is system-defined (but the window it is local to will still be properly disabled once dismissed)
- will require moving dialog behavior to the package overview
- make sure MouseEvent's documentation has dragging described correctly (both Windows and GTK+ do)
- make all widths and heights parameters in constructors in the same place (or drop the ones in Window entirely?)
- Message boxes that belong to agiven parent are still application-modal on all platforms except Mac OS X because the whole system waits... we'll need to use a channel for this, I guess :S