Changed the way dialogs work so that they do real modality properly and implemented such on Windows.
This commit is contained in:
parent
e7490ce49b
commit
adbe5303e7
|
@ -2,10 +2,19 @@
|
||||||
|
|
||||||
package ui
|
package ui
|
||||||
|
|
||||||
// OpenFile opens a dialog box that asks the user to choose a file.
|
type windowDialog interface {
|
||||||
// It returns the selected filename, or an empty string if no file was chosen.
|
openFile(f func(filename string))
|
||||||
// All events stop while OpenFile is executing. (TODO move to doc.go)
|
}
|
||||||
// If possible on a given system, OpenFile() will not dereference links; it will return the link file itself.
|
|
||||||
func OpenFile() (filename string) {
|
// OpenFile opens a dialog box that asks the user to choose a file.
|
||||||
return openFile()
|
// The dialog box is modal to win, which mut not be nil.
|
||||||
|
// Some time after the dialog box is closed, OpenFile runs f on the main thread, passing filename.
|
||||||
|
// filename is the selected filename, or an empty string if no file was chosen.
|
||||||
|
// OpenFile does not ensure that f remains alive; the programmer is responsible for this.
|
||||||
|
// If possible on a given system, OpenFile() will not dereference links; it will return the link file itself.
|
||||||
|
func OpenFile(win Window, f func(filename string)) {
|
||||||
|
if win == nil {
|
||||||
|
panic("Window passed to OpenFile() cannot be nil")
|
||||||
|
}
|
||||||
|
win.openFile(f)
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,73 +3,60 @@
|
||||||
#include "winapi_windows.h"
|
#include "winapi_windows.h"
|
||||||
#include "_cgo_export.h"
|
#include "_cgo_export.h"
|
||||||
|
|
||||||
static LRESULT CALLBACK dialogSubProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam, UINT_PTR id, DWORD_PTR data)
|
|
||||||
{
|
|
||||||
switch (uMsg) {
|
|
||||||
case WM_COMMAND:
|
|
||||||
// we must re-enable other windows in the right order (see http://blogs.msdn.com/b/oldnewthing/archive/2004/02/27/81155.aspx)
|
|
||||||
// see http://stackoverflow.com/questions/25494914/is-there-something-like-cdn-filecancel-analogous-to-cdn-fileok-for-getting-when
|
|
||||||
if (HIWORD(wParam) == BN_CLICKED && LOWORD(wParam) == IDCANCEL)
|
|
||||||
SendMessageW(msgwin, msgEndModal, 0, 0);
|
|
||||||
break; // let the dialog handle it now
|
|
||||||
case WM_NCDESTROY:
|
|
||||||
if ((*fv_RemoveWindowSubclass)(hwnd, dialogSubProc, id) == FALSE)
|
|
||||||
xpanic("error removing dialog subclass (which was for its own event handler)", GetLastError());
|
|
||||||
}
|
|
||||||
return (*fv_DefSubclassProc)(hwnd, uMsg, wParam, lParam);
|
|
||||||
}
|
|
||||||
|
|
||||||
// this should be reasonable
|
// this should be reasonable
|
||||||
#define NFILENAME 4096
|
#define NFILENAME 4096
|
||||||
|
|
||||||
static UINT_PTR CALLBACK openSaveFileHook(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
|
struct openFileData {
|
||||||
{
|
HWND parent;
|
||||||
if (uMsg == WM_INITDIALOG) {
|
void *f;
|
||||||
HWND parent;
|
WCHAR *filenameBuffer;
|
||||||
|
};
|
||||||
parent = GetParent(hwnd);
|
|
||||||
if (parent == NULL)
|
static DWORD WINAPI doOpenFile(LPVOID data)
|
||||||
xpanic("error gettign parent of OpenFile() dialog for event handling", GetLastError());
|
|
||||||
if ((*fv_SetWindowSubclass)(parent, dialogSubProc, 0, (DWORD_PTR) NULL) == FALSE)
|
|
||||||
xpanic("error subclassing OpenFile() dialog to give it its own event handler", GetLastError());
|
|
||||||
} else if (uMsg == WM_NOTIFY) {
|
|
||||||
OFNOTIFY *of = (OFNOTIFY *) lParam;
|
|
||||||
|
|
||||||
if (of->hdr.code == CDN_FILEOK)
|
|
||||||
SendMessageW(msgwin, msgEndModal, 0, 0);
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
WCHAR *openFile(void)
|
|
||||||
{
|
{
|
||||||
|
struct openFileData *o = (struct openFileData *) data;
|
||||||
OPENFILENAMEW ofn;
|
OPENFILENAMEW ofn;
|
||||||
DWORD err;
|
DWORD err;
|
||||||
WCHAR *filenameBuffer;
|
|
||||||
|
|
||||||
// freed on the Go side
|
o->filenameBuffer[0] = L'\0'; // required by GetOpenFileName() to indicate no previous filename
|
||||||
filenameBuffer = (WCHAR *) malloc((NFILENAME + 1) * sizeof (WCHAR));
|
|
||||||
if (filenameBuffer == NULL)
|
|
||||||
xpanic("memory exhausted in OpenFile()", GetLastError());
|
|
||||||
filenameBuffer[0] = L'\0'; // required by GetOpenFileName() to indicate no previous filename
|
|
||||||
ZeroMemory(&ofn, sizeof (OPENFILENAMEW));
|
ZeroMemory(&ofn, sizeof (OPENFILENAMEW));
|
||||||
ofn.lStructSize = sizeof (OPENFILENAMEW);
|
ofn.lStructSize = sizeof (OPENFILENAMEW);
|
||||||
ofn.hwndOwner = NULL;
|
ofn.hwndOwner = o->parent;
|
||||||
ofn.hInstance = hInstance;
|
ofn.hInstance = hInstance;
|
||||||
ofn.lpstrFilter = NULL; // no filters
|
ofn.lpstrFilter = NULL; // no filters
|
||||||
ofn.lpstrFile = filenameBuffer;
|
ofn.lpstrFile = o->filenameBuffer;
|
||||||
ofn.nMaxFile = NFILENAME + 1; // TODO include + 1?
|
ofn.nMaxFile = NFILENAME + 1; // TODO include + 1?
|
||||||
ofn.lpstrInitialDir = NULL; // let system decide
|
ofn.lpstrInitialDir = NULL; // let system decide
|
||||||
ofn.lpstrTitle = NULL; // let system decide
|
ofn.lpstrTitle = NULL; // let system decide
|
||||||
// TODO OFN_SHAREAWARE?
|
// TODO OFN_SHAREAWARE?
|
||||||
ofn.Flags = OFN_ENABLEHOOK | OFN_EXPLORER | OFN_FILEMUSTEXIST | OFN_FORCESHOWHIDDEN | OFN_HIDEREADONLY | OFN_LONGNAMES | OFN_NOCHANGEDIR | OFN_NODEREFERENCELINKS | OFN_NOTESTFILECREATE | OFN_PATHMUSTEXIST;
|
ofn.Flags = OFN_EXPLORER | OFN_FILEMUSTEXIST | OFN_FORCESHOWHIDDEN | OFN_HIDEREADONLY | OFN_LONGNAMES | OFN_NOCHANGEDIR | OFN_NODEREFERENCELINKS | OFN_NOTESTFILECREATE | OFN_PATHMUSTEXIST;
|
||||||
ofn.lpfnHook = openSaveFileHook;
|
|
||||||
SendMessageW(msgwin, msgBeginModal, 0, 0);
|
|
||||||
if (GetOpenFileNameW(&ofn) == FALSE) {
|
if (GetOpenFileNameW(&ofn) == FALSE) {
|
||||||
err = CommDlgExtendedError();
|
err = CommDlgExtendedError();
|
||||||
if (err == 0) // user cancelled
|
if (err != 0) // user cancelled
|
||||||
return NULL;
|
xpaniccomdlg("error running open file dialog", err);
|
||||||
xpaniccomdlg("error running open file dialog", err);
|
free(o->filenameBuffer); // free now so we can set it to NULL without leaking
|
||||||
|
o->filenameBuffer = NULL;
|
||||||
}
|
}
|
||||||
return filenameBuffer;
|
if (PostMessageW(msgwin, msgOpenFileDone, (WPARAM) (o->filenameBuffer), (LPARAM) (o->f)) == 0)
|
||||||
|
xpanic("error posting OpenFile() finished message to message-only window", GetLastError());
|
||||||
|
free(o); // won't free o->f or o->filenameBuffer in above invocation
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void openFile(HWND hwnd, void *f)
|
||||||
|
{
|
||||||
|
struct openFileData *o;
|
||||||
|
|
||||||
|
// freed by the thread
|
||||||
|
o = (struct openFileData *) malloc(sizeof (struct openFileData));
|
||||||
|
if (o == NULL)
|
||||||
|
xpanic("memory exhausted allocating data structure in OpenFile()", GetLastError());
|
||||||
|
o->parent = hwnd;
|
||||||
|
o->f = f;
|
||||||
|
// freed on the Go side
|
||||||
|
o->filenameBuffer = (WCHAR *) malloc((NFILENAME + 1) * sizeof (WCHAR));
|
||||||
|
if (o->filenameBuffer == NULL)
|
||||||
|
xpanic("memory exhausted allocating filename buffer in OpenFile()", GetLastError());
|
||||||
|
if (CreateThread(NULL, 0, doOpenFile, (LPVOID) o, 0, NULL) == NULL)
|
||||||
|
xpanic("error creating thread for running OpenFIle()", GetLastError());
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,11 +9,17 @@ import (
|
||||||
// #include "winapi_windows.h"
|
// #include "winapi_windows.h"
|
||||||
import "C"
|
import "C"
|
||||||
|
|
||||||
func openFile() string {
|
func (w *window) openFile(f func(filename string)) {
|
||||||
name := C.openFile()
|
C.openFile(w.hwnd, unsafe.Pointer(&f))
|
||||||
|
}
|
||||||
|
|
||||||
|
//export finishOpenFile
|
||||||
|
func finishOpenFile(name *C.WCHAR, fp unsafe.Pointer) {
|
||||||
|
f := (*func(string))(fp)
|
||||||
if name == nil {
|
if name == nil {
|
||||||
return ""
|
(*f)("")
|
||||||
|
return
|
||||||
}
|
}
|
||||||
defer C.free(unsafe.Pointer(name))
|
defer C.free(unsafe.Pointer(name))
|
||||||
return wstrToString(name)
|
(*f)(wstrToString(name))
|
||||||
}
|
}
|
||||||
|
|
|
@ -146,6 +146,9 @@ static LRESULT CALLBACK msgwinproc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM l
|
||||||
endModal();
|
endModal();
|
||||||
EnumThreadWindows(GetCurrentThreadId(), beginEndModalAll, msgEndModal);
|
EnumThreadWindows(GetCurrentThreadId(), beginEndModalAll, msgEndModal);
|
||||||
return 0;
|
return 0;
|
||||||
|
case msgOpenFileDone:
|
||||||
|
finishOpenFile((WCHAR *) wParam, (void *) lParam);
|
||||||
|
return 0;
|
||||||
default:
|
default:
|
||||||
return DefWindowProcW(hwnd, uMsg, wParam, lParam);
|
return DefWindowProcW(hwnd, uMsg, wParam, lParam);
|
||||||
}
|
}
|
||||||
|
|
|
@ -40,6 +40,7 @@ enum {
|
||||||
msgAreaKeyUp,
|
msgAreaKeyUp,
|
||||||
msgLoadImageList,
|
msgLoadImageList,
|
||||||
msgTableMakeInitialCheckboxImageList,
|
msgTableMakeInitialCheckboxImageList,
|
||||||
|
msgOpenFileDone,
|
||||||
};
|
};
|
||||||
|
|
||||||
// uitask_windows.c
|
// uitask_windows.c
|
||||||
|
@ -149,6 +150,6 @@ enum {
|
||||||
extern HIMAGELIST makeCheckboxImageList(HWND, HTHEME *);
|
extern HIMAGELIST makeCheckboxImageList(HWND, HTHEME *);
|
||||||
|
|
||||||
// dialog_windows.c
|
// dialog_windows.c
|
||||||
extern WCHAR *openFile(void);
|
extern void openFile(HWND, void *);
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -25,6 +25,8 @@ type Window interface {
|
||||||
// If this handler returns true, the Window is closed as defined by Close above.
|
// If this handler returns true, the Window is closed as defined by Close above.
|
||||||
// If this handler returns false, the Window is not closed.
|
// If this handler returns false, the Window is not closed.
|
||||||
OnClosing(func() bool)
|
OnClosing(func() bool)
|
||||||
|
|
||||||
|
windowDialog
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewWindow creates a new Window with the given title text, size, and control.
|
// NewWindow creates a new Window with the given title text, size, and control.
|
||||||
|
|
|
@ -75,6 +75,13 @@ func (a *areaHandler) Paint(r image.Rectangle) *image.RGBA {
|
||||||
func (a *areaHandler) Mouse(me MouseEvent) { fmt.Printf("%#v\n", me) }
|
func (a *areaHandler) Mouse(me MouseEvent) { fmt.Printf("%#v\n", me) }
|
||||||
func (a *areaHandler) Key(ke KeyEvent) bool { fmt.Printf("%#v %q\n", ke, ke.Key); return a.handled }
|
func (a *areaHandler) Key(ke KeyEvent) bool { fmt.Printf("%#v %q\n", ke, ke.Key); return a.handled }
|
||||||
|
|
||||||
|
func (tw *testwin) openFile(fn string) {
|
||||||
|
if fn == "" {
|
||||||
|
fn = "<no file selected>"
|
||||||
|
}
|
||||||
|
tw.fnlabel.SetText(fn)
|
||||||
|
}
|
||||||
|
|
||||||
func (tw *testwin) addfe() {
|
func (tw *testwin) addfe() {
|
||||||
tw.festart = NewButton("Start")
|
tw.festart = NewButton("Start")
|
||||||
tw.festart.OnClicked(func() {
|
tw.festart.OnClicked(func() {
|
||||||
|
@ -106,11 +113,7 @@ func (tw *testwin) addfe() {
|
||||||
})
|
})
|
||||||
tw.openbtn = NewButton("Open")
|
tw.openbtn = NewButton("Open")
|
||||||
tw.openbtn.OnClicked(func() {
|
tw.openbtn.OnClicked(func() {
|
||||||
fn := OpenFile()
|
OpenFile(tw.w, tw.openFile)
|
||||||
if fn == "" {
|
|
||||||
fn = "<no file selected>"
|
|
||||||
}
|
|
||||||
tw.fnlabel.SetText(fn)
|
|
||||||
})
|
})
|
||||||
tw.fnlabel = NewStandaloneLabel("<no file selected>")
|
tw.fnlabel = NewStandaloneLabel("<no file selected>")
|
||||||
tw.festack = NewVerticalStack(tw.festart, tw.felabel, tw.festop,
|
tw.festack = NewVerticalStack(tw.festart, tw.felabel, tw.festop,
|
||||||
|
|
Loading…
Reference in New Issue