1 /* tclmsgbox.c -- Tcl code to handle a Windows MessageBox in the background.
2 Copyright (C) 1998 Cygnus Solutions.
3 Written by Ian Lance Taylor <ian@cygnus.com>. */
12 /* FIXME: We use some internal Tcl and Tk Windows stuff. */
15 EXTERN HINSTANCE TclWinGetTclInstance (void);
19 /* This file defines a single Tcl command.
21 ide_messageBox CODE [ARGUMENTS]
23 This is just like tk_messageBox, except that it does not return
24 a value. Instead, when the user clicks on a button closing the
25 message box, this invokes CODE, appending the selected value.
27 On Windows, this runs the MessageBox function in another
28 thread. This permits a program which handles IDE requests from
29 other programs to not return from the request until the
30 MessageBox completes. This is not possible without using
31 another thread, since the MessageBox function call will be
32 running its own event loop, and will be higher on the stack
35 On Unix tk_messageBox runs in the regular Tk event loop, so
36 another thread is not required.
40 static LRESULT CALLBACK msgbox_wndproc (HWND, UINT, WPARAM, LPARAM);
41 static int msgbox_eventproc (Tcl_Event *, int);
43 /* The hidden message box window. */
45 static HWND hidden_hwnd;
47 /* The message number we use to indicate that the MessageBox call has
50 #define MSGBOX_MESSAGE (WM_USER + 1)
52 /* We pass a pointer to this structure to the thread function. It
53 passes it back to the hidden window procedure. */
57 /* Tcl interpreter. */
59 /* Tcl code to execute when MessageBox completes. */
61 /* Hidden window handle. */
63 /* MessageBox arguments. */
68 /* Result of MessageBox call. */
72 /* This is the structure we pass to Tcl_QueueEvent. */
76 /* The base structure for all events. */
78 /* The message box data for this event. */
79 struct msgbox_data *md;
82 /* Initialize a hidden window to handle messages from the message box
90 if (hidden_hwnd != NULL)
96 class.hInstance = TclWinGetTclInstance();
97 class.hbrBackground = NULL;
98 class.lpszMenuName = NULL;
99 class.lpszClassName = "ide_messagebox";
100 class.lpfnWndProc = msgbox_wndproc;
102 class.hCursor = NULL;
104 if (! RegisterClass (&class))
107 hidden_hwnd = CreateWindow ("ide_messagebox", "ide_messagebox", WS_TILED,
108 0, 0, 0, 0, NULL, NULL, class.hInstance, NULL);
109 if (hidden_hwnd == NULL)
115 /* This is called as an exit handler. */
118 msgbox_exit (ClientData cd)
120 if (hidden_hwnd != NULL)
122 UnregisterClass ("ide_messagebox", TclWinGetTclInstance ());
123 DestroyWindow (hidden_hwnd);
126 /* FIXME: Ideally we would kill off any remaining threads and
127 somehow free up the associated data. */
131 /* This is the thread function which actually invokes the MessageBox
132 function. This function runs in a separate thread. */
135 msgbox_thread (LPVOID arg)
137 struct msgbox_data *md = (struct msgbox_data *) arg;
139 md->result = MessageBox (md->hwnd, md->message, md->title,
140 md->flags | MB_SETFOREGROUND);
141 PostMessage (md->hidden_hwnd, MSGBOX_MESSAGE, 0, (LPARAM) arg);
145 /* This function handles Windows events for the hidden window. When
146 the MessageBox function call completes in the thread, this function
147 will be called with MSGBOX_MESSAGE. */
149 static LRESULT CALLBACK
150 msgbox_wndproc (HWND hwnd, UINT message, WPARAM wparam, LPARAM lparam)
152 struct msgbox_event *me;
154 if (message != MSGBOX_MESSAGE)
155 return DefWindowProc (hwnd, message, wparam, lparam);
157 /* Queue up a Tcl event. */
158 me = (struct msgbox_event *) ckalloc (sizeof *me);
159 me->header.proc = msgbox_eventproc;
160 me->md = (struct msgbox_data *) lparam;
161 Tcl_QueueEvent ((Tcl_Event *) me, TCL_QUEUE_TAIL);
166 /* This function handles Tcl events. It is invoked when a MessageBox
170 msgbox_eventproc (Tcl_Event *event, int flags)
172 struct msgbox_event *me = (struct msgbox_event *) event;
177 /* Only execute the Tcl code if we are waiting for window events. */
178 if ((flags & TCL_WINDOW_EVENTS) == 0)
181 /* This switch is copied from Tk_MessageBoxCmd in Tk. */
182 switch (me->md->result)
184 case IDABORT: resstr = "abort"; break;
185 case IDCANCEL: resstr = "cancel"; break;
186 case IDIGNORE: resstr = "ignore"; break;
187 case IDNO: resstr = "no"; break;
188 case IDOK: resstr = "ok"; break;
189 case IDRETRY: resstr = "retry"; break;
190 case IDYES: resstr = "yes"; break;
191 default: resstr = "";
194 Tcl_DStringInit (&ds);
195 Tcl_DStringAppend (&ds, me->md->code, -1);
196 Tcl_DStringAppendElement (&ds, resstr);
198 /* FIXME: What if the interpreter has been deleted? */
199 ret = Tcl_GlobalEval (me->md->interp, Tcl_DStringValue (&ds));
201 Tcl_DStringFree (&ds);
203 /* We are now done with the msgbox_data structure, so we can free
204 the fields and the structure itself. */
205 ckfree (me->md->code);
206 ckfree (me->md->message);
207 ckfree (me->md->title);
208 ckfree ((char *) me->md);
211 Tcl_BackgroundError (me->md->interp);
216 /* This is a direct steal from tkWinDialog.c, for the use of msgbox.
217 I kept the same formatting as well, to make it easier to merge
220 typedef struct MsgTypeInfo {
230 msgTypeInfo[NUM_TYPES] = {
231 {"abortretryignore", MB_ABORTRETRYIGNORE, 3, {"abort", "retry", "ignore"}},
232 {"ok", MB_OK, 1, {"ok" }},
233 {"okcancel", MB_OKCANCEL, 2, {"ok", "cancel" }},
234 {"retrycancel", MB_RETRYCANCEL, 2, {"retry", "cancel" }},
235 {"yesno", MB_YESNO, 2, {"yes", "no" }},
236 {"yesnocancel", MB_YESNOCANCEL, 3, {"yes", "no", "cancel"}}
239 /* This is mostly a direct steal from Tk_MessageBoxCmd in Tk. I kept
240 the same formatting as well, to make it easier to merge changes. */
243 msgbox_internal (ClientData clientData, Tcl_Interp *interp, int argc,
244 char **argv, char *code)
247 Tk_Window parent = NULL;
251 int icon = MB_ICONINFORMATION;
253 int modal = MB_SYSTEMMODAL;
255 char *defaultBtn = NULL;
256 int defaultBtnIdx = -1;
258 for (i=1; i<argc; i+=2) {
260 int len = strlen(argv[i]);
262 if (strncmp(argv[i], "-default", len)==0) {
263 if (v==argc) {goto arg_missing;}
265 defaultBtn = argv[v];
267 else if (strncmp(argv[i], "-icon", len)==0) {
268 if (v==argc) {goto arg_missing;}
270 if (strcmp(argv[v], "error") == 0) {
273 else if (strcmp(argv[v], "info") == 0) {
274 icon = MB_ICONINFORMATION;
276 else if (strcmp(argv[v], "question") == 0) {
277 icon = MB_ICONQUESTION;
279 else if (strcmp(argv[v], "warning") == 0) {
280 icon = MB_ICONWARNING;
283 Tcl_AppendResult(interp, "invalid icon \"", argv[v],
284 "\", must be error, info, question or warning", NULL);
288 else if (strncmp(argv[i], "-message", len)==0) {
289 if (v==argc) {goto arg_missing;}
293 else if (strncmp(argv[i], "-parent", len)==0) {
294 if (v==argc) {goto arg_missing;}
296 parent=Tk_NameToWindow(interp, argv[v], Tk_MainWindow(interp));
297 if (parent == NULL) {
301 else if (strncmp(argv[i], "-title", len)==0) {
302 if (v==argc) {goto arg_missing;}
306 else if (strncmp(argv[i], "-type", len)==0) {
309 if (v==argc) {goto arg_missing;}
311 for (j=0; j<NUM_TYPES; j++) {
312 if (strcmp(argv[v], msgTypeInfo[j].name) == 0) {
313 type = msgTypeInfo[j].type;
319 Tcl_AppendResult(interp, "invalid message box type \"",
320 argv[v], "\", must be abortretryignore, ok, ",
321 "okcancel, retrycancel, yesno or yesnocancel", NULL);
325 else if (strncmp (argv[i], "-modal", len) == 0) {
326 if (v==argc) {goto arg_missing;}
328 if (strcmp(argv[v], "system") == 0) {
329 modal = MB_SYSTEMMODAL;
331 else if (strcmp(argv[v], "task") == 0) {
332 modal = MB_TASKMODAL;
334 else if (strcmp(argv[v], "owner") == 0) {
335 modal = MB_APPLMODAL;
338 Tcl_AppendResult(interp, "invalid modality \"", argv[v],
339 "\", must be system, task or owner", NULL);
344 Tcl_AppendResult(interp, "unknown option \"",
345 argv[i], "\", must be -default, -icon, ",
346 "-message, -parent, -title or -type", NULL);
351 /* Make sure we have a valid hWnd to act as the parent of this message box
353 if (parent == NULL && modal == MB_TASKMODAL) {
357 if (parent == NULL) {
358 parent = Tk_MainWindow(interp);
360 if (Tk_WindowId(parent) == None) {
361 Tk_MakeWindowExist(parent);
363 hWnd = Tk_GetHWND(Tk_WindowId(parent));
366 if (defaultBtn != NULL) {
367 for (i=0; i<NUM_TYPES; i++) {
368 if (type == msgTypeInfo[i].type) {
369 for (j=0; j<msgTypeInfo[i].numButtons; j++) {
370 if (strcmp(defaultBtn, msgTypeInfo[i].btnNames[j])==0) {
375 if (defaultBtnIdx < 0) {
376 Tcl_AppendResult(interp, "invalid default button \"",
377 defaultBtn, "\"", NULL);
384 switch (defaultBtnIdx) {
385 case 0: flags = MB_DEFBUTTON1; break;
386 case 1: flags = MB_DEFBUTTON2; break;
387 case 2: flags = MB_DEFBUTTON3; break;
388 case 3: flags = MB_DEFBUTTON4; break;
394 flags |= icon | type;
396 /* At this point we diverge from Tk_MessageBoxCmd. */
398 struct msgbox_data *md;
404 md = (struct msgbox_data *) ckalloc (sizeof *md);
406 md->code = ckalloc (strlen (code) + 1);
407 strcpy (md->code, code);
408 md->hidden_hwnd = hidden_hwnd;
410 md->message = ckalloc (strlen (message) + 1);
411 strcpy (md->message, message);
412 md->title = ckalloc (strlen (title) + 1);
413 strcpy (md->title, title);
414 md->flags = flags | modal;
416 /* Start the thread. This will call MessageBox, and then start
417 the ball rolling to execute the specified code. */
418 thread = CreateThread (NULL, 0, msgbox_thread, (LPVOID) md, 0, &tid);
419 CloseHandle (thread);
425 Tcl_AppendResult(interp, "value for \"", argv[argc-1], "\" missing",
430 /* This is the ide_messageBox function. */
433 msgbox (ClientData cd, Tcl_Interp *interp, int argc, char **argv)
439 sprintf (buf, "%d", argc);
440 Tcl_AppendResult (interp, "wrong # args: got ", buf,
441 " but expected at least 2", (char *) NULL);
445 /* Note that we don't bother to pass the correct value for argv[0]
446 to msgbox_internal, since it doesn't look at it anyhow. Note
447 that we will pass a NULL clientdata argument. */
448 return msgbox_internal (cd, interp, argc - 1, argv + 1, argv[1]);
451 /* Create the Tcl command. */
454 ide_create_messagebox_command (Tcl_Interp *interp)
456 Tcl_CreateExitHandler (msgbox_exit, NULL);
457 if (Tcl_CreateCommand (interp, "ide_messageBox", msgbox, NULL, NULL) == NULL)