same callback is called when the user presses RETURN in the widget or when the user selects the OK button. This
convenience can confuse an unsuspecting programmer who may find that his callback is being invoked twice. It is
also possible to overestimate what the Motif toolkit is going to do and expect a callback to be invoked when it isn't.
The point is to be sure to verify that these callbacks are getting called at the appropriate times. See Chapter 6,
Selection Dialogs, for examples of this feature in SelectionDialogs, PromptDialogs, and CommandDialogs.
15.5.2 Text Modification Callbacks
In this section, we discuss the callback routines that can be used to monitor and control text modification. Monitoring
occurs both when the user types into a Text widget and when the text is changed using a convenience routine such as
XmTextInsert(). These callbacks work for both single−line and multiline Text widgets, as well as TextField
widgets. Since the text in a widget is modified by each keystroke, the modification callbacks are invoked quite
frequently.
There are two callbacks for text modification: XmNmodifyVerifyCallback is called before the text is modified,
and XmNvalueChangedCallback is called after the text has been changed. Depending on the needs of an
application, either or both callbacks may be used on the same widget. You should never call XtVaSetValues() in
one of these callbacks on the widget that is being modified because the state of the widget is unstable during these
callbacks. Avoid adding or deleting callbacks or changing resources, especially the XmNvalue resource, in a callback
routine. If a recursive loop occurs, you may get very unpredictable results.
Installing an XmNmodifyVerifyCallback function is useful when you need to monitor or change the user's input
before it actually gets inserted into a Text widget. In the source code we demonstrate using this callback to convert
text to uppercase. XtSetLanguageProc() is only available in X11R5; there is no corresponding function in
X11R4.
/* allcaps.c −− demonstrate the XmNmodifyVerifyCallback for
* Text widgets by using one to convert all typed input to
* capital letters.
*/
#include <Xm/Text.h>
#include <Xm/LabelG.h>
#include <Xm/RowColumn.h>
#include <ctype.h>
void allcaps();
main(argc, argv)
int argc;
char *argv[];
{
Widget toplevel, text_w, rowcol;
XtAppContext app;
XtSetLanguageProc (NULL, NULL, NULL);
toplevel = XtVaAppInitialize (&app, "Demos",
NULL, 0, &argc, argv, NULL, NULL);
rowcol = XtVaCreateWidget ("rowcol",
xmRowColumnWidgetClass, toplevel,
XmNorientation, XmHORIZONTAL,
NULL);
XtVaCreateManagedWidget ("Enter Text:",
xmLabelGadgetClass, rowcol, NULL);
15 Text Widgets 15.5.2 Text Modification Callbacks
415
text_w = XtVaCreateManagedWidget ("text_w",
xmTextWidgetClass, rowcol, NULL);
XtAddCallback (text_w, XmNmodifyVerifyCallback, allcaps, NULL);
XtManageChild (rowcol);
XtRealizeWidget (toplevel);
XtAppMainLoop (app);
}
/* allcaps() −− convert inserted text to capital letters. */
void
allcaps(text_w, client_data, call_data)
Widget text_w;
XtPointer client_data;
XtPointer call_data;
{
int len;
XmTextVerifyCallbackStruct *cbs =
(XmTextVerifyCallbackStruct *) call_data;
if (cbs−>text−>ptr == NULL)
return;
/* convert all input to upper−case if necessary */
for (len = 0; len < cbs−>text−>length; len++)
if (islower (cbs−>text−>ptr[len]))
cbs−>text−>ptr[len] = toupper (cbs−>text−>ptr[len]);
}
The program creates a RowColumn widget that contains a Label and a Text widget, as shown in the figure.
Output of allcaps.c
The Text widget uses the allcaps() routine as its XmNmodifyVerifyCallback function. The routine is
actually quite simple, but there are a lot of details to examine. The call_data parameter to the function is of type
XmTextVerifyCallbackStruct. This data structure provides information about the modification that may be
done to the text. The data structure is defined as follows:
typedef struct {
int reason;
XEvent *event;
Boolean doit;
XmTextPosition currInsert, newInsert;
XmTextPosition startPos, endPos;
XmTextBlock text;
} XmTextVerifyCallbackStruct;
15 Text Widgets 15.5.2 Text Modification Callbacks
416
With an XmNmodifyVerifyCallback, the reason field has the value XmCR_MODIFYING_TEXT_VALUE.
The event field contains the XEvent that caused the callback to be invoked; this field is NULL if the modification is
being done by a convenience routine that modifies the text. The values for currInsert and newInsert are
always the same for a modification callback. These fields specify the location of the insertion cursor, so they are only
different for the XmNmotionVerifyCallback when the user moves the insertion point.
The values for startPos and endPos indicate the range of text that is affected by the modification. For insertion,
these values are always the same. However, for text deletion or replacement, the values specify the beginning and end
of the text about to be deleted. For example, if the user selects some text and presses the BACKSPACE key, the
startPos and endPos values indicate the boundaries of the text about to be deleted. We discuss text deletion in
detail in an upcoming section.
The text field points to a data structure that describes the text about to be added to the widget. The field is a pointer
of type XmTextBlock, which is defined as follows:
typedef struct {
char *ptr;
int length;
XmTextFormat format;
} XmTextBlockRec, *XmTextBlock;
The text being added is accessible through ptr; it is dynamically allocated using XtMalloc() for each callback
invocation. The ptr field is not NULL−terminated, so you should not use strlen() or strcpy() to copy the
data. The length is stored in the length field, so if you want to copy the text, you should use strncpy(). If the
user is deleting text, length is 0. While ptr should also be NULL in this case, the field isn't always set this way, so
you shouldn't rely on it. The format field specifies the width of the text characters and can have the value FMT8BIT
or FMT16BIT.
Let's review the simple case of adding new text, as demonstrated in the source code When new text is inserted into the
Text widget, the values for currInsert, newInsert, startPos, and endPos all have the same value, which is
the position in the widget where the new text will be added. Since the new text has not yet been added to the value of
the widget, the application can change the value of ptr in the text block. In the allcaps() routine, we modify the
input to be all capital letters by looping through the valid bytes in the ptr field of the text block that is going to be
added, as shown in the following fragment:
for (len = 0; len < cbs−>text−>length; len++)
if (islower (cbs−>text−>ptr[len]))
cbs−>text−>ptr[len] = toupper (cbs−>text−>ptr[len]);
The islower() and toupper() macros are found in the <ctype.h> header file.
Since allcaps() is called each time new text is added to the widget, you might wonder how length can ever be
more than one. If the user pastes a block of text into the widget, the entire block is added at once, so ptr points to that
text, and length specifies the amount of text. Our loop handles both single−character typing and text−block paste
operations. the source code demonstrates how an application can modify the text that is entered by a user before it is
displayed. An application may also want to filter the new text and prevent certain characters from being inserted. The
easiest way to prevent a text modification is to set the doit field in the XmTextVerifyCallbackStruct to
False. When the modification callback routine returns, the Text widget checks this field. If it has been set to False,
the widget discards the new text, and the widget is left unmodified.
When a text modification is vetoed, the Text widget can sound the console bell to provide audio feedback informing
the user that the input has been rejected. This action is dependent on the value of the XmNverifyBell resource. The
15 Text Widgets 15.5.2 Text Modification Callbacks
417
default value is based on the value of the XmNaudibleWarning resource of the VendorShell, so it is set to True
by default. You should allow a user to set this resource in a resource file, so he can turn off error notification if he
doesn't want it. If you hard−code the resource value, users cannot control this feature. You should provide
documentation with your application that explains how to set this resource or provide a way to set the value from the
application.
the source code demonstrates a modification callback routine that filters input and prevents certain characters from
being entered. The check_zip() routine would be used as the XmNmodifyVerifyCallback for a Text widget
that prompts for a ZIP code. We want the user to type only digits; all other input should be ignored. We also want to
keep the user from typing a string that is longer than five digits.
/* check_zip() −− limit the user to entering a ZIP code. */
void
check_zip(text_w, client_data, call_data)
Widget text_w;
XtPointer client_data;
XtPointer call_data;
{
XmTextVerifyCallbackStruct *cbs =
(XmTextVerifyCallbackStruct *) call_data;
int len = XmTextGetLastPosition (text_w);
if (cbs−>startPos < cbs−>currInsert) /* backspace */
return;
if (len == 5) {
cbs−>doit = False;
return;
}
/* check that the new additions won't put us over 5 */
if (len + cbs−>text−>length > 5) {
cbs−>text−>ptr[5 − len] = 0;
cbs−>text−>length = strlen (cbs−>text−>ptr);
}
for (len = 0; len < cbs−>text−>length; len++) {
/* make sure all additions are digits. */
if (!isdigit (cbs−>text−>ptr[len])) {
/* not a digit−− move all chars down one and
* decrement cbs−>text−>length.
*/
int i;
for (i = len; (i+1) < cbs−>text−>length; i++)
cbs−>text−>ptr[i] = cbs−>text−>ptr[i+1];
cbs−>text−>length−−;
len−−;
}
}
if (cbs−>text−>length == 0)
cbs−>doit = False;
}
The first thing we do in check_zip() is to see if the user is backspacing, in which case we simply return. If text is
not being deleted, then new text is definitely being added. Since the length of the current text is not available in the
callback structure, we call XmTextGetLastPosition() to determine it. If the string is already five digits long,
we don't want to add more digits, so we set doit to False and return.
Otherwise, we loop through the length of the new text and check for characters that are not digits. If any exist, we
15 Text Widgets 15.5.2 Text Modification Callbacks
418
remove them by shifting all of the characters that follow down one place, overwriting the undesirable character. If we
loop through all of the characters and find that none of them are digits, the length ends up being zero, so we set doit
to False. A modification callback can determine if the user is backspacing or deleting a large block of text by
checking to see if startPos is less than currInsert. Alternatively, the routine could check to see if
text−>length is 0. For backspacing, the values differ by one. If the user selects a large block of text and deletes
the selection, the XmNmodifyVerifyCallback is invoked once to delete the text and may be invoked a second
time if the user has typed new text to replace the selected text.
Our next example program demonstrates how to process character deletions in a text modification callback. the source
code creates a single−line Text widget that prompts the user for a password. We don't provide any encryption for the
password; we simply mask what the user is typing by displaying an asterisk (*) for each character. The actual text is
stored in a separate internal variable. The challenge for this application is to capture the input text, store it internally,
and modify the output, even for backspacing. XtSetLanguageProc() is only available in X11R5; there is no
corresponding function in X11R4.
/* password.c −− prompt for a password. All input looks like
* a series of *'s. Store the actual data typed by the user in
* an internal variable. Don't allow paste operations. Handle
* backspacing by deleting all text from insertion point to the
* end of text.
*/
#include <Xm/Text.h>
#include <Xm/LabelG.h>
#include <Xm/RowColumn.h>
#include <ctype.h>
void check_passwd();
char *passwd; /* store user−typed passwd here. */
main(argc, argv)
int argc;
char *argv[];
{
Widget toplevel, text_w, rowcol;
XtAppContext app;
XtSetLanguageProc (NULL, NULL, NULL);
toplevel = XtVaAppInitialize (&app, "Demos",
NULL, 0, &argc, argv, NULL, NULL);
rowcol = XtVaCreateWidget ("rowcol",
xmRowColumnWidgetClass, toplevel,
XmNorientation, XmHORIZONTAL,
NULL);
XtVaCreateManagedWidget ("Password:",
xmLabelGadgetClass, rowcol, NULL);
text_w = XtVaCreateManagedWidget ("text_w",
xmTextWidgetClass, rowcol, NULL);
XtAddCallback(text_w, XmNmodifyVerifyCallback, check_passwd, NULL);
XtAddCallback(text_w, XmNactivateCallback, check_passwd, NULL);
XtManageChild (rowcol);
XtRealizeWidget (toplevel);
XtAppMainLoop (app);
}
15 Text Widgets 15.5.2 Text Modification Callbacks
419

Get Volume 6A: Motif Programming Manual now with O’Reilly online learning.

O’Reilly members experience live online training, plus books, videos, and digital content from 200+ publishers.