This chapter explains how the Text and TextField
widgets can be used to provide text-entry capabilities in an
application. These widgets can be used for a variety of purposes, from
a simple data-entry field to a full-fledged text editor. The chapter
describes the selection mechanisms provided by the widgets and how they
can be used to communicate with other applications via the clipboard.
The widgets also allow the programmer to control the format of the data
that is entered by the user.
Despite all that can be done with menus, buttons,
and lists, there are times when the user can best interact with an
application by typing at the keyboard. The Text widget is usually the
best choice for providing this style of interface, as it provides
full-featured text editing capabilities. The Text widget can be used
anywhere the user might be expected to type free-form text, such as in
a compose window in a mail application. Unlike standard text editors,
the Text widget supports the point-and-click model that people expect
from GUI-based applications. The TextField widget provides a
single-line data entry field with the same set of editing commands as
the Text widget, but it requires less overhead. Text widgets can also
be used in output-only mode to display more textual information than is
practical with a label or a button.
Even though the text widgets allow for complex
interaction, they still provide simple mechanisms for program control.
The widgets have resources that access the text, as well as control
their behavior. They also provide callback routines that allow an
application to intervene on actions that add text, delete text, or move
the insertion cursor. The widgets support keyboard management methods
that control the editing style, paging style, character positioning,
and line-wrapping. There are also convenience routines that enable
quick and simple access to the clipboard.
The text widgets do have their limitations. For
example, they do not support multiple colors or fonts, so a single
widget can only use one color and one font. There is no support for
text formatting such as paragraph specifications, automatic line
numbering, or indentation, so you cannot create WYSIWYG documents.
WYSIWYG stands for What You See Is What You Get. This term is
used to describe page formatting programs that can produce camera-ready
documents that match what is displayed on the screen. The Text widget
is not a terminal emulator; it cannot be used to run interactive
programs. The widgets cannot display multi-media objects either, which
means that it is not possible to insert graphics into the text stream.
There are some cases where a text widget is not the
most appropriate user-interface element, even though you are displaying
text. For example, a Text widget should not be used to display a list
whose items can be individually selected; that is the job of the List
widget. Text that cannot be edited, or selected should be displayed in
a Label widget. Chapter 11, Labels and Buttons, and Chapter 12,
The List Widget, describe the appropriate uses of these components.
If you have not used the Motif Text widget, you should familiarize yourself with one before getting too involved in this chapter. Running some of our introductory examples should provide an adequate platform for experimentation. the figure shows an application that uses several Text widgets. Two widgets are used for single-line data entry. The widget with the ScrollBars attached to it is used for editing multiple lines.
The Text widget supports both single-line and
multiline editing. In single-line mode, which is the default mode,
newlines are ignored. However, single-line text entry is usually done
with the TextField widget class. This widget class is a completely
separate class, not a subclass, of Text that is lighter-weight because
it only supports single-line text editing. The TextField widget was
added to the toolkit in Motif 1.1; in early versions of that release
the widget had a number of bugs that made it difficult to use. These
bugs have been fixed in later releases. Although they are two separate
widget classes, the Text and TextField widgets provide many of the same
resources and convenience routines. We will point out the differences
as we go, but keep in mind that there are two widget classes so you
don't get confused as we discuss them throughout this chapter.
Since the TextField widget cannot handle multiline
editing, you must use the Text widget for this purpose. When multiple
lines are used for editing, the number of lines typically grows and
shrinks dynamically as the user edits the text. The Text widget is
often used in a scrollable window, so that the user can view different
portions of the underlying text. The combination of a Text widget and a
ScrolledWindow widget is called a ScrolledText object. This object is
not a widget class, although there is a convenience routine,
XmCreateScrolledText(), that allows you to create both widgets at
once.
The Text and TextField widgets are highly
configurable in terms of appearance and behavior. Given the level of
sophistication for both the programmer and the user, the widgets should
not be taken lightly or underestimated. The ease of configurability
should not tempt you to enforce your personal ideas about how a text
editor should work. The best thing to do with text widgets is configure
them as minimally as possible to suit the needs of your program. You
should let the user have control over as many details of their display
and operation as possible. This laissez-faire approach ensures that
your application is more compatible with other Motif programs.
The user interface for the text widgets follows the
point-and-click model of interaction. The insertion cursor indicates
the position where the next character that is typed will be inserted.
In Motif 1.2, the insertion position is marked by an I-beam cursor.
Using the left mouse button, the user can click on a new location in
the widget to move the insertion cursor there, so text may be inserted
at any location in the widget.
In Motif 1.1, the text widgets used two different
cursors. The I-beam was used to mark the insertion position, while a
caret (^) was used as the destination cursor when it was
separate from the insertion cursor. The destination cursor showed the
last position that text was inserted, edited, or selected. Having two
separate cursors was confusing for users and programmers, so the model
has been simplified for Motif 1.2 to use only the I-beam cursor.
The text widgets have predefined action routines
that allow the user to perform simple editing operations such as moving
one character to the right or deleting backwards to the beginning of
the line. The user can specify translations in a resource file that
modify the input behavior of the widgets. The widgets are modeless, so
they are always in text-insertion mode. In Motif 1.2, there is an
action that puts the Text widget in overstrike mode, while in Motif
1.1, it is programmatically possible to emulate such a mode using
multiple action routines.
The user can use the action routines provided by the
widgets to set up the translation table to mimic an editor such as
emacs. The Text widget does not insert nonprintable characters, so
users typically bind control-character sequences to editing action
routines. An editor like vi cannot be emulated because there is
no distinction between command mode and text-entry mode.
Users have become accustomed to the ability to cut
and paste text between windows in GUI-based applications. Cut and paste
is more difficult for the programmer to implement with the X Window
System than a system where a single vendor controls all of the
variables, because the nature of X requires a more general solution .
For example, applications running on the same display may actually be
executing on different systems; these systems may have different byte
orders or other differences in the underlying data format. Currently,
only text selections are implemented, which makes byte order
irrelevant. However, the mechanism is designed to allow transparent
transfer of any kind of data. In order to insulate cut and paste
operations from dependencies like these, all communication between
applications is implemented via the X server. Data that is cut is
stored in a property on the X server. A property is simply a
named piece of data associated with a window and stored on the server.
The Interclient Communications Conventions Manual
Reprinted as Appendix L in Volume Zero, X Protocol Reference Manual
. (ICCCM) defines a set of standard property names to be used for
operations such as cut and paste and lays out rules for how
applications should interact with these properties. According to the
ICCCM, text that is selected is typically stored in the PRIMARY
property. The SECONDARY property is defined as an alternate storage
area for use by applications that wish to support more than one
simultaneous selection operation or that wish to support operations
requiring two selections, such as switching the contents of the two
selections. The CLIPBOARD property is defined as a longer-term holding
area for data that is actually cut (rather than simply copied) from the
application's window. When we refer to the primary, secondary, or
clipboard selection, we mean the property of the same name.
The most common implementation of the selection
mechanism is provided by the X Toolkit Intrinsics. The low-level
routines that are used to implement selections are described in detail
in Volume Four, X Toolkit Intrinsics Programming Manual. In
general, applications such as xterm and widgets such as the
Motif Text widget encapsulate this functionality in action routines
that are invoked by the user with mouse button or key combinations.
The user can select text in a Motif Text widget by
pressing the left mouse button and dragging the pointer across the
text. The selected text is displayed in reverse video. When the button
is released, the text widget has ownership of the selection, but no
text is copied. The selection can be extended either by pressing the
SHIFT key and then dragging the pointer with the left mouse button
down, or by pressing any of the arrow keys while holding down the SHIFT
key. In addition to the click-and-drag technique for text selection,
the Text widget also supports multiple-clicking techniques:
double-clicking selects a word, triple-clicking selects the current
line, and quadruple-clicking selects all of the text in the widget. An
important constraint imposed by the ICCCM is that only one window may
own a selection property at one time, which means that once the user
makes another primary selection, the original selection is lost.
The user can copy text directly from the primary
selection into the Text widget by clicking the middle mouse button at
the location where the text is to be inserted. This action is sometimes
called stuffing the selection into the widget. The user can stuff text
at any location in the text stream, as long as the location is not
inside the current selection. The text is copied only when the middle
mouse button is clicked, which is defined as a quick succession of
press and release actions. The operation does not take place simply
because the middle mouse button is pressed, as this action is used for
drag and drop operations.
In Motif 1.2, the Text and TextField widgets support
the drag-and-drop model of transferring textual data. Once text has
been selected in a widget, the selection can be dragged by pressing the
middle mouse button over the selection and dragging the pointer. The
text is transferred when the user releases the middle mouse button with
the pointer over another location in the same widget or over another
text widget. By default, the text is moved, which means that the
original text is deleted once the transfer is complete. The user can
force a copy operation by holding down the CONTROL key while dragging
the pointer and releasing the mouse button. For more information on
drag and drop, see Chapter 18, Drag and Drop.
The secondary selection is used by the Motif text
widgets to copy text directly within a widget. The user performs this
type of operation by first selecting the location where the copied text
is to be placed; clicking the left mouse button places the insertion
point. Then the text that is to be copied is selected by pressing and
dragging the middle mouse button while the ALT key is pressed. The
selected text is underlined rather than highlighted in reverse video.
When the button is released, the selected text is immediately stuffed
at the location of the insertion cursor. Unlike the primary selection,
which may be retrieved many times, the secondary selection is immediate
and can only be stuffed once.
The third location for holding text is the clipboard
selection. The clipboard selection is designed to be used as a
longer-term storage area for data. For example, MIT provides a client
called xclipboard that asserts ownership of the CLIPBOARD
property and provides a user interface to it. xclipboard not
only allows a selection to survive the termination of the window where
the data was originally selected, but it also allows for the storage of
multiple selections. The user can view all of the selections before
deciding which one to paste.
OSF's implementation of the clipboard is
incompatible with xclipboard. If xclipboard is running,
any Motif routines that attempt to store data on the clipboard will not
succeed. The Motif routines temporarily try to lock the clipboard, and
xclipboard will not give up its own lock. Motif treats the
clipboard as a two-item cache. Only Motif applications that use the
clipboard routines described in Chapter 17, The Clipboard, can
interoperate using this selection. The advantage of the Motif
implementation is that it provides functionality far beyond that
provided by the standard MIT clients. With xterm and the Athena
widgets, selections can really only be used for copy-and-paste
operations; the selected text is unchanged. The Motif Text widget, by
contrast, allows you to cut, copy, clear, or type over a selection.
While there is a translation and action-based interface defined for
these operations, it is typically not implemented.
As described in Chapter 2, The Motif Programming
Model, Motif defines translations in terms of virtual key bindings.
By default, the virtual keys osfCut, osfCopy, osfPaste
, et. al., are not bound to any actual keys. If a user wants to
use these keys, he must specify the bindings in a .motifbind
file in his home directory. The interface for these features is usually
provided by menu items associated with the Text widget, as we will
demonstrate in this chapter.
When text is selected in a Text widget, it is
automatically stored in the primary selection. When one of the Text
widget functions, such as XmTextCut(), is used, the text is
also stored in the clipboard selection. Most users will be completely
unaware that there are separate holding areas for selected text. If
your application gets heavily into cutting and pasting, you may find
that the fusion of the primary and clipboard selections in the
convenience routines is confusing. You should be careful to implement
the selection operations so that the different properties are
transparent to the user.
The reference pages for the Text and TextField
widgets (in Volume Six B, Motif Reference Manual; Section 2,
Motif and Xt Widget Classes) lists the default translations for the
widgets. See Volume Four, X Toolkit Intrinsics Programming Manual
, for a description of how to programmatically alter translation tables;
see Volume Three, X Window System User's Guide, for a
description of how a user can customize widget translations. See
Chapter 17, The Clipboard, for a discussion of the lower-level
Motif clipboard functions.
In order to understand the complexities of the Text
and TextField widgets, you need to know about some of the basic
resources and functions that they provide. This section describes the
fundamentals of working with text widgets, including how to create the
widgets, how to work with the textual data, and how to control simple
aspects of appearance and behavior. Applications that wish to use the
Text widget need to include the file <Xm/Text.h>. TextField
widgets require the file <Xm/TextF.h>. You can create a Text
widget using XtVaCreateManagedWidget() as usual:
Widget text_w; text_w = XtVaCreateManagedWidget("name", xmTextWidgetClass, parent, resource-value-list, NULL);To create a TextField widget instead, specify the class as xmTextFieldWidgetClass.
The XmNvalue resource of the Text and
TextField widgets provides the most basic means of access to the
internal text storage for the widgets. Unlike the other widgets in the
Motif toolkit that use text, the text widgets do not use compound
strings for their values. Instead, the value is specified as a regular
C string, as shown in the source code XtSetLanguageProc() is
only available in X11R5; there is no corresponding function in X11R4.
/* simple_text.c -- Create a minimally configured Text widget */ #include <Xm/Text.h> main(argc, argv) int argc; char *argv[]; { Widget toplevel; XtAppContext app; XtSetLanguageProc (NULL, NULL, NULL); toplevel = XtVaAppInitialize (&app, "Demos", NULL, 0, &argc, argv, NULL, NULL); XtVaCreateManagedWidget ("text", xmTextWidgetClass, toplevel, XmNvalue, "Now is the time...", NULL); XtRealizeWidget (toplevel); XtAppMainLoop (app); }This short program simply creates a Text widget with the initial value shown in the figure.
In Motif 1.2, both widgets also provide the
XmNvalueWcs resource for storing a wide-character representation of
the text value. For more information on using the text widgets in an
internationalized application, see Section #stexti18n. The initial
value of the XmNvalue resource may be set either when the
widget is created or by using XtVaSetValues() after the widget
has been created. The value of the resource always represents the
entire text of the widget. You can also use a Motif convenience
routine, XmTextSetString(), to set the text value. This
routine takes the following form:
void XmTextSetString(text_w, value) Widget text_w; char *value;This routine works for both Text and TextField widgets. The TextField widget has a corresponding routine, XmTextFieldSetString(), but it only works for TextField widgets. If you are using both types of text widgets in an application, we recommend using the Text widget routines to manipulate all of the widgets. Since these routines work with both types of widgets, you don't need to keep track of the widget types.
Although the convenience routine and
XtVaSetValues() produce the same results, the convenience routine
may be more efficient since it accesses the internals of the widget
directly, while the XtVaSetValues() method involves going
through Xt. On the other hand, if you are setting a number of resources
at the same time, the XtVaSetValues() method is better because
all of the resources can be set in a single function call. Whichever
function you use, the text value is copied into the internals of the
widget, and the displayed value is changed accordingly.
If, for whatever reason, you are making multiple
changes in a short period of time to the text in a Text widget, you may
have problems with visual flashing in the widget. With Motif 1.2, you
can solve this problem by calling XmTextDisableRedisplay() to
turn off visual updating in the widget. After the call, the appearance
of the widget remains unchanged until XmTextEnableRedisplay()
is called. You can access the textual data in a Text widget using
XtVaGetValues() or XmTextGetString().
XmTextGetString() allocates enough space (using XtMalloc()
) for all of the text in the widget and returns a pointer to the
allocated data. You can modify the returned data any way you like, and
then you must free it using XtFree() when you are done. The
code fragment below demonstrates the use of XmTextGetString():
char *text; if (text = XmTextGetString (text_w)) { /* manipulate text in whatever way is necessary */ ... /* free text or there will be a memory leak */ XtFree (text); }XmTextGetString() works with both Text and TextField widgets, while the corresponding TextField routine, XmTextFieldGetString() , only works with TextField widgets. In Motif 1.2, you can also use XmTextGetSubstring() to get a copy of a portion of the text in a Text widget.
The alternative to XmTextGetString() is the
Xt function XtVaGetValues(). The Text widget responds to
XtVaGetValues() by allocating memory and returning a copy of the
text. As a result, this data must be freed after use. This use of the
GetValues() method is different from most other resources. For most
resources, XtVaGetValues() returns a pointer to internal data
that should be treated as read-only data. In order to avoid memory
leaks, you need to be sure to free the memory that is allocated by
XtVaGetValues() for the XmNvalue resource, as shown in the
following code fragment:
char *text; XtVaGetValues (text_w, XmNvalue, &text, NULL); /* manipulate text in whatever way is necessary */ ... /* free text or there will be a memory leak */ XtFree (text);
Getting the value of a Text widget can be an
expensive operation if the widget contains a large amount of text. In
all situations, whenever text is retrieved from the Text widget with
any function, the length of time the data is valid is only guaranteed
until the next Xt call into the same Text widget; what any particular
call might do to the internal text stream is undefined, and that
information will not be reflected in the current character pointer
handle you may have.
A Text widget may contain an arbitrarily large
amount of text, assuming that there is enough memory on the computer
running the client application. The text for a widget is not stored on
the X server; only the client computer stores widget-specific
information. The server displays a bitmap rendition of what the Text
widget chooses to show. The XmNmaxLength resource specifies
the upper limit on the number of characters the user can type into a
Text widget. The default value of this resource is the largest integer
for the particular system, so it is likely that the user's computer
will run out of memory before the Text widget's maximum capacity is
reached. You can lower the value of the resource to limit the number of
characters that the user can input to a particular Text widget.
The Text widget does not use a temporary file to
store its data. All of the data resides in memory on the machine, so
you cannot use a Text widget to browse or edit a file directly.
Instead, you load the contents of a file into a Text widget and allow
the user to edit the internal buffer. The application controls when to
rewrite files with updated data. An application can also provide an
interface that allows the user to control this action. Applications
that use Text widgets to edit vital information should make provisions
for data recovery if the system fails or the application terminates
unexpectedly. The Text widget does not support this type of recovery.
In the source code the Text widget provides a
single-line text entry area that is 20 columns wide; it is shown in the
figure. Both the single-line editing style and the width are default
values. The width of each column is based on the font that is used for
the text. Since the widget uses the single-line editing style, nothing
happens when the user presses RETURN in the widget. If the user types
more text than the widget can display, the text scrolls to the left.
Since newlines are not interpreted when they are typed by the user,
textual data is always a single line. It is possible to set
XmNvalue to a string that contains newline characters in a
single-line Text widget, but the interaction with the user is
undefined, and the widget produces confusing behavior. The user can
resize the widget to make it appear large enough to display multiple
lines, but this action does not affect the operation of the widget or
the way it handles input.
Multiline editing allows the user to enter newlines
into a Text widget and provides the capability to edit a large amount
of text. The switch from single-line to multiline causes a number of
changes in the behavior of the widget. For example, now widget geometry
must be considered in order to determine the amount of text that is
visible at one time. The Text widget may need to be placed in a
ScrolledWindow, so that the user can view all of the text.
Single or multiline editing is controlled through
the XmNeditMode resource. The value of the resource can be
either XmSINGLE_LINE_EDIT or XmMULTI_LINE_EDIT. While
the two editing modes are quite different in concept, it should be
quite intuitive when to use the different modes. Single-line text entry
areas are commonly used to prompt for file and directory names, short
phrases, or single words. They are also useful for command-line entry
in applications that were originally based on a tty-style interface.
Multiline editing is used for editing files or other large quantities
of text.
The layout of a multiline Text widget can be
difficult to manage, especially if the text is editable by the user. An
application needs to decide how many lines of text are displayed, how
to handle the layout when the user adds new text, and how to deal with
resizing the Text widget. The easiest way to manage an editable
multiline Text widget is to create it as part of a ScrolledText
compound object. The ScrolledText object is not a widget class in and
of itself, but rather a compound object that is composed of a Text
widget and a ScrolledWindow widget.
When you create a ScrolledText object, the
ScrolledWindow automatically handles scrolling the text in the Text
widget. Basically, the two widget classes have hooks and procedures
that allow them to cooperate intelligently with each other. As of Motif
1.2, the performance of the ScrolledText object has improved
considerably. One unfortunate side-effect of the performance
improvement is that subclasses of the Text widget may not work under
Motif 1.2, due to the addition of a new data structure. In previous
releases, scrolling operations could be quite slow when the Text widget
contained a large amount of text.
You can create a ScrolledText object using the Motif
convenience routine XmCreateScrolledText(), which takes the
following form:
Widget XmCreateScrolledText(parent, name, arglist, argcount) Widget parent; char *name; ArgList arglist; Cardinal argcount;This routine is not a variable-argument list function; it uses the argument-list style of setting resources with the XtSetArg() macro.
XmCreateScrolledText() creates a
ScrolledWindow widget and a Text widget as its child. The routine
returns a handle to the Text widget; you can get a handle to the
ScrolledWindow using the function XtParent(). When you are
laying out an application that uses ScrolledText objects, you should be
sure to use XtParent() to get the ScrolledWindow widget, since
that is the widget that you need to position.
For purposes of specifying resources, the
ScrolledWindow takes the name of the Text widget with the suffix SW
. For example, if the name of the Text widget is name, its
ScrolledWindow parent widget has the name nameSW.
If you specify an argument list in a call to
XmCreateScrolledText(), the resources are set for the Text widget
or the ScrolledWindow as appropriate. The routine also sets some
resources for the ScrolledWindow so that scrolling is handled
automatically. You should be sure to set the XmNeditMode
resource to XmMULTI_LINE_EDIT, since it doesn't make sense to
have a single-line Text widget in a ScrolledWindow. If you don't set
the resource, the Text widget defaults to single-line editing mode. The
behavior of a single-line Text widget (or a TextField widget) in a
ScrolledWindow is undefined.
XmCreateScrolledText() is adequate for most
situations, but you can also create the two widgets separately, as
shown in the following code fragment:
Widget scrolled_w, text_w; scrolled_w = XtVaCreateManagedWidget ("scrolled_w", xmScrolledWindowWidgetClass, parent, XmNscrollingPolicy, XmAPPLICATION_DEFINED, XmNvisualPolicy, XmVARIABLE, XmNscrollBarDisplayPolicy, XmSTATIC, XmNshadowThickness, 0, NULL); text_w = XtVaCreateManagedWidget ("text", xmTextWidgetClass, scrolled_w, XmNeditMode, XmMULTI_LINE_EDIT, ... NULL);We create the ScrolledWindow widget with the same resource setting that the Motif function uses. Since we are creating the ScrolledWindow ourselves, we can give it our own name. The Text widget itself is created as a child of the ScrolledWindow. In this situation, it is clear that the parent of the ScrolledWindow controls the position of both of the widgets.
This creation method makes the programmer
responsible for managing both of the widgets. You may also need to
handle the case in which the widgets are destroyed. When you call
XmCreateScrolledText(), the routine installs an
XmNdestroyCallback on the Text widget that destroys the
ScrolledWindow parent. When you create the widgets yourself, you also
need to be sure that they are destroyed together, either by destroying
them explicitly or installing a callback routine on the Text widget.
Unless you are creating and destroying ScrolledText objects
dynamically, this issue should not be a concern.
the source code shows a simple file browser that
displays the contents of a file using a ScrolledText object. The user
can specify a file by typing a filename in the TextField widget below
the Filename: prompt. The user can also select a file from the
FileSelectionDialog that is popped up by the Open entry on the
File menu. The specified file is displayed immediately in the Text
widget. XtSetLanguageProc() is only available in X11R5; there
is no corresponding function in X11R4. XmStringCreateLocalized()
is only available in Motif 1.2; XmStringCreateSimple() is the
corresponding function in Motif 1.1. XmFONTLIST_DEFAULT_TAG
replaces XmSTRING_DEFAULT_CHARSET in Motif 1.2.
/* file_browser.c -- use a ScrolledText object to view the * contents of arbitrary files chosen by the user from a * FileSelectionDialog or from a single-line text widget. */ #include <X11/Xos.h> #include <Xm/Text.h> #include <Xm/TextF.h> #include <Xm/FileSB.h> #include <Xm/MainW.h> #include <Xm/RowColumn.h> #include <Xm/LabelG.h> #include <sys/types.h> #include <sys/stat.h> #include <stdio.h> main(argc, argv) int argc; char *argv[]; { Widget top, main_w, menubar, menu, rc, text_w, file_w; XtAppContext app; XmString file, open, exit; extern void read_file(), file_cb(); Arg args[10]; int n; XtSetLanguageProc (NULL, NULL, NULL); /* initialize toolkit and create toplevel shell */ top = XtVaAppInitialize (&app, "Demos", NULL, 0, &argc, argv, NULL, NULL); /* MainWindow for the application -- contains menubar * and ScrolledText/Prompt/TextField as WorkWindow. */ main_w = XtVaCreateManagedWidget ("main_w", xmMainWindowWidgetClass, top, NULL); /* Create a simple MenuBar that contains one menu */ file = XmStringCreateLocalized ("File"); menubar = XmVaCreateSimpleMenuBar (main_w, "menubar", XmVaCASCADEBUTTON, file, 'F', NULL); XmStringFree (file); /* Menu is "File" -- callback is file_cb() */ open = XmStringCreateLocalized ("Open..."); exit = XmStringCreateLocalized ("Exit"); menu = XmVaCreateSimplePulldownMenu (menubar, "file_menu", 0, file_cb, XmVaPUSHBUTTON, open, 'O', NULL, NULL, XmVaSEPARATOR, XmVaPUSHBUTTON, exit, 'x', NULL, NULL, NULL); XmStringFree (open); XmStringFree (exit); /* Menubar is done -- manage it */ XtManageChild (menubar); rc = XtVaCreateWidget ("work_area", xmRowColumnWidgetClass, main_w, NULL); XtVaCreateManagedWidget ("Filename:", xmLabelGadgetClass, rc, XmNalignment, XmALIGNMENT_BEGINNING, NULL); file_w = XtVaCreateManagedWidget ("text_field", xmTextFieldWidgetClass, rc, NULL); /* Create ScrolledText -- this is work area for the MainWindow */ n = 0; XtSetArg(args[n], XmNrows, 12); n++; XtSetArg(args[n], XmNcolumns, 70); n++; XtSetArg(args[n], XmNeditable, False); n++; XtSetArg(args[n], XmNeditMode, XmMULTI_LINE_EDIT); n++; XtSetArg(args[n], XmNcursorPositionVisible, False); n++; text_w = XmCreateScrolledText (rc, "text_w", args, n); XtManageChild (text_w); /* store text_w as user data in "File" menu for file_cb() callback */ XtVaSetValues (menu, XmNuserData, text_w, NULL); /* add callback for TextField widget passing "text_w" as client data */ XtAddCallback (file_w, XmNactivateCallback, read_file, text_w); XtManageChild (rc); /* Store the filename text widget to ScrolledText object */ XtVaSetValues (text_w, XmNuserData, file_w, NULL); XmMainWindowSetAreas (main_w, menubar, NULL, NULL, NULL, rc); XtRealizeWidget (top); XtAppMainLoop (app); } /* file_cb() -- "File" menu item was selected so popup a * FileSelectionDialog. */ void file_cb(widget, client_data, call_data) Widget widget; XtPointer client_data; XtPointer call_data; { static Widget dialog; Widget text_w; extern void read_file(); int item_no = (int) client_data; if (item_no == 1) exit (0); /* user chose Exit */ if (!dialog) { Widget menu = XtParent (widget); dialog = XmCreateFileSelectionDialog (menu, "file_sb", NULL, 0); /* Get the text widget handle stored as "user data" in File menu */ XtVaGetValues (menu, XmNuserData, &text_w, NULL); XtAddCallback (dialog, XmNokCallback, read_file, text_w); XtAddCallback (dialog, XmNcancelCallback, XtUnmanageChild, NULL); } XtManageChild (dialog); XtPopup (XtParent (dialog), XtGrabNone); XMapRaised (XtDisplay (dialog), XtWindow (XtParent (dialog))); } /* read_file() -- callback routine when the user selects OK in the * FileSelection Dialog or presses Return in the single-line text widget. * The specified file must be a regular file and readable. * If so, it's contents are displayed in the text_w provided as the * client_data to this function. */ void read_file(widget, client_data, call_data) Widget widget; /* file selection box or text field widget */ XtPointer client_data; XtPointer call_data; { char *filename, *text; struct stat statb; FILE *fp; Widget file_w; Widget text_w = (Widget) client_data; XmFileSelectionBoxCallbackStruct *cbs = (XmFileSelectionBoxCallbackStruct *) call_data; if (XtIsSubclass (widget, xmTextFieldWidgetClass)) { filename = XmTextFieldGetString (widget); file_w = widget; /* this *is* the file_w */ } else { /* file was selected from FileSelectionDialog */ XmStringGetLtoR (cbs->value, XmFONTLIST_DEFAULT_TAG, &filename); /* the user data stored the file_w widget in the text_w */ XtVaGetValues (text_w, XmNuserData, &file_w, NULL); } if (!filename || !*filename) { /* nothing typed? */ if (filename) XtFree (filename); return; } /* make sure the file is a regular text file and open it */ if (stat (filename, &statb) == -1 || (statb.st_mode & S_IFMT) != S_IFREG || !(fp = fopen(filename, "r"))) { if ((statb.st_mode & S_IFMT) == S_IFREG) perror (filename); /* send to stderr why we can't read it */ else fprintf (stderr, "%s: not a regular file0, filename); XtFree (filename); return; } /* put the contents of the file in the Text widget by allocating * enough space for the entire file, reading the file into the * allocated space, and using XmTextFieldSetString() to show the file. */ if (!(text = XtMalloc ((unsigned)(statb.st_size + 1)))) { fprintf (stderr, "Can't alloc enough space for %s", filename); XtFree (filename); fclose (fp); return; } if (!fread (text, sizeof (char), statb.st_size + 1, fp)) fprintf (stderr, "Warning: may not have read entire file!0); text[statb.st_size] = 0; /* be sure to NULL-terminate */ /* insert file contents in Text widget */ XmTextSetString (text_w, text); /* make sure text field is up to date */ if (file_w != widget) { /* only necessary if activated from FileSelectionDialog */ XmTextFieldSetString (file_w, filename); XmTextFieldSetCursorPosition (file_w, strlen(filename)); } /* free all allocated space and */ XtFree (text); XtFree (filename); fclose (fp); }The output of the program is shown in the figure.
We use the convenience routine
XmCreateScrolledText() to create a ScrolledText area. We specify
that the Text widget displays 12 lines by 70 columns of text by setting
the XmNrows and XmNcolumns resources. These settings
are used only at initialization. Once the application is up and
running, the user can resize the window and effectively change those
dimensions.
The XmNeditable resource is set to
False to prevent the user from editing the contents of the Text
widget. Since we do not provide a way to write changes back to the
file, we don't want to mislead the user into thinking that the file is
editable. Since a noneditable Text widget should not display an
insertion cursor, we remove it by setting XmNcursorPositionVisible
to False.
The FileSelectionDialog is created and managed when
the user selects the Open button from the File menu. The
user can exit the program by selecting the Exit button from this
menu. The read_file() routine is activated when the user
presses the OK button in the FileSelectionDialog or enters
RETURN in the TextField widget. This function gets the specified file
and checks its type. If the file chosen is not a regular file (e.g., if
it is a directory, device, tty, etc.) or if it cannot be opened, an
error is reported and the function simply returns. If you are
unfamiliar with the use of the stat() system call, or any other
aspect of UNIX programming used in examples in this book, a good source
of information is the Nutshell Handbook Using C on the UNIX System
, by Dave Curry (O'Reilly & Associates, 1988).
Assuming that the file checks out, its contents are
placed in the Text widget. Rather than loading the file by reading each
line using a function like fgets(), we allocate enough space
to contain the entire file and read it all in with one call to
fread(). The text is then loaded into the Text widget using
XmTextSetString(). The ScrollBars are updated automatically and the
text is positioned so that the beginning of the file is displayed. In
file_browser.c, the ScrolledText object has two ScrollBars that are
installed automatically. The vertical ScrollBar is needed in case the
text exceeds 12 lines; the horizontal ScrollBar is needed in case any
of those lines are wider than 70 columns. Most users are accustomed to
having Text windows be a fixed width (typically 80 columns), especially
if they have ever used an ASCII terminal. However, it can be annoying
to have text that is scrollable in the horizontal direction, since you
need to see the entire line to read smoothly through a page of text.
The XmNscrollHorizontal resource controls
whether or not a horizontal ScrollBar is displayed. If the resource is
set to False, the ScrollBar is not displayed, but that does
not stop text from being displayed beyond the visible area. In order to
have text wrap appropriately, the XmNwordWrap resource must be
set to True. When this resource is set, the Text widget breaks
lines at spaces, tabs, and newlines. While line breaking is fine for
previewing files and other output-only Text widgets, you should not
enforce such a policy for Text widgets that are used for text editing,
as the user may want to edit wide files.
The XmNscrollVertical resource controls
whether or not a vertical ScrollBar is displayed. This resource
defaults to True when a Text widget is created as a child of a
ScrolledWindow. The XmNscrollLeftSide and XmNscrollTopSide
resources take Boolean values that control the location of the
ScrollBars within the ScrolledWindow. By default, XmNscrollTopSide
is set to False, which causes the ScrollBar to be placed below
the ScrolledWindow. The default value of XmNscrollLeftSide
depends upon the value of XmNstringDirection. These two
resources should not be set by the application, but left to users to
specify themselves. The XmNresizeWidth and XmNresizeHeight
resources control whether or not a Text widget should resize itself
vertically or horizontally in order to display the entire text stream.
Both of the resources default to False. If XmNresizeWidth
is set to True and new text is added such that the number of
columns needs to grow, the width of the widget grows to contain the new
text. Similarly, if XmNresizeHeight is set to True
and the number of lines increases, the height of the widget increases
so that it can display all of the lines. These resources have no effect
in a ScrolledText object, since the ScrollBars are managing the
widget's size. Also, if line breaking is active, XmNresizeWidth
has no effect.
In most cases, it is not appropriate to set these
resources, as it is regarded as poor user-interface design to have a
Text widget that dynamically resizes as the text is being edited. It is
also impolite for a window to resize itself except as the result of an
explicit user action. One example of an acceptable use of these
resources involves using a Text widget to display text for a help
dialog. In this situation, the Text widget can resize itself silently
before it is mapped to the screen, so that by the time it is visible,
its size is constant.
A position in a Text widget specifies the
number of characters from the beginning of the text in the widget,
where the first character position is defined as zero (0). All
whitespace and newline characters are considered part of the text and
are counted as single characters. For example, in the figure, the
insertion cursor in the TextField widget is at position 14. When the
user types in a Text widget, the new text is always added at the
position of the insertion cursor and the insertion cursor is advanced.
If the user does not move the cursor, it is always positioned at the
end of the text in the widget.
You can set the position of the insertion cursor
explicitly using XmTextSetInsertionPosition(), which takes the
following form:
void XmTextSetInsertionPosition(text_w, position) Widget text_w; XmTextPosition position;This function is identical to XmTextSetCursorPosition(). The XmTextPosition type is a long value, so it can represent all of the positions in a Text widget. You can get the current cursor position using XmTextGetInsertionPosition() or XmTextGetCursorPosition(). As with most of the Text widget functions, there are corresponding TextField functions for setting and getting the position of the insertion cursor. The TextField routines only work with TextField widgets, while the Text routines work with both Text and TextField widgets.
the source code shows an application that uses these
routines as part of a search operation. The program searches the Text
widget for a specified pattern and then positions the insertion cursor
so that the pattern is displayed. XtSetLanguageProc() is only
available in X11R5; there is no corresponding function in X11R4.
/* search_text.c -- demonstrate how to position a cursor at a * particular location. The position is determined by a pattern * match search. */ #include <Xm/Text.h> #include <Xm/TextF.h> #include <Xm/LabelG.h> #include <Xm/RowColumn.h> #include <X11/Xos.h> /* for the index() function */ Widget text_w, search_w, text_output; main(argc, argv) int argc; char *argv[]; { Widget toplevel, rowcol_v, rowcol_h; XtAppContext app; int i, n; void search_text(); Arg args[10]; XtSetLanguageProc (NULL, NULL, NULL); toplevel = XtVaAppInitialize (&app, "Demos", NULL, 0, &argc, argv, NULL, NULL); rowcol_v = XtVaCreateWidget ("rowcol_v", xmRowColumnWidgetClass, toplevel, NULL); rowcol_h = XtVaCreateWidget ("rowcol_h", xmRowColumnWidgetClass, rowcol_v, XmNorientation, XmHORIZONTAL, NULL); XtVaCreateManagedWidget ("Search Pattern:", xmLabelGadgetClass, rowcol_h, NULL); search_w = XtVaCreateManagedWidget ("search_text", xmTextFieldWidgetClass, rowcol_h, NULL); XtManageChild (rowcol_h); text_output = XtVaCreateManagedWidget ("text_output", xmTextWidgetClass, rowcol_v, XmNeditable, False, XmNcursorPositionVisible, False, XmNshadowThickness, 0, XmNhighlightThickness, 0, NULL); n = 0; XtSetArg (args[n], XmNrows, 10); n++; XtSetArg (args[n], XmNcolumns, 80); n++; XtSetArg (args[n], XmNeditMode, XmMULTI_LINE_EDIT); n++; XtSetArg (args[n], XmNscrollHorizontal, False); n++; XtSetArg (args[n], XmNwordWrap, True); n++; text_w = XmCreateScrolledText (rowcol_v, "text_w", args, n); XtManageChild (text_w); XtAddCallback (search_w, XmNactivateCallback, search_text, NULL); XtManageChild (rowcol_v); XtRealizeWidget (toplevel); XtAppMainLoop (app); } /* search_text() -- called when the user activates the TextField. */ void search_text(widget, client_data, call_data) Widget widget; XtPointer client_data; XtPointer call_data; { char *search_pat, *p, *string, buf[32]; XmTextPosition pos; int len; Boolean found = False; /* get the text that is about to be searched */ if (!(string = XmTextGetString (text_w)) || !*string) { XmTextSetString (text_output, "No text to search."); XtFree (string); /* may have been ""; free it */ return; } /* get the pattern we're going to search for in the text. */ if (!(search_pat = XmTextGetString (search_w)) || !*search_pat) { XmTextSetString (text_output, "Specify a search pattern."); XtFree (string); /* this we know is a string; free it */ XtFree (search_pat); /* this may be "", XtFree() checks.. */ return; } len = strlen (search_pat); /* start searching at current cursor position + 1 to find * the -next- occurrance of string. we may be sitting on it. */ pos = XmTextGetCursorPosition (text_w); for (p = &string[pos+1]; p = index (p, *search_pat); p++) if (!strncmp (p, search_pat, len)) { found = True; break; } if (!found) { /* didn't find pattern? */ /* search from beginning till we've passed "pos" */ for (p = string; (p = index (p, *search_pat)) && p - string <= pos; p++) if (!strncmp (p, search_pat, len)) { found = True; break; } } if (!found) XmTextSetString (text_output, "Pattern not found."); else { pos = (XmTextPosition)(p - string); sprintf (buf, "Pattern found at position %ld.", pos); XmTextSetString (text_output, buf); XmTextSetInsertionPosition (text_w, pos); } XtFree (string); XtFree (search_pat); }In this example, the user can search for strings in a ScrolledText, as shown in the figure.
This program doesn't provide a way to load a file,
so if you want to experiment, you need to type or paste some text into
the widget. Once there is some text in the widget, type a string
pattern in the Search Pattern TextField widget and press RETURN
to activate the search. The text is searched starting at the position
immediately following the current cursor position. If the search
routine reaches the end of the text before it finds the pattern, it
resumes searching from the beginning of the text and continues until it
finds the pattern or reaches the cursor position. If the routine finds
the pattern, it moves the insertion point to that location using
XmTextSetInsertionPosition(). Otherwise, the routine prints an
error message and does not move the cursor.
The search_text() routine shown in the
source code searches the text using various string routines. In Motif
1.2, there is a new Text routine that provides the same functionality.
XmTextFindString() searches a Text widget for a specified string.
This routine takes the following form:
Boolean XmTextFindString(text_w, start, string, direction, position) Widget text_w; XmTextPosition start; char *string; XmTextDirection direction; XmTextPosition *position;The start argument specifies the starting position for the search, while direction indicates whether the routine searches forward or backward in the text. This parameter can have the value XmTEXT_FORWARD or XmTEXT_BACKWARD. The routine returns True if it finds the string, and in this case, the position parameter returns the position where the string starts in the text. If the string is not found, the routine returns False, and the value of position is undefined. It is easy to rewrite search_text() to take advantage of XmTextFindString(). In Section #stexteditor, we implement a full text editor and use XmTextFindString() to handle the various search operations.
The text_output widget in search_text.c
is also a Text widget, even though it looks more like a Label widget.
By setting XmNshadowThickness to 0 and
XmNeditable to False, we create the Text widget that
doesn't look like a normal Text widget, and the user cannot edit the
text. We demonstrate this technique not to advocate such usage, but to
point out the versatility of this widget class.
If you paste a large amount of text into the main
Text widget and search repeatedly for a common pattern, you should
notice that the Text widget may scroll automatically to make the
specified text visible. This action is controlled by the
XmNautoShowCursorPosition resource. This resource has a default
value of True, which means that the Text widget adjusts the
visible text to make sure that the cursor is always visible. When the
resource is set to False, the widget does not scroll to
compensate for the cursor's invisibility. This resource also works in
single-line Text widgets and TextField widgets; these widgets may
scroll their displays horizontally to display the insertion cursor.
It is easy to scroll a Text widget to a particular
position in the text stream by setting the cursor position and then
calling XmTextShowPosition(). This routine takes the following
form:
void XmTextShowPosition(text_w, position) Widget text_w; XmTextPosition position;To scroll to the end of the text, you need to scroll to the last position, which can be retrieved using XmTextGetLastPosition() . It is also possible to perform relative scrolling using the function XmTextScroll(), which takes the following form:
void XmTextScroll(text_w, lines) Widget text_w; int lines;A positive value for lines causes a Text widget to scroll upward by that many lines, while a negative value causes downward scrolling. The Text widget does not have to be a child of ScrolledWindow for this routine to work; the widget simply adjusts the viewable text.
Now that we have a routine that searches for text,
the next logical step is to implement a function that performs a
search-and-replace operation. Motif makes this task fairly easy by
providing the XmTextReplace() routine, which takes the
following form:
void XmTextReplace(text_w, from_pos, to_pos, value) Widget text_w; XmTextPosition from_pos; XmTextPosition to_pos; char *value;This function identifies the text to be replaced in the Text widget starting at the position from_pos and ending at, but not including, the position to_pos. This text is replaced by the text in value. If value is NULL or an empty string, the text between the two positions is simply deleted. If you want to remove all of the text from the widget, call XmTextSetString() with a NULL string as the text value.
To add search-and-replace functionality to the
program in the source code we need to add a new TextField widget that
prompts for the replacement text and provide a callback routine for the
widget. the source code shows the additional code that is necessary.
Widget text_w, search_w, replace_w, text_output; main(argc, argv) int argc; char *argv[]; { ... replace_w = XtVaCreateManagedWidget ("replace_text", xmTextFieldWidgetClass, rowcol_h, NULL); XtAddCallback (replace_w, XmNactivateCallback, search_and_replace, NULL); ... } void search_and_replace(widget, client_data, call_data) Widget widget; XtPointer client_data; XtPointer call_data; { char *search_pat, *p, *string, *new_pat, buf[32]; XmTextPosition pos; int search_len, pattern_len; int nfound = 0; string = XmTextGetString (text_w); if (!*string) { XmTextSetString (text_output, "No text to search."); XtFree (string); return; } search_pat = XmTextGetString (search_w); if (!*search_pat) { XmTextSetString (text_output, "Specify a search pattern."); XtFree (string); XtFree (search_pat); return; } new_pat = XmTextGetString (replace_w); search_len = strlen (search_pat); pattern_len = strlen (new_pat); /* start at beginning and search entire Text widget */ for (p = string; p = index (p, *search_pat); p++) if (!strncmp (p, search_pat, search_len)) { nfound++; /* get the position where pattern was found */ pos = (XmTextPosition)(p-string); /* replace the text from our position + strlen (new_pat) */ XmTextReplace (text_w, pos, pos + search_len, new_pat); /* "string" has changed -- we must get the new version */ XtFree (string); /* free the one we had first... */ string = XmTextGetString (text_w); /* continue search for next pattern -after- replacement */ p = &string[pos + pattern_len]; } if (!nfound) strcpy (buf, "Pattern not found."); else sprintf (buf, "Made %d replacements.", nfound); XmTextSetString (text_output, buf); XtFree (string); XtFree (search_pat); XtFree (new_pat); }In this routine, the pattern search starts at the beginning of the text and searches all of the text in the widget. We are not interested in the cursor position and do not attempt to move it. The main loop of the function only needs to find the specified pattern and replace each occurrence with the new text. After each call to XmTextReplace() , we reread the text, since the old value is no longer valid. As with the search_text() routine, we could easily use XmTextFindString() to search for the pattern, as we do in the text editor in Section #stexteditor.
The Text and TextField widgets can be used in an
output-only mode by setting the XmNeditable resource to
False. If the user tries to edit the text in a read-only widget,
the widget beeps and does not allow the modification. We used an
output-only Text widget in our file browsing application.
Our next example addresses a common need for many
developers: a method for displaying text messages while an application
is running. These messages may include status messages about
application actions, as well as error messages from Xlib, Xt, and
functions internal to the application. The message area is an important
part of the main window of many applications, as discussed in
Chapter 4, The Main Window. While a message area can be
implemented using a Label widget, an output-only ScrolledText object
is better suited for use as a message area because the user can scroll
back to previous messages.
the source code shows the wprint() function
that we wrote to handle displaying messages. The function acts like
printf() in that it takes variable arguments and understands the
standard string formatting characters. The output goes to a
ScrolledText widget so the user can review previous messages. All new
text is appended to the end of the output, so it is immediately visible
and the user does not have to manually scroll to the end of the
display.
#include <stdio.h> #include <varargs.h> /* or <stdarg.h> */ /* global variable */ Widget text_output; main(argc, argv) int argc; char *argv[]; { Arg args[10]; int n; ... /* Create output_text as a ScrolledText window */ n = 0; XtSetArg(args[n], XmNrows, 6); n++; XtSetArg(args[n], XmNcolumns, 80); n++; XtSetArg(args[n], XmNeditable, False); n++; XtSetArg(args[n], XmNeditMode, XmMULTI_LINE_EDIT); n++; XtSetArg(args[n], XmNwordWrap, True); n++; XtSetArg(args[n], XmNscrollHorizontal, False); n++; XtSetArg(args[n], XmNcursorPositionVisible, False); n++; text_output = XmCreateScrolledText(rowcol, "text_output", args, n); XtManageChild (text_output); ... } /*VARARGS*/ void wprint(va_alist) va_dcl { char msgbuf[256]; char *fmt; static XmTextPosition wpr_position; va_list args; va_start (args); fmt = va_arg (args, char *); #ifndef NO_VPRINTF (void) vsprintf (msgbuf, fmt, args); #else /* !NO_VPRINTF */ { FILE foo; foo._cnt = 256; foo._base = foo._ptr = msgbuf; /* (unsigned char *) ?? */ foo._flag = _IOWRT+_IOSTRG; (void) _doprnt (fmt, args, &foo); *foo._ptr = ' '; /* plant terminating null character */ } #endif /* NO_VPRINTF */ va_end (args); XmTextInsert (text_output, wpr_position, msgbuf); wpr_position = wpr_position + strlen (msgbuf); XtVaSetValues (text_output, XmNcursorPosition, wpr_position, NULL); XmTextShowPosition (text_output, wpr_position); }Since the wprint() function acts like printf(), it takes a variable-length argument list, which requires the inclusion of either <varargs.h> or <stdarg.h>. If you have access to the source code for X, you could include <X11/VarargsI.h> instead. This file is used by the X Toolkit whenever variable-length argument lists are used; it includes the appropriate file for the current operating system. The function wprint() takes va_alist as its only parameter. This argument is a pointer to the first of a list of arguments passed to the function; it is declared as va_dcl in accordance with the standards for functions that take variable-length argument lists.
The va_start() and va_arg() macros
are used to extract the first parameter from the argument list. Since
wprint() is supposed to act like printf(), we know that
the first parameter is going to be a char pointer. The call to
va_arg() causes fmt to point to the format string,
which may or may not contain % formatting characters that
expand to other strings depending on the other arguments to the
function.
The rest of the arguments are read and parsed by
either vsprintf() or _doprnt(), depending on the C
library that you are using. vsprintf() is a varargs version of
sprintf() that exists on most modern UNIX machines. System V
has vsprintf(), as does SunOS, but Ultrix and older
BSD machines typically use _doprnt(). If your machine does not
have vsprintf(), you can use _doprnt() as shown in
the source code Both of these functions consume all of the arguments in
the list and leave the result in msgbuf.
Now that we have the complete string in msgbuf
, we can append it to the existing text in the Text widget. We keep
track of the end of text_output with wpr_position.
Each time msgbuf is concatenated to the end of the text, the
value of wpr_position is incremented appropriately. The new
text is added using the convenience routine XmTextInsert(),
which takes the following form:
void XmTextInsert(text_w, position, string) Widget text_w; XmTextPosition position; char *string;The function simply inserts the given text at the specified position. Finally, we call XmTextShowPosition() to make the end position visible within the Text widget. This routine may cause the Text widget to adjust its text so that the new text is visible, as a convenience to the user so that he does not have to scroll the window to view new messages.
The routines in the source code show how
wprint() can be used to reset the error handling functions for Xlib
and Xt so that the messages are printed in a Text widget rather than to
stderr.
extern void wprint(); static void x_error(dpy, err_event) Display *dpy; XErrorEvent *err_event; { char buf[256]; XGetErrorText (dpy, err_event->error_code, buf, (sizeof buf)); wprint("X Error: <%s>0, buf); } static void xt_error(message) char *message; { wprint ("Xt Error: %s0, message); } main(argc, argv) int argc; char *argv[]; { XtAppContext app; ... /* catch Xt errors */ XtAppSetErrorHandler (app, xt_error); XtAppSetWarningHandler (app, xt_error); /* and Xlib errors */ XSetErrorHandler (x_error); ... }Using XtAppSetErrorHandler(), XtAppSetWarningHandler() , and XSetErrorHandler(), we send all X-related error messages to a Text widget through wprint(). You can also use wprint() to send any application-specific messages to the ScrolledText area.
Both the Text widget and the TextField widget have
convenience routines that support communication with the clipboard.
Using these functions, you can implement the standard cut, copy, and
paste functionality, as well as support communication with other
windows or applications on the desktop. If you are not familiar with
the clipboard and how it works, see Chapter 17, The Clipboard.
Briefly, the clipboard is one of three transient locations where
arbitrary data such as text can be stored so that other windows or
applications can copy the data. For the Text widget, we are only
interested in copying textual data and providing visual feedback within
the widget. The Text widget can send and receive data from all three of
the locations, depending on the interface style that you are using.
As described earlier in this chapter, the user
typically selects text by pressing the first mouse button and dragging
the pointer across the text. When text is selected, it is rendered in
reverse video and automatically copied into the primary selection. Now
the user can paste text from the primary selection into any Text widget
on the desktop by pressing the middle mouse button. The insertion
cursor is moved to the location of the button press, and the data is
automatically copied into the Text widget at this position. This
functionality works by default within the Text widget. However, the
actions operate on the primary selection, not the clipboard selection.
Furthermore, the actions only allow you to copy data to and from the
selection, not cut it or clear it.
To provide these features, most applications provide
other user-interface controls, such as a PulldownMenu and appropriate
menu items, that call Text widget clipboard routines. These routines
store text on the clipboard. They also allow the user to move text
between the clipboard and the primary selection, as well as between
windows that are interested only in the clipboard selection. Typical
menu entries include Cut, Copy, Paste, and
Clear. the source code demonstrates these common editing actions.
The application creates a MenuBar with an Edit PulldownMenu that
contains actions that operate on the Text widget.
XtSetLanguageProc() is only available in X11R5; there is no
corresponding function in X11R4. XmStringCreateLocalized() is
only available in Motif 1.2; XmStringCreateSimple() is the
corresponding function in Motif 1.1.
/* cut_paste.c -- demonstrate the text functions that handle * clipboard operations. These functions are convenience routines * that relieve the programmer of the need to use clipboard functions. * The functionality of these routines already exists in the Text * widget, yet it is common to place such features in the interface * via the MenuBar's "Edit" pulldown menu. */ #include <Xm/Text.h> #include <Xm/LabelG.h> #include <Xm/PushBG.h> #include <Xm/RowColumn.h> #include <Xm/MainW.h> Widget text_w, text_output; main(argc, argv) int argc; char *argv[]; { Widget toplevel, main_w, menubar, rowcol_v; XtAppContext app; void cut_paste(); XmString label, cut, clear, copy, paste; Arg args[10]; int n; XtSetLanguageProc (NULL, NULL, NULL); toplevel = XtVaAppInitialize (&app, "Demos", NULL, 0, &argc, argv, NULL, NULL); main_w = XtVaCreateWidget ("main_w", xmMainWindowWidgetClass, toplevel, NULL); /* Create a simple MenuBar that contains a single menu */ label = XmStringCreateLocalized ("Edit"); menubar = XmVaCreateSimpleMenuBar (main_w, "menubar", XmVaCASCADEBUTTON, label, 'E', NULL); XmStringFree (label); cut = XmStringCreateLocalized ("Cut"); /* create a simple */ copy = XmStringCreateLocalized ("Copy"); /* pulldown menu that */ clear = XmStringCreateLocalized ("Clear"); /* has these menu */ paste = XmStringCreateLocalized ("Paste"); /* items in it. */ XmVaCreateSimplePulldownMenu (menubar, "edit_menu", 0, cut_paste, XmVaPUSHBUTTON, cut, 't', NULL, NULL, XmVaPUSHBUTTON, copy, 'C', NULL, NULL, XmVaPUSHBUTTON, paste, 'P', NULL, NULL, XmVaSEPARATOR, XmVaPUSHBUTTON, clear, 'l', NULL, NULL, NULL); XmStringFree (cut); XmStringFree (clear); XmStringFree (copy); XmStringFree (paste); XtManageChild (menubar); /* create a standard vertical RowColumn... */ rowcol_v = XtVaCreateWidget ("rowcol_v", xmRowColumnWidgetClass, main_w, NULL); text_output = XtVaCreateManagedWidget ("text_output", xmTextWidgetClass, rowcol_v, XmNeditable, False, XmNcursorPositionVisible, False, XmNshadowThickness, 0, XmNhighlightThickness, 0, NULL); n = 0; XtSetArg (args[n], XmNrows, 10); n++; XtSetArg (args[n], XmNcolumns, 80); n++; XtSetArg (args[n], XmNeditMode, XmMULTI_LINE_EDIT); n++; XtSetArg (args[n], XmNscrollHorizontal, False); n++; XtSetArg (args[n], XmNwordWrap, True); n++; text_w = XmCreateScrolledText (rowcol_v, "text_w", args, n); XtManageChild (text_w); XtManageChild (rowcol_v); XtManageChild (main_w); XtRealizeWidget (toplevel); XtAppMainLoop (app); } /* cut_paste() -- the callback routine for the items in the edit menu */ void cut_paste(widget, client_data, call_data) Widget widget; XtPointer client_data; XtPointer call_data; { Boolean result = True; int reason = (int) client_data; XEvent *event = ((XmPushButtonCallbackStruct *) call_data)->event; Time when; XmTextSetString (text_output, NULL); /* clear message area */ if (event != NULL) { switch (event->type) { case ButtonRelease : when = event->xbutton.time; break; case KeyRelease : when = event->xkey.time; break; default: when = CurrentTime; break; } } switch (reason) { case 0 : result = XmTextCut (text_w, when); break; case 1 : result = XmTextCopy (text_w, when); break; case 2 : result = XmTextPaste (text_w); case 3 : XmTextClearSelection (text_w, when); break; } if (result == False) XmTextSetString (text_output, "There is no selection."); else XmTextSetString (text_output, NULL); }
The application creates a MainWindow widget, so that it can contain the MenuBar. The MenuBar and the PulldownMenu are created using their respective convenience routines, as described in Chapter 4, The Main Window, and Chapter 15, Menus. The output of the program is shown in the figure.
Again, you need to enter some text or paste it from
another window if you want to experiment with this application. The
main window contains the same Text widgets used in previous examples.
The Edit PulldownMmenu allows the user to interact with the
clipboard. The cut_paste() routine is the callback function
for all of the menu items in the Edit menu. This function uses
four Text convenience routines to work with the clipboard:
XmTextCut(), XmTextCopy(), XmTextPaste(), and
XmTextClearSelection(). These routines take the following form:
Boolean XmTextCut(text_w, time) Widget text_w; Time time;
Boolean XmTextCopy(text_w, time) Widget text_w; Time time;
Boolean XmTextPaste(text_w) Widget text_w;
void XmTextClearSelection(text_w, time) Widget text_w; Time time;
XmTextCopy() copies the text that is
selected in the Text widget and places it on the clipboard.
XmTextCut() is similar to XmTextCopy(), except that the
Text widget that owns the selection is instructed to delete the text
once it has been copied to the clipboard. The deletion is handled by
sending a DELETE protocol request to the window holding the selection.
This protocol is not the same as the WM_DELETE protocol, which
indicates that a window is being deleted. See Chapter 16,
Interacting With the Window Manager, for more information on window
manager protocols. The time parameters should not be
set to CurrentTime to avoid race conditions with other
clipboard operations that may be occurring at the same time. Since the
clipboard routines are called by menu item callback routines, you can
use the time field of the XEvent that is passed in
the callback structure, as we do in the source code Both
XmTextCopy() and XmTextCut() return True if the
operation succeeds. False may be returned if there is no
selected text or an error occurs in attempting to communicate with the
clipboard.
XmTextPaste() gets the current selection
from the clipboard and inserts it at the location of the insertion
cursor. If there is some selected text in the Text widget, that text is
replaced by the selection from the clipboard. XmTextPaste()
returns True if there is a selection on the clipboard that can
be retrieved.
XmTextClearSelection() deselects the text
selection in the Text widget. If there is no selected text, nothing
happens. The routine does not provide any feedback or return any value.
Any text that is held on the clipboard or in a selection property
remains.
One additional convenience routine that operates on
the selection is XmTextRemove(). This function is like
XmTextCut(), in that it removes the selected text from a Text
widget, but it does not place the text on the clipboard.
You can get the selected text from a Text widget
using XmTextGetSelection(), which takes the following form:
char * XmTextGetSelection(text_w) Widget text_w;This routine returns allocated data that contains the selected text. This text must be freed using XtFree() when you are through using it. The routine returns NULL if there is no text selected in the Text widget.
XmTextGetSelectionPosition() provides
information about the selected text in a Text widget. This routine
takes the following form:
Boolean XmTextGetSelectionPosition(text_w, left, right) Widget text_w; XmTextPosition *left; XmTextPosition *right;If XmTextGetSelectionPosition() returns True, the values for left and right specify the boundaries of the selected text. If the routine returns False, the widget does not contain any selected text, and the values for left and right are undefined.
The Text widget supports multi-clicking techniques
for selecting increasingly large chunks of text. The default
multi-clicking actions in the Text widget are shown in tab(@),
linesize(2); l | l l | l. User Action@Text Widget Action
_
Single click@Resets insertion cursor to position Double
click@Selects a word (bounded by whitespace) Triple click@Selects a
line (bounded by newlines) Quadruple click@Selects all of the text
_ These default actions can be modified using the
XmNselectionArray and XmNselectionArrayCount resources.
The XmNselectionArray resource specifies an array of
XmTextScanType values, where XmTextScanType is an
enumerated type defined as follows:
typedef enum { XmSELECT_POSITION, XmSELECT_WHITESPACE, XmSELECT_WORD, XmSELECT_LINE, XmSELECT_PARAGRAPH XmSELECT_ALL, } XmTextScanType;XmSELECT_WHITESPACE works in the same way as XmSELECT_WORD . Each successive button click in a Text widget selects the text according to the corresponding item in the array. The default array is defined as follows:
static XmTextScanType sarray[] = { XmSELECT_POSITION, XmSELECT_WORD, XmSELECT_LINE, XmSELECT_ALL };You should keep the items in the array in ascending order, so as not to confuse the user. The following code fragment shows an acceptable change to the array:
static XmTextScanType sarray[] = { XmSELECT_POSITION, XmSELECT_WORD, XmSELECT_LINE, XmSELECT_PARAGRAPH, XmSELECT_ALL }; ... XtVaSetValues (text_w, XmNselectionArray, selectionArray, XmNselectionArrayCount, 5, NULL);
The maximum time interval between button clicks in a
multi-click action is specified by the multiClickTime
resource. This resource is maintained by the X server and set for all
applications; it is not a Motif resource. The value of the resource can
be retrieved using XtGetMultiClickTime() and changed with
XtSetMultiClickTime(). For more discussion on this value, see
Chapter 11, Labels and Buttons.
The XmNselectThreshold resource can be used
to modify the behavior of click-and-drag actions. This resource
specifies the number of pixels that the user must move the pointer
before a character can be selected. The default value is 5,
which means that the user must move the mouse at least 5 pixels before
the Text widget decides whether or not to select a character. This
threshold is used throughout a selection operation to determine when
characters are added or deleted from the selection. If you are using an
extremely large font, you may want to increase the value of this
resource to cut down on the number of calculations that are necessary
to determine if a character should be added or deleted from the
selection.
Before we describe the Text widget callback routines, we are going
to present an example that combines all the information covered so far.
The example is a full-featured text editor built from the examples
presented so far in this chapter. You should recognize most of the code
in the example; the code that you don't recognize should be
understandable from the context in which it is used. The output of the
program is shown in the figure; the code is shown in the source code
XtSetLanguageProc() is only available in X11R5; there is no
corresponding function in X11R4. XmStringCreateLocalized() is
only available in Motif 1.2; XmStringCreateSimple() is the
corresponding function in Motif 1.1. XmFONTLIST_DEFAULT_TAG
replaces XmSTRING_DEFAULT_CHARSET in Motif 1.2.
XmTextFindString() is only available in Motif 1.2; there is no
corresponding function in Motif 1.1, so you have to implement your own
search capabilities.
/* editor.c -- create a full-blown Motif editor application complete * with a menubar, facilities to read and write files, text search * and replace, clipboard support and so forth. */ #include <Xm/Text.h> #include <Xm/TextF.h> #include <Xm/LabelG.h> #include <Xm/PushBG.h> #include <Xm/RowColumn.h> #include <Xm/MainW.h> #include <Xm/Form.h> #include <Xm/FileSB.h> #include <X11/Xos.h> #include <stdio.h> #include <sys/types.h> #include <sys/stat.h> Widget text_edit, search_text, replace_text, text_output; #define FILE_OPEN 0 #define FILE_SAVE 1 #define FILE_EXIT 2 #define EDIT_CUT 0 #define EDIT_COPY 1 #define EDIT_PASTE 2 #define EDIT_CLEAR 3 #define SEARCH_FIND_NEXT 0 #define SEARCH_SHOW_ALL 1 #define SEARCH_REPLACE 2 #define SEARCH_CLEAR 3 main(argc, argv) int argc; char *argv[]; { XtAppContext app_context; Widget toplevel, main_window, menubar, form, search_panel; void file_cb(), edit_cb(), search_cb(); Arg args[10]; int n = 0; XmString open, save, exit, exit_acc, file, edit, cut, clear, copy, paste, search, next, find, replace; XtSetLanguageProc (NULL, NULL, NULL); toplevel = XtVaAppInitialize (&app_context, "Demos", NULL, 0, &argc, argv, NULL, NULL); XmRepTypeInstallTearOffModelConverter (); main_window = XtVaCreateWidget ("main_window", xmMainWindowWidgetClass, toplevel, NULL); /* Create a simple MenuBar that contains three menus */ file = XmStringCreateLocalized ("File"); edit = XmStringCreateLocalized ("Edit"); search = XmStringCreateLocalized ("Search"); menubar = XmVaCreateSimpleMenuBar (main_window, "menubar", XmVaCASCADEBUTTON, file, 'F', XmVaCASCADEBUTTON, edit, 'E', XmVaCASCADEBUTTON, search, 'S', NULL); XmStringFree (file); XmStringFree (edit); XmStringFree (search); /* First menu is the File menu -- callback is file_cb() */ open = XmStringCreateLocalized ("Open..."); save = XmStringCreateLocalized ("Save..."); exit = XmStringCreateLocalized ("Exit"); exit_acc = XmStringCreateLocalized ("Ctrl+C"); XmVaCreateSimplePulldownMenu (menubar, "file_menu", 0, file_cb, XmVaPUSHBUTTON, open, 'O', NULL, NULL, XmVaPUSHBUTTON, save, 'S', NULL, NULL, XmVaSEPARATOR, XmVaPUSHBUTTON, exit, 'x', "Ctrl<Key>c", exit_acc, NULL); XmStringFree (open); XmStringFree (save); XmStringFree (exit); XmStringFree (exit_acc); /* ...create the "Edit" menu -- callback is edit_cb() */ cut = XmStringCreateLocalized ("Cut"); copy = XmStringCreateLocalized ("Copy"); clear = XmStringCreateLocalized ("Clear"); paste = XmStringCreateLocalized ("Paste"); XmVaCreateSimplePulldownMenu (menubar, "edit_menu", 1, edit_cb, XmVaPUSHBUTTON, cut, 't', NULL, NULL, XmVaPUSHBUTTON, copy, 'C', NULL, NULL, XmVaPUSHBUTTON, paste, 'P', NULL, NULL, XmVaSEPARATOR, XmVaPUSHBUTTON, clear, 'l', NULL, NULL, NULL); XmStringFree (cut); XmStringFree (copy); XmStringFree (paste); /* create the "Search" menu -- callback is search_cb() */ next = XmStringCreateLocalized ("Find Next"); find = XmStringCreateLocalized ("Show All"); replace = XmStringCreateLocalized ("Replace Text"); XmVaCreateSimplePulldownMenu (menubar, "search_menu", 2, search_cb, XmVaPUSHBUTTON, next, 'N', NULL, NULL, XmVaPUSHBUTTON, find, 'A', NULL, NULL, XmVaPUSHBUTTON, replace, 'R', NULL, NULL, XmVaSEPARATOR, XmVaPUSHBUTTON, clear, 'C', NULL, NULL, NULL); XmStringFree (next); XmStringFree (find); XmStringFree (replace); XmStringFree (clear); XtManageChild (menubar); /* create a form work are */ form = XtVaCreateWidget ("form", xmFormWidgetClass, main_window, NULL); /* create horizontal RowColumn inside the form */ search_panel = XtVaCreateWidget ("search_panel", xmRowColumnWidgetClass, form, XmNorientation, XmHORIZONTAL, XmNpacking, XmPACK_TIGHT, XmNtopAttachment, XmATTACH_FORM, XmNleftAttachment, XmATTACH_FORM, XmNrightAttachment, XmATTACH_FORM, NULL); /* Create two TextField widgets with Labels... */ XtVaCreateManagedWidget ("Search Pattern:", xmLabelGadgetClass, search_panel, NULL); search_text = XtVaCreateManagedWidget ("search_text", xmTextFieldWidgetClass, search_panel, NULL); XtVaCreateManagedWidget (" Replace Pattern:", xmLabelGadgetClass, search_panel, NULL); replace_text = XtVaCreateManagedWidget ("replace_text", xmTextFieldWidgetClass, search_panel, NULL); XtManageChild (search_panel); text_output = XtVaCreateManagedWidget ("text_output", xmTextFieldWidgetClass, form, XmNeditable, False, XmNcursorPositionVisible, False, XmNshadowThickness, 0, XmNleftAttachment, XmATTACH_FORM, XmNrightAttachment, XmATTACH_FORM, XmNbottomAttachment, XmATTACH_FORM, NULL); n = 0; XtSetArg (args[n], XmNrows, 10); n++; XtSetArg (args[n], XmNcolumns, 80); n++; XtSetArg (args[n], XmNeditMode, XmMULTI_LINE_EDIT); n++; XtSetArg (args[n], XmNtopAttachment, XmATTACH_WIDGET); n++; XtSetArg (args[n], XmNtopWidget, search_panel); n++; XtSetArg (args[n], XmNleftAttachment, XmATTACH_FORM); n++; XtSetArg (args[n], XmNrightAttachment, XmATTACH_FORM); n++; XtSetArg (args[n], XmNbottomAttachment, XmATTACH_WIDGET); n++; XtSetArg (args[n], XmNbottomWidget, text_output); n++; text_edit = XmCreateScrolledText (form, "text_edit", args, n); XtManageChild (text_edit); XtManageChild (form); XtManageChild (main_window); XtRealizeWidget (toplevel); XtAppMainLoop (app_context); } /* file_select_cb() -- callback routine for "OK" button in * FileSelectionDialogs. */ void file_select_cb(dialog, client_data, call_data) Widget dialog; XtPointer client_data; XtPointer call_data; { char buf[256], *filename, *text; struct stat statb; long len; FILE *fp; int reason = (int) client_data; XmFileSelectionBoxCallbackStruct *cbs = (XmFileSelectionBoxCallbackStruct *) call_data; if (!XmStringGetLtoR (cbs->value, XmFONTLIST_DEFAULT_TAG, &filename)) return; /* must have been an internal error */ if (*filename == NULL) { XtFree (filename); XBell (XtDisplay (text_edit), 50); XmTextSetString (text_output, "Choose a file."); return; /* nothing typed */ } if (reason == FILE_SAVE) { if (!(fp = fopen (filename, "w"))) { perror (filename); sprintf (buf, "Can't save to %s.", filename); XmTextSetString (text_output, buf); XtFree (filename); return; } /* saving -- get text from Text widget... */ text = XmTextGetString (text_edit); len = XmTextGetLastPosition (text_edit); /* write it to file (check for error) */ if (fwrite (text, sizeof (char), len, fp) != len) strcpy (buf, "Warning: did not write entire file!"); else { /* make sure a newline terminates file */ if (text[len-1] != '0) fputc ('0, fp); sprintf (buf, "Saved %ld bytes to %s.", len, filename); } } else { /* reason == FILE_OPEN */ /* make sure the file is a regular text file and open it */ if (stat (filename, &statb) == -1 || (statb.st_mode & S_IFMT) != S_IFREG || !(fp = fopen (filename, "r"))) { perror (filename); sprintf (buf, "Can't read %s.", filename); XmTextSetString (text_output, buf); XtFree (filename); return; } /* put the contents of the file in the Text widget by * allocating enough space for the entire file, reading the * file into the space, and using XmTextSetString() to show * the file. */ len = statb.st_size; if (!(text = XtMalloc ((unsigned)(len+1)))) /* +1 for NULL */ sprintf (buf, "%s: XtMalloc(%ld) failed", len, filename); else { if (fread (text, sizeof (char), len, fp) != len) sprintf (buf, "Warning: did not read entire file!"); else sprintf (buf, "Loaded %ld bytes from %s.", len, filename); text[len] = 0; /* NULL-terminate */ XmTextSetString (text_edit, text); } } XmTextSetString (text_output, buf); /* purge output message */ /* free all allocated space. */ XtFree (text); XtFree (filename); fclose (fp); XtUnmanageChild (dialog); } /* popdown_cb() -- callback routine for "Cancel" button. */ void popdown_cb (w, client_data, call_data) Widget w; XtPointer client_data; XtPointer call_data; { XtUnmanageChild (w); } /* file_cb() -- a menu item from the "File" pulldown menu was selected */ void file_cb(w, client_data, call_data) Widget w; XtPointer client_data; XtPointer call_data; { static Widget open_dialog, save_dialog; Widget dialog = NULL; XmString button, title; int reason = (int) client_data; if (reason == FILE_EXIT) exit (0); XmTextSetString (text_output, NULL); /* clear message area */ if (reason == FILE_OPEN && open_dialog) dialog = open_dialog; else if (reason == FILE_SAVE && save_dialog) dialog = save_dialog; if (dialog) { XtManageChild (dialog); /* make sure that dialog is raised to top of window stack */ XMapRaised (XtDisplay (dialog), XtWindow (XtParent (dialog))); return; } dialog = XmCreateFileSelectionDialog (text_edit, "Files", NULL, 0); XtAddCallback (dialog, XmNcancelCallback, popdown_cb, NULL); XtAddCallback (dialog, XmNokCallback, file_select_cb, reason); if (reason == FILE_OPEN) { button = XmStringCreateLocalized ("Open"); title = XmStringCreateLocalized ("Open File"); open_dialog = dialog; } else { /* reason == FILE_SAVE */ button = XmStringCreateLocalized ("Save"); title = XmStringCreateLocalized ("Save File"); save_dialog = dialog; } XtVaSetValues (dialog, XmNokLabelString, button, XmNdialogTitle, title, NULL); XmStringFree (button); XmStringFree (title); XtManageChild (dialog); } /* search_cb() -- a menu item from the "Search" pulldown menu selected */ void search_cb(w, client_data, call_data) Widget w; XtPointer client_data; XtPointer call_data; { char *search_pat, *p, *string, *new_pat, buf[256]; XmTextPosition pos = 0; int len, nfound = 0; int search_len, pattern_len; int reason = (int) client_data; Boolean found = False; XmTextSetString (text_output, NULL); /* clear message area */ if (reason == SEARCH_CLEAR) { pos = XmTextGetLastPosition (text_edit); XmTextSetHighlight (text_edit, 0, pos, XmHIGHLIGHT_NORMAL); return; } if (!(string = XmTextGetString (text_edit)) || !*string) { XmTextSetString (text_output, "No text to search."); return; } if (!(search_pat = XmTextGetString (search_text)) || !*search_pat) { XmTextSetString (text_output, "Specify a search pattern."); XtFree (string); return; } new_pat = XmTextGetString (replace_text); search_len = strlen (search_pat); pattern_len = strlen (new_pat); if (reason == SEARCH_FIND_NEXT) { pos = XmTextGetCursorPosition (text_edit) + 1; found = XmTextFindString (text_edit, pos, search_pat, XmTEXT_FORWARD, &pos); if (!found) found = XmTextFindString (text_edit, 0, search_pat, XmTEXT_FORWARD, &pos); if (found) nfound++; } else { /* reason == SEARCH_SHOW_ALL || reason == SEARCH_REPLACE */ do { found = XmTextFindString (text_edit, pos, search_pat, XmTEXT_FORWARD, &pos); if (found) { nfound++; if (reason == SEARCH_SHOW_ALL) XmTextSetHighlight (text_edit, pos, pos + search_len, XmHIGHLIGHT_SELECTED); else XmTextReplace (text_edit, pos, pos + search_len, new_pat); pos++; } } while (found); } if (nfound == 0) XmTextSetString (text_output, "Pattern not found."); else { switch (reason) { case SEARCH_FIND_NEXT : sprintf (buf, "Pattern found at position %ld.", pos); XmTextSetInsertionPosition (text_edit, pos); break; case SEARCH_SHOW_ALL : sprintf (buf, "Found %d occurrences.", nfound); break; case SEARCH_REPLACE : sprintf (buf, "Made %d replacements.", nfound); } XmTextSetString (text_output, buf); } XtFree (string); XtFree (search_pat); XtFree (new_pat); } /* edit_cb() -- the callback routine for the items in the edit menu */ void edit_cb(widget, client_data, call_data) Widget widget; XtPointer client_data; XtPointer call_data; { Boolean result = True; int reason = (int) client_data; XEvent *event = ((XmPushButtonCallbackStruct *) call_data)->event; Time when; XmTextSetString (text_output, NULL); /* clear message area */ if (event != NULL && reason == EDIT_CUT || reason == EDIT_COPY || reason == EDIT_CLEAR) { switch (event->type) { case ButtonRelease : when = event->xbutton.time; break; case KeyRelease : when = event->xkey.time; break; default: when = CurrentTime; break; } } switch (reason) { case EDIT_CUT : result = XmTextCut (text_edit, when); break; case EDIT_COPY : result = XmTextCopy (text_edit, when); break; case EDIT_PASTE : result = XmTextPaste (text_edit); case EDIT_CLEAR : XmTextClearSelection (text_edit, when); break; } if (result == False) XmTextSetString (text_output, "There is no selection."); }
The Text and TextField widgets use callback routines
in the same way as other Motif widgets. The widgets provide callbacks
for a number of different purposes, such as text modification,
activation, and selection ownership. Some of the routines, such as
those that monitor keyboard input, may be invoked rather frequently. In
the next few sections, we introduce several of the callback routines
for the widgets.
We begin by exploring the callback routine that is
most commonly used for single-line Text widgets and TextField widgets.
This callback is the XmNactivateCallback, which is invoked
when the user presses RETURN in a TextField widget or a single-line
Text widget. The callback is not called for multiline Text widgets. The
callback routine for an XmNactivateCallback receives the
common XmAnyCallbackStruct as the call_data parameter
to the function. The callback reason is always XmCR_ACTIVATE.
the source code shows a callback function for some TextField widgets.
XtSetLanguageProc() is only available in X11R5; there is no
corresponding function in X11R4.
/* text_box.c -- demonstrate simple use of XmNactivateCallback * for TextField widgets. Create a rowcolumn that has rows of Form * widgets, each containing a Label and a Text widget. When * the user presses Return, print the value of the text widget * and move the focus to the next text widget. */ #include <Xm/TextF.h> #include <Xm/LabelG.h> #include <Xm/Form.h> #include <Xm/RowColumn.h> char *labels[] = { "Name:", "Address:", "City:", "State:", "Zip:" }; main(argc, argv) int argc; char *argv[]; { Widget toplevel, text_w, form, rowcol; XtAppContext app; int i; void print_result(); XtSetLanguageProc (NULL, NULL, NULL); toplevel = XtVaAppInitialize (&app, "Demos", NULL, 0, &argc, argv, NULL, NULL); rowcol = XtVaCreateWidget ("rowcol", xmRowColumnWidgetClass, toplevel, NULL); for (i = 0; i < XtNumber (labels); i++) { form = XtVaCreateWidget ("form", xmFormWidgetClass, rowcol, XmNfractionBase, 10, NULL); XtVaCreateManagedWidget (labels[i], xmLabelGadgetClass, form, XmNtopAttachment, XmATTACH_FORM, XmNbottomAttachment, XmATTACH_FORM, XmNleftAttachment, XmATTACH_FORM, XmNrightAttachment, XmATTACH_POSITION, XmNrightPosition, 3, XmNalignment, XmALIGNMENT_END, NULL); text_w = XtVaCreateManagedWidget ("text_w", xmTextFieldWidgetClass, form, XmNtraversalOn, True, XmNrightAttachment, XmATTACH_FORM, XmNleftAttachment, XmATTACH_POSITION, XmNleftPosition, 4, NULL); /* When user hits return, print the label+value of text_w */ XtAddCallback (text_w, XmNactivateCallback, print_result, labels[i]); XtManageChild( form); } XtManageChild (rowcol); XtRealizeWidget (toplevel); XtAppMainLoop (app); } /* preint_result() -- callback for when the user hits return in the * TextField widget. */ void print_result(text_w, client_data, call_data) Widget text_w; XtPointer client_data; XtPointer call_data; { char *value = XmTextFieldGetString (text_w); char *label = (char *) client_data; printf ("%s %s0, label, value); XtFree (value); XmProcessTraversal (text_w, XmTRAVERSE_NEXT_TAB_GROUP); }The program displays a data form using a RowColumn widget that manages several rows of Form widgets. Each Form contains a Label and a TextField widget, as shown in the figure.
When the user enters a value for a field and presses RETURN, the print_result() callback routine is invoked. The routine prints the value of the field and advances the keyboard focus to the next widget using XmProcessTraversal(). This function takes a widget and a traversal direction as its two parameters. We use the XmTRAVERSE_NEXT_TAB_ GROUP direction because each TextField widget is a tab group in and of itself, so we need to move to the next tab group, rather than to the next item in the same tab group. See Section #skeybtrav, for more information on tab groups.
When a single-line Text widget or a TextField widget
is used as part of a predefined Motif dialog, the
XmNactivateCallback for the widget is automatically hooked up to
the OK button in the dialog. As a result, the 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.
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); 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.
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;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 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
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); } /* check_passwd() -- handle the input of a password. */ void check_passwd(text_w, client_data, call_data) Widget text_w; XtPointer client_data; XtPointer call_data; { char *new; int len; XmTextVerifyCallbackStruct *cbs = (XmTextVerifyCallbackStruct *) call_data; if (cbs->reason == XmCR_ACTIVATE) { printf ("Password: %s0, passwd); return; } if (cbs->startPos < cbs->currInsert) { /* backspace */ cbs->endPos = strlen (passwd); /* delete from here to end */ passwd[cbs->startPos] = 0; /* backspace--terminate */ return; } if (cbs->text->length > 1) { cbs->doit = False; /* don't allow "paste" operations */ return; /* make the user *type* the password! */ } new = XtMalloc (cbs->endPos + 2); /* new char + NULL terminator */ if (passwd) { strcpy (new, passwd); XtFree (passwd); } else new[0] = NULL; passwd = new; strncat (passwd, cbs->text->ptr, cbs->text->length); passwd[cbs->endPos + cbs->text->length] = 0; for (len = 0; len < cbs->text->length; len++) cbs->text->ptr[len] = '*'; }As you can see in the figure, the Text widget only displays asterisks, no matter what the user has typed.
We use the check_passwd() function for both
the XmNactivateCallback and the XmNmodifyVerifyCallback
callbacks. When the user presses RETURN, the routine prints what has
been typed to stdout. If the user is not backspacing through
the text, we know we can add the new text to passwd, which is
the internal variable we use to store the text. Once the new text has
been copied, we convert it into asterisks, so that the user cannot see
what has been typed.
We need to handle two different cases for deletion.
If the insertion cursor is at the end of the typed string and the user
backspaces, we simply allow the action. If the user clicks somewhere in
the middle of the string and then backspaces, we delete all of the
characters from that point in the string to the end, since the user
cannot see the characters that he is deleting.
To handle the different forms of text deletion, we
test to see if startPos is less than currInsert.
Since startPos and endPos specify the range of text
that is being deleted, we can change these values and effectively
delete more text than the user originally intended. By setting
endPos to the string length of the internal variable passwd
, we handle both of the cases that we just described. If we had wanted
to, we could also have set startPos to 0 and deleted
all of the text. We can expand on the ZIP code example that we used for
filtering non-digits from typed input by providing an input field for
an area code and phone number. The format for a US phone number is as
follows:
123-456-7890We want to filter out all non-digits for a phone number, but we also want to add the dash character (-) automatically as it is needed. For example, after the user enters three digits, the Text widget should automatically insert a dash, so that the next character expected from the user is still a digit. Similarly, when the user backspaces and deletes a dash character, the widget should delete the preceding digit as well. shows how the interaction should work.
tab(@), linesize(2); l | l n | n. User Types@Text
Widget Displays
_
4@4 1@41 5@415- 4@415-4 T{
BACKSPACE T}@415- T{ BACKSPACE T}@41
_ We can continue to use the same type of algorithm that we used in
check_zip() to filter digits, and we can use some of the code
from check_passwd() to handle backspacing. The only remaining
problem is adding the necessary dash characters. Since we are using US
phone numbers, we know that the dashes should occur after the third and
seventh digits. Therefore, when currInsert is either 2 or 6,
the new digit should be added first, followed by the dash. the source
code shows the program that implements this functionality.
XtSetLanguageProc() is only available in X11R5; there is no
corresponding function in X11R4.
/* prompt_phone.c -- a complex problem for XmNmodifyVerifyCallback. * Prompt for a phone number by filtering digits only from input. * Don't allow paste operations and handle backspacing. */ #include <Xm/Text.h> #include <Xm/LabelG.h> #include <Xm/RowColumn.h> #include <ctype.h> void check_phone(); 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 ("Phone Number:", xmLabelGadgetClass, rowcol, NULL); text_w = XtVaCreateManagedWidget ("text_w", xmTextWidgetClass, rowcol, NULL); XtAddCallback (text_w, XmNmodifyVerifyCallback, check_phone, NULL); XtManageChild (rowcol); XtRealizeWidget (toplevel); XtAppMainLoop (app); } /* check_phone() -- handle phone number input. */ void check_phone(text_w, client_data, call_data) Widget text_w; XtPointer client_data; XtPointer call_data; { char c; int len = XmTextGetLastPosition(text_w); XmTextVerifyCallbackStruct *cbs = (XmTextVerifyCallbackStruct *) call_data; /* no backspacing or typing in the middle of string */ if (cbs->currInsert < len) { cbs->doit = False; return; } if (cbs->text->length == 0) { /* backspace */ if (cbs->startPos == 3 || cbs->startPos == 7) cbs->startPos--; /* delete the hyphen too */ return; } if (cbs->text->length > 1) { /* don't allow clipboard copies */ cbs->doit = False; return; } /* don't allow non-digits or let the input exceed 12 chars */ if (!isdigit (c = cbs->text->ptr[0]) || len >= 12) cbs->doit = False; else if (len == 2 || len == 6) { cbs->text->ptr = XtRealloc (cbs->text->ptr, 2); cbs->text->length = 2; cbs->text->ptr[0] = c; cbs->text->ptr[1] = '-'; } }There are a couple of ways that you could think to add the dashes. One way would be to use the XmNvalueChangedCallback to keep track of the phone number after it has been entered and then use XmTextInsert() to add the dashes when appropriate. The problem with this approach is that XmTextInsert() activates the XmNmodifyVerifyCallback function again, so the dash would be subject to the input filtering.
As a result, the only way to handle the situation is
to actually add the dashes in the XmNmodifyVerifyCallback
routine at the same time the digits are added. This approach involves
modifying the ptr and length fields of the
XmTextBlock structure in the XmTextVerifyCallbackStruct.
The check_phone() routine checks the current length of the
phone number. If it is either two or six characters long, the routine
reallocates ptr to hold two characters, adds the dash, and
increments length to account for the dash.
When the Text widget adds the digit and the dash, it
positions the insertion cursor at the end of the new text. Prior to
Motif 1.2, the position of the insertion cursor was not affected by the
amount of text that was added. The workaround to this problem was to
use the XmNvalueChangedCallback and call
XmTextSetInsertionPosition(). Although we haven't demonstrated its
use, the XmNvalueChangedCallback is useful when you need to
keep track of the changes in a Text widget, but you don't need to
monitor or change the input before it is displayed. This callback is
invoked after the text has been modified in any way, which means that
it is called for each insertion and deletion. The call_data
parameter to the routine is of type XmAnyCallbackStruct; the
reason field is always XmCR_VALUE_CHANGED.
The check_phone() routine is fairly simple,
in that it only allows text insertions and deletions that occur when
the insertion cursor is at the end of the text. While it is possible to
handle modifications in the middle of the text, the code quickly
becomes a large bowl of spaghetti. We do not allow clipboard copies of
more than one character at a time for the same reason. Our routine is
sufficient for demonstration purposes, but for a real application, you
should handle these cases.
The XmNmotionVerifyCallback can be used to
monitor the position of the insertion cursor. This callback is invoked
when the user moves the location cursor using the mouse or the arrow
keys, when the user drags the mouse or multi-clicks to extend the text
selection, or when the application calls a Text widget function that
moves the cursor or adds, deletes, or replaces text. However, if the
cursor does not move as a result of a function being called, the
callback is not invoked. The XmNmotionVerifyCallback allows an
application to intercept and prevent cursor movement.
The XmNmotionVerifyCallback uses the
XmTextVerifyCallbackStruct as its callback structure, just like the
XmNmodifyVerifyCallback. However, for motion callbacks, the
reason is XmCR_MOVING_INSERT_CURSOR and the startPos
, endPos, and text fields are invalid. The doit
field can be set to False to reject requests to reposition the
insertion cursor.
If the cursor motion occurs as a result of a user
action, the event field should point to an XEvent
structure describing the action that caused the cursor position to be
modified, When the cursor moves as a result of an application action,
the field should be set to NULL. However, the event
field is currently set to NULL regardless of what caused the
cursor motion. This bug makes it impossible to tell the difference
between a cursor motion performed by the user and one caused by the
application.
We can use the XmNmotionVerifyCallback to
tie up a loose end in prompt_phone.c. To make the text
verification simpler, we don't want to allow the user to move the
insertion cursor except by entering digits or backspacing. the source
code shows a new version of the check_phone() routine that
prevents cursor movement.
main(argc, argv) int argc; char *argv[]; { Widget text_w; ... XtAddCallback (text_w, XmNmodifyVerifyCallback, check_phone, NULL); ... } /* check_phone() -- handle phone number input. */ void check_phone(text_w, client_data, call_data) Widget text_w; XtPointer client_data; XtPointer call_data; { char c; int len = XmTextGetLastPosition (text_w); XmTextVerifyCallbackStruct *cbs = (XmTextVerifyCallbackStruct *) call_data; if (cbs->reason == XmCR_MOVING_INSERT_CURSOR) { if (cbs->newInsert != len) cbs->doit = False; return; } /* no backspacing, typing or stuffing in middle of string */ if (cbs->currInsert < len) { cbs->doit = False; return; } if (cbs->text->length == 0) { /* backspace */ if (cbs->startPos == 3 || cbs->startPos == 7) cbs->startPos--; /* delete the hyphen too */ return; } if (cbs->text->length > 1) { /* don't allow clipboard copies */ cbs->doit = False; return; } /* don't allow non-digits or let the input exceed 12 chars */ if (!isdigit (c = cbs->text->ptr[0]) || len >= 12) cbs->doit = False; else if (len == 2 || len == 6) { cbs->text->ptr = XtRealloc (cbs->text->ptr, 2); cbs->text->length = 2; cbs->text->ptr[0] = c; cbs->text->ptr[1] = '-'; } }We check the value of newInsert against the length of the current string to determine whether or not the intended cursor position is at the end of the text string. If it is not, we set doit to False to prevent the cursor movement. The XmNmotionVerifyCallback function can also be used to monitor pointer dragging for text selections.
The XmNfocusCallback and
XmNlosingFocusCallback callback routines can be used to monitor
when a Text widget gains and loses the keyboard focus. A Text widget
can receive the input focus if the user intentionally shifts the focus
to the widget or if the application moves the focus using
XmProcessTraversal(). When a widget gains the input focus and the
insertion cursor is not visible, we can make it visible and cause the
widget to automatically scroll to the current cursor location by
installing an XmNfocusCallback routine that calls
XmTextShowCursorPosition(), as shown in the following code
fragment:
{ Widget text_w; extern void gain_focus(); ... text_w = XmCreateScrolledText(...); XtAddCallback(text_w, XmNfocusCallback, gain_focus, NULL); ... } void gain_focus(text_w, client_data, call_data) Widget text_w; XtPointer client_data; XtPointer call_data; { XmTextShowCursorPosition (text_w, XmTextGetCursorPosition (text_w)); }The XmNfocusCallback is passed a callback structure of type XmAnyCallbackStruct with the callback reason set to XmCR_FOCUS .
The XmNlosingFocusCallback callback can be
used to monitor when the Text widget loses its focus. The callback
structure passed to the callback function is an
XmTextVerifyCallbackStruct. All of the fields except the text
field are valid, and the reason field is set to
XmCR_LOSING_FOCUS.
In Motif 1.2, the Text and TextField widgets have
been modified to support internationalized input and output. The
internationalization capabilities of the widgets are layered on top of
the functionality provided in X11R5, which is based on the ANSI-C
locale model. An internationalized application uses a library that
reads a locale database at runtime to get information about the user's
language environment. An application that uses the X Toolkit
establishes its language environment (or locale) by registering a
language procedure using XtSetLanguageProc(), as explained in
Section #slangproc. See Volume Four, X Toolkit Intrinsics
Programming Manual, for more information on the localization of an
Xt-based application.
One of the important characteristics of a locale is
the encoding used to represent the character set for the locale. A
character set is simply a set of characters, while an encoding is a
numeric representation of these characters. A charset (not the same as
a character set) is an encoding in which all of the characters use the
same number of bits. The Latin-1 charset (ISO8859-1) defines an
encoding for all of the characters used in Western languages. However,
not all languages can be represented by a single charset. Japanese text
commonly contains words written using the Latin alphabet, as well as
phonetic characters from the katakana and hirigana
alphabets, and ideographic kanji characters. Each of these
character sets has its own charset. The phonetic and Latin charsets are
8-bits wide, while the ideographic charset is 16-bits wide. Since the
charsets must be combined into a single encoding for Japanese text, the
encoding uses shift sequences to specify the character set for each
character in a string.
When an encoding contains shift sequences and
characters of nonuniform width, strings can still be stored in a
standard NULL-terminated array of characters; this
representation is known as a multibyte string. Strings can also
be stored using a wide-character type (wchar_t in
ANSI-C) in which each character has a fixed size and occupies one array
element. ANSI-C provides functions that convert between multibyte and
wide-character strings and the text output routines in X11R5 support
both types of strings. Multibyte strings are usually more compact than
wide-character strings, but wide-character strings are easier to work
with. If an internationalized application performs any text
manipulation, it must take care to handle all strings properly.
Fortunately, many applications can do internationalized text input and
output without performing any manipulations on the text.
Multibyte strings are NULL-terminated,
while there is no single convention for the termination of
wide-character strings. The following C string-handling routines are
safe to use with multibyte strings: strcat(), strcmp()
, strcpy(), strlen(), and strncmp(). The
string comparison routines are only useful to check for byte-for-byte
equality; use strcoll() to compare strings for sorting. None
of the C string-handling routines work with wide-character strings.
Multibyte strings can be written to a file or an
output stream. If the terminal is operating in the current locale,
printing a multibyte string to stdout or stderr
causes the correct text to be displayed. Multibyte strings can also be
read from a file or the stdin input stream. If the file is
encoded in the current locale, or the terminal is operating in the
current locale, the strings that are read are meaningful. For a more
complete description of working with multibyte and wide-character
strings, see Volume One, Xlib Programming Manual.
The Motif 1.2 Text and TextField widgets provide two
resources for specifying their textual data: XmNvalue and
XmNvalueWcs. The XmNvalue resource specifies the text
string as a char * value, so it can be used to set
the value of the widget to a multibyte string. XmNvalueWcs
specifies the string as a wchar_t * value, so it is
used to set the value to a wide-character string. This resource cannot
be specified in a resource file. If XmNvalue and
XmNvalueWcs are both defined, the value of XmNvalueWcs
takes precedence.
Regardless of which resource you set, the widgets
store the text internally as a multibyte string. The widgets take care
of converting between multibyte strings and wide-character strings when
necessary. As a result, you can set the text string using the
XmNvalue resource and retrieve it with XtVaGetValues()
using the XmNvalueWcs resource.
The Text widget provides the following convenience
routines for manipulating the text value as a wide-character string:
XmTextFindStringWcs() XmTextGetSelectionWcs() XmTextGetStringWcs() XmTextGetSubstringWcs() XmTextInsertWcs() XmTextReplaceWcs() XmTextSetStringWcs()These routines work for both Text and TextField widgets. The TextField also provides corresponding functions that only work with TextField widgets. All of these routines function identically to their regular character string counterparts, except that they take or return wide-character string values. If you have specified the text string using XmNvalue, you can still use the wide-character string routines because they handle any necessary string conversions. For more information on the different wide-character routines, see Volume Six B, Motif Reference Manual.
The widgets also provide a wide-character version of
the text modification callback, XmNmodifyVerifyCallbackWcs.
This callback is invoked before the value of the widget is modified, so
an application can use it to monitor changes in the widget. The
callback is passed a callback structure of type
XmTextVerifyCallbackStructWcs, which is defined as follows:
typedef struct { int reason; XEvent *event; Boolean doit; XmTextPosition currInsert, newInsert; XmTextPosition startPos, endPos; XmTextBlockWcs text; } XmTextVerifyCallbackStructWcs;
With this structure the reason field has
the value XmCR_MODIFYING_TEXT_VALUE. All of the fields have
the same meaning as the fields in the regular
XmTextVerifyCallbackStruct, except that the text field is
a pointer of type XmTextBlockWcs. This structure is defined as
follows:
typedef struct { wchar_t *wcsptr; int length; } XmTextBlockRecWcs, *XmTextBlockWcs;If callback routines are registered for both the XmNmodifyVerifyCallback and the XmNmodifyVerifyCallbackWcs , the routines for the XmNmodifyVerifyCallback are invoked first. The resulting data, which may have been modified, is passed to the XmNmodifyVerifyCallbackWcs routines.
The Text and TextField widgets do not use compound
strings, so their text output functionality is based directly on Xlib's
internationalized text output capabilities. To support languages that
use multiple charsets, X11R5 introduced the XFontSet
abstraction for its text output routines. An XFontSet contains
all of the fonts necessary to display text in the current locale. The
new text output routines work with font sets, so they can render text
for locales that require multiple charsets. See Volume One, Xlib
Programming Manual, for more information on internationalized text
output.
Each of the widgets has a XmNfontList
resource for specifying the font that it uses. Since the widgets do not
use compound strings, they cannot use font list tags to display text
using different fonts as decribed in Section #sfonttag. However, the
font list can specify a font set, so the widgets can display text using
multiple character sets in a locale that requires them. The widgets
pick a font by searching the font list for a font set that has the tag
XmFONTLIST_DEFAULT_TAG. If the search finds such a font set, it is
used. Otherwise, the widgets use the first font set specified in the
font list. If the font list does not contain a font set, the first font
is used. If you specify a font list entry with the tag
XmFONTLIST_DEFAULT_TAG, make sure that it is appropriate for the
encoding of the current locale.
Converting user keystrokes into text in the encoding
of the current locale is the most difficult task of
internationalization. An internationalized program cannot assume any
particular mapping between keystrokes and input characters, since it
must run in any locale on a single workstation, using a single
keyboard. The mapping between keystrokes and Japanese characters is
very different and much more complex than the mapping between
keystrokes and Latin characters, for example. When there are more
characters in the codeset of a locale than there are keys on a
keyboard, some sort of input method is required for mapping
between multiple keystrokes and input characters.
All of the characters for English can be entered
using the standard keyboard; the SHIFT key makes it possible to enter
both lowercase and uppercase letters as well as the number and
punctuation characters. For many European languages, the most common
accented characters may appear directly on a keyboard, but there are
still a number of other characters that cannot be entered with any
single shifted or unshifted keystroke. In these cases, the input method
is typically implemented in the keyboard hardware using a special key
that puts the keyboard in "compose" mode in which one or more of the
following keystrokes are combined into a single character.
The Asian ideographic languages are what make
internationalized text input complicated. Japanese and Korean both have
phonetic alphabets that are small enough to be mapped onto a keyboard.
While it is sometimes adequate to leave text in this representation,
the user usually wants the final text to be in the full ideographic
language. Input methods for these languages often have the user type
the phonetic symbols for a particular word or words and then signal
that the composition or pre-editing is complete. At this point, the
input method can look up the string of phonetic characters in a
dictionary and convert it to the equivalent character or characters in
the ideographic language. Multiple characters can have the same
phonetic representation, so the user may still have to select the
desired character.
Since input methods can be large and complex and
they vary from locale to locale, it does not make sense to link every
application with a generic input method that is localized at runtime.
The X Input Method (XIM) abstraction in X11R5 supports the
model of an input manager that is run as a separate process and
that communicates with the X server and with the application. An
application that needs to use an input method calls XOpenIM()
to establish a connection to the input method that is appropriate for
the current locale.
An input method needs to provide feedback to the
user, so X defines three areas for interaction:
Just as the X server can display multiple windows
for a single client, an input method can maintain multiple input
contexts for an application. A text editor that supports multiple
editing windows within a single top-level window could create an input
context for each window or share a single context among all of the
windows. The function XCreateIC() creates an X Input Context (
XIC) that keeps track of information about the input context, such
as the interaction style, the windows used for the pre-edit and status
areas, and the font set for the text.
When an application gets a KeyPress event,
it needs to use that event in a call to XmbLookupString() or
XwcLookupString() to get the multibyte or wide-character string
encoded in the current locale. These routines are analogous to
XLookupString(), but this routine can only return Latin-1 strings,
so it is not appropriate for internationalized input.
The support for input methods in Xlib is designed to
be incorporated within toolkits and widgets. Accordingly, the
internationalized text input capabilities of the Motif Text and
TextField widgets are layered on top of the input method mechanism.
Fortunately, the widgets encapsulate most of the lower-level
functionality, so you don't need to understand the details of the Xlib
implementation. For a more complete description of the Xlib
functionality, see Volume One, Xlib Programming Manual.
Motif leaves it to the hardware vendors to supply
input methods, so the toolkit does not provide any itself. If you need
to provide internationalized text input, consult the documentation for
your system for information about the input methods that it supports.
Alternately, you can build one of the contributed input methods
provided as part of X11R5. R5 as shipped from MIT contains two separate
implementations of the input method facilities. The Xsi implementation
is the default on all but Sony machines, which use the Ximp
implementation. Each implementation defines its own protocol for
communication between Xlib and input methods. Ximp and Xsi each come
with contributed input methods that are not compatible with each other.
For X11R6, the X Consortium is planning to standardize the input method
implementation, so you may want to enquire about the status of that
effort before putting any significant effort into a product that uses
one of these implementations.
When you create an editable Text or TextField
widget, it automatically provides a connection to the input method for
the current locale. The VendorShell widget plays a role in
internationalization as it defines the XmNinputMethod and
XmNpreeditType resources for specifying the input method and the
interaction style, respectively. A Text or TextField widget is always
created as an ancestor of a VendorShell, so the widget can access these
resources to set up the connection to the input method. The resources
are defined by the VendorShell because it handles the geometry
management of the pre-edit and status areas for the input method.
The XmNinputMethod resource specifies the
input method portion of the locale modifier that is set before an input
method is opened. The format of the value for this resource is
vendor-defined. The XmNpreeditType resource sets the
interaction style used by the input method. The syntax, possible
values, and default value of this resource are also vendor-dependent.
Motif only supports the over-the-spot, off-the-spot,
and root-window interaction styles. Under the off-the-spot style, the
VendorShell positions the pre-edit and status areas below the
application's main window but inside the shell. The VendorShell handles
the geometry management for the areas and places a separator between
the main window and the input method area. If the application sets or
gets the XmNheight of the shell using XtVaSetValues()
or XtVaGetValues(), the height includes the height of the
input method area. With the over-the-spot style, the VendorShell still
displays the status area at the bottom of the application's top-level
window, but the pre-edit area is positioned over the insertion cursor
in the Text widget. The Text widget passes the insertion position to
the input method, so that the pre-edit area moves as with the insertion
cursor.
The Motif toolkit implements its internationalized
text input functionality using the following undocumented public
routines:
XmImRegister() XmImUnregister() XmImSetFocusValues() XmImSetValues() XmImUnsetFocus() XmImGetXIM() XmImMbLookupString() XmImVaSetFocusValues() XmImVaSetValues()These routines simplify the interaction with the lower-level XIM and XIC constructs provided by Xlib. If you need to provide text input in another widget, such as a DrawingArea, you have to handle opening an input method, creating an input context, and obtaining input from the input method yourself. If you have access to the source code, you may want to investigate these routines. The only danger is that because the routines are undocumented, they may change in the next release of Motif.
The Motif Text and TextField widgets can be used to
provide an application with sophisticated text entry capabilities. The
widgets come with a full set of convenience routines that make it easy
to perform a number of standard text editing tasks. However, these
widgets work best when they are left alone to do their jobs. While they
are highly configurable, the little bits of fine tuning you add may
cause your code to grow twice as much to accommodate the new features
and the necessary error checking.
The following exercises are designed to expand on
the ideas described in this chapter and introduce some new directions
for using Text widgets.