summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPietro Gagliardi <[email protected]>2014-08-26 12:52:32 -0400
committerPietro Gagliardi <[email protected]>2014-08-26 12:52:32 -0400
commitadbe5303e7d97e439e1f1f75df23d357b037f702 (patch)
treea9bafa99c1def93cae7afc54c3f2679445b3c0f5
parente7490ce49b113f4d1877de8f5ee4b40b183fbabf (diff)
Changed the way dialogs work so that they do real modality properly and implemented such on Windows.
-rw-r--r--redo/dialog.go17
-rw-r--r--redo/dialog_windows.c87
-rw-r--r--redo/dialog_windows.go14
-rw-r--r--redo/uitask_windows.c3
-rw-r--r--redo/winapi_windows.h3
-rw-r--r--redo/window.go2
-rw-r--r--redo/zz_test.go13
7 files changed, 75 insertions, 64 deletions
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
+type windowDialog interface {
+ openFile(f func(filename string))
+}
+
// 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)
+// 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() (filename string) {
- return openFile()
+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;
-}
+struct openFileData {
+ HWND parent;
+ void *f;
+ WCHAR *filenameBuffer;
+};
-WCHAR *openFile(void)
+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 = "<no file selected>"
+ }
+ 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 = "<no file selected>"
- }
- tw.fnlabel.SetText(fn)
+ OpenFile(tw.w, tw.openFile)
})
tw.fnlabel = NewStandaloneLabel("<no file selected>")
tw.festack = NewVerticalStack(tw.festart, tw.felabel, tw.festop,