diff --git a/redo/dialog.go b/redo/dialog.go index 877ccf2..1567cc7 100644 --- a/redo/dialog.go +++ b/redo/dialog.go @@ -2,10 +2,19 @@ package ui -// OpenFile opens a dialog box that asks the user to choose a file. -// It returns the selected filename, or an empty string if no file was chosen. -// 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) { - return openFile() +type windowDialog interface { + openFile(f func(filename string)) +} + +// OpenFile opens a dialog box that asks the user to choose a file. +// 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) } diff --git a/redo/dialog_windows.c b/redo/dialog_windows.c index ad48cd5..10fb75c 100644 --- a/redo/dialog_windows.c +++ b/redo/dialog_windows.c @@ -3,73 +3,60 @@ #include "winapi_windows.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 #define NFILENAME 4096 -static UINT_PTR CALLBACK openSaveFileHook(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) -{ - if (uMsg == WM_INITDIALOG) { - HWND parent; - - parent = GetParent(hwnd); - if (parent == NULL) - 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 { + HWND parent; + void *f; + WCHAR *filenameBuffer; +}; + +static DWORD WINAPI doOpenFile(LPVOID data) { + struct openFileData *o = (struct openFileData *) data; OPENFILENAMEW ofn; DWORD err; - WCHAR *filenameBuffer; - // freed on the Go side - 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 + o->filenameBuffer[0] = L'\0'; // required by GetOpenFileName() to indicate no previous filename ZeroMemory(&ofn, sizeof (OPENFILENAMEW)); ofn.lStructSize = sizeof (OPENFILENAMEW); - ofn.hwndOwner = NULL; + ofn.hwndOwner = o->parent; ofn.hInstance = hInstance; ofn.lpstrFilter = NULL; // no filters - ofn.lpstrFile = filenameBuffer; + ofn.lpstrFile = o->filenameBuffer; ofn.nMaxFile = NFILENAME + 1; // TODO include + 1? ofn.lpstrInitialDir = NULL; // let system decide ofn.lpstrTitle = NULL; // let system decide // 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.lpfnHook = openSaveFileHook; - SendMessageW(msgwin, msgBeginModal, 0, 0); + ofn.Flags = OFN_EXPLORER | OFN_FILEMUSTEXIST | OFN_FORCESHOWHIDDEN | OFN_HIDEREADONLY | OFN_LONGNAMES | OFN_NOCHANGEDIR | OFN_NODEREFERENCELINKS | OFN_NOTESTFILECREATE | OFN_PATHMUSTEXIST; if (GetOpenFileNameW(&ofn) == FALSE) { err = CommDlgExtendedError(); - if (err == 0) // user cancelled - return NULL; - xpaniccomdlg("error running open file dialog", err); + if (err != 0) // user cancelled + 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()); } diff --git a/redo/dialog_windows.go b/redo/dialog_windows.go index 9ea483c..80f7ac6 100644 --- a/redo/dialog_windows.go +++ b/redo/dialog_windows.go @@ -9,11 +9,17 @@ import ( // #include "winapi_windows.h" import "C" -func openFile() string { - name := C.openFile() +func (w *window) openFile(f func(filename string)) { + C.openFile(w.hwnd, unsafe.Pointer(&f)) +} + +//export finishOpenFile +func finishOpenFile(name *C.WCHAR, fp unsafe.Pointer) { + f := (*func(string))(fp) if name == nil { - return "" + (*f)("") + return } defer C.free(unsafe.Pointer(name)) - return wstrToString(name) + (*f)(wstrToString(name)) } diff --git a/redo/uitask_windows.c b/redo/uitask_windows.c index 193b24f..6e5e384 100644 --- a/redo/uitask_windows.c +++ b/redo/uitask_windows.c @@ -146,6 +146,9 @@ static LRESULT CALLBACK msgwinproc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM l endModal(); EnumThreadWindows(GetCurrentThreadId(), beginEndModalAll, msgEndModal); return 0; + case msgOpenFileDone: + finishOpenFile((WCHAR *) wParam, (void *) lParam); + return 0; default: return DefWindowProcW(hwnd, uMsg, wParam, lParam); } diff --git a/redo/winapi_windows.h b/redo/winapi_windows.h index 477a2f0..ca88f01 100644 --- a/redo/winapi_windows.h +++ b/redo/winapi_windows.h @@ -40,6 +40,7 @@ enum { msgAreaKeyUp, msgLoadImageList, msgTableMakeInitialCheckboxImageList, + msgOpenFileDone, }; // uitask_windows.c @@ -149,6 +150,6 @@ enum { extern HIMAGELIST makeCheckboxImageList(HWND, HTHEME *); // dialog_windows.c -extern WCHAR *openFile(void); +extern void openFile(HWND, void *); #endif diff --git a/redo/window.go b/redo/window.go index 0c6391f..d700d14 100644 --- a/redo/window.go +++ b/redo/window.go @@ -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 false, the Window is not closed. OnClosing(func() bool) + + windowDialog } // NewWindow creates a new Window with the given title text, size, and control. diff --git a/redo/zz_test.go b/redo/zz_test.go index ab6591e..0afd7d7 100644 --- a/redo/zz_test.go +++ b/redo/zz_test.go @@ -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) Key(ke KeyEvent) bool { fmt.Printf("%#v %q\n", ke, ke.Key); return a.handled } +func (tw *testwin) openFile(fn string) { + if fn == "" { + fn = "" + } + tw.fnlabel.SetText(fn) +} + func (tw *testwin) addfe() { tw.festart = NewButton("Start") tw.festart.OnClicked(func() { @@ -106,11 +113,7 @@ func (tw *testwin) addfe() { }) tw.openbtn = NewButton("Open") tw.openbtn.OnClicked(func() { - fn := OpenFile() - if fn == "" { - fn = "" - } - tw.fnlabel.SetText(fn) + OpenFile(tw.w, tw.openFile) }) tw.fnlabel = NewStandaloneLabel("") tw.festack = NewVerticalStack(tw.festart, tw.felabel, tw.festop,