![]() |
![]() |
![]() |
![]() |
This chapter discusses:
When you have to perform an operation that will take a long time to execute, it's not a good idea to implement the operation as a simple callback. During the time the callback is executing, the widgets in your application won't repair damage and they won't respond to user inputs at all. You should develop a strategy for handling lengthy operations within your application that involves returning from your callback as quickly as possible.
Returning from your callback allows the widgets to continue to update themselves visually. It also gives some visual feedback if the user attempts to do anything. If you don't want the user to be able to perform any UI operations during this time, you should deactivate the menu and command buttons. You can do this by setting the Pt_BLOCKED flag in the application window widget's Pt_ARG_FLAGS resource.
You might consider one of several different mechanisms for dealing with lengthy operations:
![]() |
It's safe to call PtBkgdHandlerProcess() in callbacks, work procedures, and input procedures, but not in a widget's Draw method or a PtRaw widget's drawing function. |
The following functions process Photon events:
Another difficult situation to deal with is when you wish to have your program prompt the user for information before continuing. This is usually handled by popping up a dialog for the user to enter the required information. If you don't want the user to be able to select any other operations until the information has been provided, you should create the dialog as a modal dialog.
A modal dialog doesn't allow user input to go to any of the other widgets in the application. To use a modal dialog to prompt for information, you have to make sure that events are processed within the callback function. See "Modal dialogs," below.
In general, the Photon libraries aren't threadsafe. To use Photon in a multithreaded program, do one of the following:
Or:
![]() |
Don't cancel a thread that might be executing a Photon library function or even a callback (the library might need to do some cleanup when the callback returns). |
Why use threads in a Photon application? Photon applications are event-driven and callback-based; whenever an event arrives, the appropriate callback is invoked to handle it, and then the control returns to the event loop to wait for the next event. How would adding threads improve that?
In most cases it wouldn't, and that's why most Photon applications are single-threaded. But there are situations where you might want to use multithreading in a Photon application.
For instance, some applications might need to be multithreaded for reasons that don't have much to do with Photon. Some of the tasks that such an application performs have to be done in a thread that can't bother processing Photon events, but needs to interact with your GUI from time to time, perhaps only to display its status. The application still needs to have a "Photon thread" that runs a Photon event loop and processes events.
Since most Photon applications are likely to remain single-threaded, the goal was to make using threads possible without punishing single-threaded apps with an unreasonable amount of overhead. That's one of the reasons why the Photon library is "thread-friendly" rather than completely thread-safe the way printf() and malloc() are thread-safe.
In other words, Photon library is still mostly single-threaded but has a mechanism that lets multiple threads use it in a safe way. This mechanism is a library lock, implemented by the PtEnter() and PtLeave() functions.
This lock behaves more or less like a big mutex protecting the Photon library: only one thread can own the lock at a time, and only that thread is allowed to make Photon calls. Any other thread that wants to call a Photon function must call PtEnter() first, which blocks until the lock is available. When the thread no longer needs the lock, it calls PtLeave() to let other threads use the Photon library.
To write your non-Photon threads:
PtLeave() doesn't atomically give the library lock to another thread blocked inside PtEnter(); the other thread gets unblocked, but then it must compete with any other threads as if it just called PtEnter(). If the thread that just called PtLeave() calls PtEnter() right away, it has a very good chance to be the first to get the lock, because it has the advantage of having the CPU already. But if the other thread has a higher priority, it takes over the CPU and prevents the first thread from obtaining the lock -- unless, of course, your application is running on a multiprocessor system.
For instance, a thread running the following loop has a very good chance of starving any other same-priority threads:
for (;;) { PtEnter(0); sleep(1); PtLeave(0); }
You should generally avoid writing a callback that takes a long time to complete, because it prevents your application from processing events and makes it feel unresponsive.
The traditional single-threaded way of dealing with the unresponsiveness is to have your callback call PtBkgdHandlerProcess() often enough to process any pending Photon events. But this may not always be possible or convenient, and it doesn't guarantee that your non-Photon thread is given access to the Photon library often enough.
An obvious multithreaded solution to this is to spawn a new thread to do the job instead of doing it in the callback. Quite often, it's the best solution, but it's not the only one.
The other choice is to have more than one Photon thread that processes Photon events in your application. Here's how:
![]() |
Remember that unlocking the library lets other threads modify your widgets and global variables while you're not looking, so be careful. |
If your callback allows other threads to process events while it's doing its lengthy operation, there's a chance that the person holding the mouse may press the same button again and your callback will be called again before its first invocation has completed.
You have to make sure that you don't do something silly if that happens -- or that it can't happen in the first place. Here are several ways to do this:
Or:
Or:
Or:
How you handle the problem doesn't really have much to do with Photon. It doesn't even have much to do with multithreading -- the same problem exists in a single-threaded app that calls PtBkgdHandlerProcess() from a callback.
The main reason why you should use PtEnter() and PtLeave() instead of using your own mutex is that when PtMainLoop() (or, more accurately, PtProcessEvent() called by PtMainLoop()) is about to wait for an event, it unlocks the library, and locks it back after it has an event it can process. This way, your non-Photon threads can freely access Photon functions when you don't have any events to process.
If you use your own mutex that PtProcessEvent() doesn't know about, it's unlocked only when your code unlocks it, which means that your non-Photon threads can't lock the mutex when your application is idle, only when it's processing an event that invokes one of your callbacks.
If some parts of your application must have deterministic realtime behavior, it's not a good idea to make Photon calls from your realtime threads. It's hard to predict how long PtEnter() will block; it can take a while for the thread that owns the lock to finish processing the current event or call PtLeave(), especially if it involves sending to other processes (like the window manager).
In a realtime application, it's better to have a "buffer thread" that accepts requests from your realtime threads and executes them in its own enter-leave section. A condition variable -- and possibly a queue of requests -- is a good way of sending these requests between threads.
The library keeps track of which of your threads are Photon threads (event readers) and which are non-Photon threads (non-readers). This way, the library always knows how many of your threads are available to receive and process events. This information is currently used only by the PtModalBlock() function (see "Modal operations and threads," below).
By default, the thread that called PtInit() is an event reader, and any other thread isn't. But if a nonreader thread calls PtProcessEvent() or PtMainLoop(), it automatically becomes an event reader.
![]() |
Don't expect Photon to start new threads for you if you run out of Photon threads -- it's your responsibility to make sure that you have enough threads. |
You can also turn a nonreader into a reader and back by passing a flag to PtEnter() or PtLeave():
If you don't need to change the thread's status, don't set either of these bits in the flags to PtEnter() and PtLeave(). In particular, this applies to any non-Photon thread that never processes any events.
If you're calling PtLeave() in a callback because you're about to do something really lengthy, it's a good idea to pass Pt_EVENT_PROCESS_PREVENT to PtLeave(). This tells the library that this thread isn't going to process events for a significant amount of time. Make sure to pass Pt_EVENT_PROCESS_ALLOW to PtEnter() before returning from the callback.
The exact definition of "a significant amount of time" depends on how long you think it's acceptable for your application to stop responding to events.
If, on the other hand, you know that the time your callback is going to spend between PtLeave() and PtEnter() isn't too long in terms of your user interface's being unresponsive, and the only reason you're unlocking the library is to avoid blocking your non-Photon threads unnecessarily, it's fine to pass zero to PtLeave() and PtEnter(). This saves you a few CPU cycles and doesn't do any harm; the worst that can happen is that you'll run out of Photon threads for a short while. What's more, the only way for it to happen is by starting a new modal operation (see below).
A modal operation is when you need to wait until a particular event happens before you can proceed -- for example, when you want the user to make a decision and push a Yes button or a No button. Since other events usually arrive before the one you're waiting for, you need to make sure that they're processed. To do that, you set up a callback that calls PtModalUnblock() when you have your answer, and then you call PtModalBlock(). In a single-threaded application, PtModalBlock() runs a loop similar to PtMainLoop(), except it can be told to return by calling PtModalUnblock(), and that's what you do in the callbacks attached to the Yes and No buttons.
In a multithreaded application, PtModalBlock() may either do the same or just block on a condition variable and let other Photon threads process events. By default, a condition variable is used if you have any other Photon threads. This removes the thread from the pool of event-processing threads, but prevents a situation where starting a second modal operation in a thread that's running the event loop in PtModalBlock() makes it impossible for the first PtModalBlock() to return until after the second modal operation has completed.
In most applications, there's no chance of this happening; usually, you either don't want to allow another modal operation until the current one has completed, or if you do, then you actually want the stacking behavior where the second modal operation prevents completion of the first one. Imagine, for instance, that the first modal operation is a file selector and the second one is an "Are you sure you want to overwrite this file?" question; you don't want the user to be able to dismiss the file selector before answering the question.
If you know that all the modal operations in your application are like that (or, in other words, if your application doesn't have two unrelated modal operations that may happen at the same time and still need to be able to complete in any order), then you can pass Pt_EVENT_PROCESS_ALLOW to PtModalBlock(). This will tell PtModalBlock() to run an event loop even if you have other Photon threads available, and may reduce the total number of Photon threads that your application needs.
Terminating a multithreaded application can be tricky: since calling exit() makes all your threads just disappear, you have to make sure that you don't exit while another thread is doing something that shouldn't be interrupted, like saving a file.
In a Photon application, the library may call PtExit() when your application's last window is closed. If you don't want that to happen while a thread is doing something important, turn off Ph_WM_CLOSE in your base window's Pt_ARG_WINDOW_MANAGED_FLAGS resource and handle the close message yourself. You will also need to find all the calls to exit() or PtExit() in your code and make sure that you don't exit until it's safe to exit. If something in your base window has a Done or Cancel type callback, you'll have to handle that, too.
The Photon library same some mechanisms to make handling this type of situations easier and safer:
The functions that implement this counter, PtPreventExit() and PtAllowExit(), are not only thread-safe, but also realtime-safe: they're guaranteed to run a bound number of opcodes and never generate priority inversion.
This mechanism is considered relatively low-level and meant primarily for threads that don't have anything to do with Photon (perhaps temporarily -- i.e. while in a PtLeave() / PtEnter() section). The reason is that certain Photon calls that normally are blocking will just terminate the calling thread if PtExit() is pending (otherwise PtExit() would potentially block for a long time). This also happens when a thread blocks before another thread calls PtExit(); the blocked thread is terminated without returning from the blocking call. The list of Photon calls that are "lethal" after another thread has called PtExit() includes attempts to process events, do anything modal, block on a condvar using PtCondWait() or PtCondTimedWait(), or call PtEnter() or PtLeave().
To prevent such situations, there's a Pt_DELAY_EXIT flag that you can pass to PtEnter() and PtLeave(). Doing it not only prevents PtEnter() and PtLeave() from terminating your thread if another thread has called PtExit(), but also implicitly calls PtPreventExit(). And if your thread dies for any reason, the library knows to call PtAllowExit() for you. The Pt_DELAY_EXIT flag makes your "save file" callback as simple as this:
my_callback( ... ) { PtLeave( Pt_DELAY_EXIT ); save_file(); /* You're safe here... */ PtEnter( 0 ); /* But this may kill you -- and that's OK! */ }
You still have to make sure that save_file() doesn't attempt any of the "lethal" calls. In particular, you can't pop up a dialog with an error message if something goes wrong (well, you can try, but it will kill your thread if PtExit() is pending). If you want to pop up a dialog that will potentially sit on the screen for minutes or hours, you have to do it before calling PtExit() -- for instance, by using the Pt_ARG_WINDOW_MANAGED_FLAGS trick mentioned above.
To terminate a thread that's running PtMainLoop() without terminating the entire application, call PtQuitMainLoop().
Note the following concerning threads and work procedures:
A work procedure is run whenever there are no messages for your application to respond to. In every iteration of the Photon event-handling loop, this procedure is called if no messages have arrived (rather than block on a MsgReceive() waiting for more messages). This procedure will be run very frequently, so it should be as short as possible.
![]() |
If your work procedure changes the display, it should call
PtFlush() to
make sure the display is updated.
See "Threads and work procedures," above, if you're writing a work procedure for a multithreaded program. |
A work procedure is a callback function that takes a single void * parameter, client_data. This client_data is data that you associate with the work procedure when you register it with the widget library. You should create a data structure for the work procedure that contains all its state information and provide this as the client_data.
You register, or add, a work procedure using the PtAppAddWorkProc():
PtWorkProcId_t *PtAppAddWorkProc( PtAppContext_t app_context, PtWorkProc_t work_func, void *data );
The parameters are:
PtAppAddWorkProc() returns a pointer to a PtWorkProcId_t structure that identifies the work procedure.
To remove a a work procedure when it's no longer needed, call PtAppRemoveWorkProc():
void PtAppRemoveWorkProc( PtAppContext_t app_context, PtWorkProcId_t *WorkProc_id );
passing it the same application context and the pointer returned by PtAppAddWorkProc().
A practical example of the use of work procedures would be too long to cover here, so here's a simple iterative example. The work procedure counts to a large number, updating a label to reflect its progress on a periodic basis.
#include <Pt.h> typedef struct workDialog { PtWidget_t *widget; PtWidget_t *label; PtWidget_t *ok_button; } WorkDialog_t; typedef struct countdownClosure { WorkDialog_t *dialog; int value; int maxvalue; int done; PtWorkProcId_t *work_id; } CountdownClosure_t; WorkDialog_t *create_working_dialog(PtWidget_t *parent) { PhDim_t dim; PtArg_t args[3]; int nargs; PtWidget_t *window, *group; WorkDialog_t *dialog = (WorkDialog_t *)malloc(sizeof(WorkDialog_t)); if (dialog) { dialog->widget = window = PtCreateWidget(PtWindow, parent, 0, NULL); nargs = 0; PtSetArg(&args[nargs], Pt_ARG_GROUP_ORIENTATION, Pt_GROUP_VERTICAL, 0); nargs++; PtSetArg(&args[nargs], Pt_ARG_GROUP_VERT_ALIGN, Pt_GROUP_VERT_CENTER, 0); nargs++; group = PtCreateWidget(PtGroup, window, nargs, args); nargs = 0; dim.w = 200; dim.h = 100; PtSetArg(&args[nargs], Pt_ARG_DIM, &dim, 0); nargs++; PtSetArg(&args[nargs], Pt_ARG_TEXT_STRING, "Counter: ", 0); nargs++; dialog->label = PtCreateWidget(PtLabel, group, nargs, args); PtCreateWidget(PtSeparator, group, 0, NULL); nargs = 0; PtSetArg(&args[nargs], Pt_ARG_TEXT_STRING, "Stop", 0); nargs++; dialog->ok_button = PtCreateWidget(PtButton, group, 1, args); } return dialog; } int done(PtWidget_t *w, void *client, PtCallbackInfo_t *call) { CountdownClosure_t *closure = (CountdownClosure_t *)client; call = call; if (!closure->done) { PtAppRemoveWorkProc(NULL, closure->work_id); } PtDestroyWidget(closure->dialog->widget); free(closure->dialog); free(closure); return (Pt_CONTINUE); } int count_cb(void *data) { CountdownClosure_t *closure = (CountdownClosure_t *)data; char buf[64]; int finished = 0; if ( closure->value++ == 0 || closure->value % 1000 == 0 ) { sprintf(buf, "Counter: %d", closure->value); PtSetResource( closure->dialog->label, Pt_ARG_TEXT_STRING, buf, 0); } if ( closure->value == closure->maxvalue ) { closure->done = finished = 1; PtSetResource( closure->dialog->ok_button, Pt_ARG_TEXT_STRING, "Done", 0); } return finished ? Pt_END : Pt_CONTINUE; } int push_button_cb(PtWidget_t *w, void *client, PtCallbackInfo_t *call) { PtWidget_t *parent = (PtWidget_t *)client; WorkDialog_t *dialog; w = w; call = call; dialog = create_working_dialog(parent); if (dialog) { CountdownClosure_t *closure = (CountdownClosure_t *) malloc(sizeof(CountdownClosure_t)); if (closure) { PtWorkProcId_t *id; closure->dialog = dialog; closure->value = 0; closure->maxvalue = 200000; closure->done = 0; closure->work_id = id = PtAppAddWorkProc(NULL, count_cb, closure); PtAddCallback(dialog->ok_button, Pt_CB_ACTIVATE, done, closure); PtRealizeWidget(dialog->widget); } } return (Pt_CONTINUE); } int main(int argc, char *argv[]) { PhDim_t dim; PtArg_t args[3]; int n; PtWidget_t *window; PtCallback_t callbacks[] = {{push_button_cb, NULL}}; char Helvetica14b[MAX_FONT_TAG]; if (PtInit(NULL) == -1) PtExit(EXIT_FAILURE); dim.w = 200; dim.h = 100; PtSetArg(&args[0], Pt_ARG_DIM, &dim, 0); if ((window = PtCreateWidget(PtWindow, Pt_NO_PARENT, 1, args)) == NULL) PtExit(EXIT_FAILURE); callbacks[0].data = window; n = 0; PtSetArg(&args[n++], Pt_ARG_TEXT_STRING, "Count Down...", 0); /* Use 14-point, bold Helvetica if it's available. */ if(PfGenerateFontName("Helvetica", PF_STYLE_BOLD, 14, Helvetica14b) == NULL) { perror("Unable to generate font name"); } else { PtSetArg(&args[n++], Pt_ARG_TEXT_FONT, Helvetica14b, 0); } PtSetArg(&args[n++], Pt_CB_ACTIVATE, callbacks, sizeof(callbacks)/sizeof(PtCallback_t)); PtCreateWidget(PtButton, window, n, args); PtRealizeWidget(window); PtMainLoop(); return (EXIT_SUCCESS); }
When the pushbutton is pressed, the callback attached to it creates a working dialog and adds a work procedure, passing a closure containing all the information needed to perform the countdown and to clean up when it's done.
The closure contains a pointer to the dialog, the current counter, and the value to count to. When the value is reached, the work procedure changes the label on the dialog's button and attaches a callback that will tear down the entire dialog when the button is pressed. Upon such completion, the work procedure returns Pt_END in order to get removed.
The done() function is called if the user stops the work procedure, or if it has completed. This function destroys the dialog associated with the work procedure and removes the work procedure if it was stopped by the user (i.e. it didn't run to completion).
If you run this example, you may discover one of the other features of work procedures -- they preempt one another. When you add a new work procedure, it preempts all others. The new work procedure will be the only one run until it has completed or is removed. After that, the work procedure that was previously running resumes. This is illustrated in the above example if the user presses the Count Down... button before a countdown is finished. A new countdown dialog is created, and that countdown runs to the exclusion of the first until it's done.
The granularity for this preemption is at the function call level. When the callback function for a work procedure returns, that work procedure may be preempted by another work procedure.
To create a modal dialog, you have to create a new PtWindow widget, normally as a child of the main application window.
To activate the modal dialog, you have to realize the dialog widget and block all the other window widgets in the application. To block the window or windows, call one of:
Both of these routines return a list of blocked widgets, which you'll need when you unblock them. Instead of blocking the windows, you can make the dialog modal by calling PtMakeModal().
After the modal dialog has been activated, call PtModalBlock() to start a modal loop to process Photon events until a termination condition is met.
When the operation associated with the modal dialog is completed or aborted, you have to dismiss the dialog. To do so:
We can easily change our previous example of work procedures so that its progress dialog behaves as a modal dialog. We'll add a PtModalCtrl_t structure to the callback closure, for PtModalBlock() and PtModalUnblock() to use.
The done() callback is altered to call PtModalUnblock() rather than free the closure:
int done(PtWidget_t *w, void *client, PtCallbackInfo_t *call) { CountdownClosure *closure = (CountdownClosure *)client; call = call; if (!closure->done) { PtAppRemoveWorkProc(NULL, closure->work_id); } PtDestroyWidget(closure->dialog->widget); free(closure->dialog); /* New: end the modal loop, return the counter's value as the response. */ PtModalUnblock(&(closure->modal_control), (void *) &(closure->value)); return (Pt_CONTINUE); }
All that remains at this point is to change the push_button_cb() callback function so that it blocks the window after realizing the progress dialog, starts the modal loop, and unblocks the windows and frees the closure after the dialog is dismissed.
Here's the new version of the push_button_cb() callback function:
int push_button_cb(PtWidget_t *w, void *client, PtCallbackInfo_t *call) { PtWidget_t *parent = (PtWidget_t *)client; WorkDialog *dialog; PtBlockedList_t * blocked_list; void * response; w = w; call = call; dialog = create_working_dialog(parent); if (dialog) { CountdownClosure *closure = (CountdownClosure *) malloc(sizeof(CountdownClosure)); if (closure) { PtWorkProcId_t *id; closure->dialog = dialog; closure->value = 0; closure->maxvalue = 200000; closure->done = 0; closure->work_id = id = PtAppAddWorkProc(NULL, count_cb, closure); PtAddCallback(dialog->ok_button, Pt_CB_ACTIVATE, done, closure); PtRealizeWidget(dialog->widget); /* New: Block all the windows except the dialog, process events until the dialog is closed, and then unblock all the windows. */ blocked_list = PtBlockAllWindows (dialog->widget, Ph_CURSOR_NOINPUT, Pg_TRANSPARENT); response = PtModalBlock( &(closure->modal_control), 0 ); printf ("Value reached was %d\n", *(int *)response ); free (closure); PtUnblockWindows (blocked_list); } } return (Pt_CONTINUE); }
Here's the new version of the whole program:
#include <Pt.h> typedef struct workDialog { PtWidget_t *widget; PtWidget_t *label; PtWidget_t *ok_button; } WorkDialog; typedef struct countdownClosure { WorkDialog *dialog; int value; int maxvalue; int done; PtWorkProcId_t *work_id; /* New member: */ PtModalCtrl_t modal_control; } CountdownClosure; WorkDialog *create_working_dialog(PtWidget_t *parent) { PhDim_t dim; PtArg_t args[3]; int nargs; PtWidget_t *window, *group; WorkDialog *dialog = (WorkDialog *)malloc(sizeof(WorkDialog)); if (dialog) { nargs = 0; PtSetArg(&args[nargs], Pt_ARG_WIN_PARENT, parent, 0); nargs++; PtSetParentWidget(NULL); dialog->widget = window = PtCreateWidget(PtWindow, parent, nargs, args); nargs = 0; PtSetArg(&args[nargs], Pt_ARG_GROUP_ORIENTATION, Pt_GROUP_VERTICAL, 0); nargs++; PtSetArg(&args[nargs], Pt_ARG_GROUP_VERT_ALIGN, Pt_GROUP_VERT_CENTER, 0); nargs++; group = PtCreateWidget(PtGroup, window, nargs, args); nargs = 0; dim.w = 200; dim.h = 100; PtSetArg(&args[nargs], Pt_ARG_DIM, &dim, 0); nargs++; PtSetArg(&args[nargs], Pt_ARG_TEXT_STRING, "Counter: ", 0); nargs++; dialog->label = PtCreateWidget(PtLabel, group, nargs, args); PtCreateWidget(PtSeparator, group, 0, NULL); nargs = 0; PtSetArg(&args[nargs], Pt_ARG_TEXT_STRING, "Stop", 0); nargs++; dialog->ok_button = PtCreateWidget(PtButton, group, 1, args); } return dialog; } int done(PtWidget_t *w, void *client, PtCallbackInfo_t *call) { CountdownClosure *closure = (CountdownClosure *)client; call = call; if (!closure->done) { PtAppRemoveWorkProc(NULL, closure->work_id); } PtDestroyWidget(closure->dialog->widget); free(closure->dialog); /* New: end the modal loop, return the counter's value as the response. */ PtModalUnblock(&(closure->modal_control), (void *) &(closure->value)); return (Pt_CONTINUE); } int count_cb(void *data) { CountdownClosure *closure = (CountdownClosure *)data; char buf[64]; int finished = 0; if ( closure->value++ == 0 || closure->value % 1000 == 0 ) { sprintf(buf, "Counter: %d", closure->value); PtSetResource( closure->dialog->label, Pt_ARG_TEXT_STRING, buf, 0); } if ( closure->value == closure->maxvalue ) { closure->done = finished = 1; PtSetResource( closure->dialog->ok_button, Pt_ARG_TEXT_STRING, "Done", 0); } return finished ? Pt_END : Pt_CONTINUE; } int push_button_cb(PtWidget_t *w, void *client, PtCallbackInfo_t *call) { PtWidget_t *parent = (PtWidget_t *)client; WorkDialog *dialog; PtBlockedList_t * blocked_list; void * response; w = w; call = call; dialog = create_working_dialog(parent); if (dialog) { CountdownClosure *closure = (CountdownClosure *) malloc(sizeof(CountdownClosure)); if (closure) { PtWorkProcId_t *id; closure->dialog = dialog; closure->value = 0; closure->maxvalue = 200000; closure->done = 0; closure->work_id = id = PtAppAddWorkProc(NULL, count_cb, closure); PtAddCallback(dialog->ok_button, Pt_CB_ACTIVATE, done, closure); PtRealizeWidget(dialog->widget); /* New: Block all the windows except the dialog, process events until the dialog is closed, and then unblock all the windows. */ blocked_list = PtBlockAllWindows (dialog->widget, Ph_CURSOR_NOINPUT, Pg_TRANSPARENT); response = PtModalBlock( &(closure->modal_control), 0 ); printf ("Value reached was %d\n", *(int *)response ); free (closure); PtUnblockWindows (blocked_list); } } return (Pt_CONTINUE); } int main(int argc, char *argv[]) { PhDim_t dim; PtArg_t args[3]; int n; PtWidget_t *window; PtCallback_t callbacks[] = {{push_button_cb, NULL } }; char Helvetica14b[MAX_FONT_TAG]; if (PtInit(NULL) == -1) PtExit(EXIT_FAILURE); dim.w = 200; dim.h = 100; PtSetArg(&args[0], Pt_ARG_DIM, &dim, 0); if ((window = PtCreateWidget(PtWindow, Pt_NO_PARENT, 1, args)) == NULL) PtExit(EXIT_FAILURE); callbacks[0].data = window; n = 0; PtSetArg(&args[n++], Pt_ARG_TEXT_STRING, "Count Down...", 0); /* Use 14-point, bold Helvetica if it's available. */ if(PfGenerateFontName("Helvetica", PF_STYLE_BOLD, 14, Helvetica14b) == NULL) { perror("Unable to generate font name"); } else { PtSetArg(&args[n++], Pt_ARG_TEXT_FONT, Helvetica14b, 0); } PtSetArg(&args[n++], Pt_CB_ACTIVATE, callbacks, sizeof(callbacks)/sizeof(PtCallback_t)); PtCreateWidget(PtButton, window, n, args); PtRealizeWidget(window); PtMainLoop(); return (EXIT_SUCCESS); }
If your modal dialog is self-contained and you just need to wait for it, you might find this function useful:
![]() |
![]() |
![]() |
![]() |