\begindata{text,538945640} \textdsversion{12} \template{default} \define{global

\begindata{text,538945640}
\textdsversion{12}
\template{default}
\define{global
}
\define{itemize
}
\formatnote{\chapter{Example 4:
input}
\indexi{Key bindings}
Keyboard input}]} \indexi{Keyboard
\indexi{Keymap} \indexi{Keystate}
\indexi{Bind} \indexi{Key commands}
Example 4 illustrates how to create a program that responds to keyboard
input.
The Andrew Toolkit provides three classes--\italic{Keymap,}
\italic{Keystate}
and \italic{Bind}--that together allow objects to respond to input from
the
keyboard. \indexi{Class++Keymap} \indexi{Class++Keystate}
\indexi{Class++Bind}
Like Example 3, this program draws \italic{hello world} initially at the
center of the window. If the user clicks on the left or the right mouse
button, the program draws \italic{hello world} centered at the location
of the
mouse cursor when the user lets up on the button. In addition, the
program
responds to two key commands: Ctrl-c and Ctrl-i. If the user presses
Ctrl-c,
the program centers \italic{hello world} in the window. If the user
presses
Ctrl-i, the program inverts the screen, from white to black or black to
white.
The discussion that follows shows how to modify the
\italic{helloworldview}
class in Example 3 to produce Example 4. If you were to follow along,
you
would produce a program, called \italic{helloworldapp}, in five files:
\itemize{
a hellov.ch file -- will contain the class definition for
\italic{helloworldview.}
We will modify the class definition from Example 3 to override
\italic{View}'s methods for receiving and losing the input focus.
general,
the
In
\italic{Interaction Manager} sends keyboard input to the view with the
input
focus.
In addition, we will add a class procedure for initializing
\italic{helloworldview}'s keymap and for binding keys to command
procedures.
Finally, we will add some data to the class for keeping track of whether
\italic{helloworldview} has the input focus and whether the screen should
be
black on white or white on black.
a hellov.c file -- will contain statements that import Andrew Toolkit
classes
and define the object's methods and class procedures. We will define two
new
methods: The first for receiving the input focus and the second for
losing it.
We will also define a class procedure for initializing the keymap and
for
binding keys to command procedures. Finally, we will define the command
procedures for centering \italic{hello world} and for inverting the
screen.
a helloa.ch file -- will contain the class definition for the application
program that will display an instance of the class in a window. This
will be
exactly the same as in Example 3.
a helloa.c -- will contain declarations needed by the Andrew Toolkit
linking
and loading facilities as well the definition of the
\italic{helloworldview}
application method. We will add a statement to request the input focus
for
the view immediately upon start-up.
Makefile -- will contain the directions for compiling, linking and
loading.
This will be exactly the same as Example 3.
}For a complete listing of these files, see \italic{Program listing for
Example 4 } at the end of this section on p.
\begindata{textref,539315976}
\textdsversion{12}
# ExFourListing\
\enddata{textref,539315976}
\view{textrefv,539315976,917,0,0}. The source code is available in the
directory /usr/andrew/examples/ex4, together with the compiled program.
Although the discussion of the steps refers directly to this example, the
information generally applies to the creation of any stand-alone
application
program that will respond to keyboard input.
\section{Running the example program}
\formatnote{\bold{Action 1.}\formatnote{ } \formatnote{To run the
program, at
the \bold{command }prompt type
runapp /usr/andrew/examples/ex4/helloa
and press the Enter key.
}
\bold{Response.}\formatnote{
\italic{hello world}
}The program will produce a window with
centered in the body.
\bold{Action 2.}\formatnote{
window,
position the
}To move \italic{hello world} in the
mouse cursor within the program's window, click on the left mouse button,
and drag the mouse.
\bold{Response. }\formatnote{
wherever
you move the
} \italic{hello world} will be drawn
mouse.
\bold{Action 3.}\formatnote{
} \formatnote{@beginTmultiple To use the
keyboard to center \italic{hello world} in the center of the window,
type
\italic{Ctrl-c}
}
\bold{Response. }\formatnote{
the
center of the window.
\bold{Action 4.}\formatnote{
} \italic{hello world} will be drawn in
}\formatnote{To invert the drawing, type
\italic{Ctrl-i}
}
\bold{Response.}\formatnote{
case, it
will change
}The background will invert.
In this
from white to black.
\bold{Action 5.}\formatnote{
the
window title
}To quit the program, move the cursor to
bar, pop-up the menus and choose \bold{Zap} from the \italic{This Window}
card.
\bold{Response.}\formatnote{
screen.
}The program will disappear from the
}
\begindata{bp,538929288}
\enddata{bp,538929288}
\view{bpv,538929288,918,0,0}
\section{Keyboard input concepts} \indexi{Keyboard input++Concepts}
If you use the methods that \italic{Keymap,} \italic{Keystate} and
\italic{Bind} provide in order to bind a sequence of keys to a procedure,
then
when the user types that sequence, the \italic{Interaction Manager}
invokes
the corresponding procedure. For example, if you bind \italic{Ctrl-x
Ctrl-s}
to a procedure called \italic{Write_Buffer_to_File}, then when the user
types
\italic{Ctrl-x Ctrl-s}, the \italic{Interaction Manager} invokes the
\italic{Write_Buffer_to_File} procedure. Likewise, if you bind the key
stroke
\italic{a} to \italic{Insert_Character}, then when the user types an
\italic{a}, the \italic{Interaction Manager} will invoke the procedure
\italic{Insert_Character}.
\subsection{Key maps} \indexi{Key maps}
The Andrew Toolkit uses key maps for keyboard input. Formally, a key map
is a
function that maps a sequence of keyboard characters to a procedure. To
understand how key maps work, it is useful to think of a key map as an
array
with 128 entries, one for each \smaller{ASCII} character.
Each entry in the key map array may have one of three values or types of
bindings: a procedure, another key map, or a special value used to
indicate
that the character has no binding.
\paragraph{Procedure bindings} \indexi{Procedure bindings} \indexi{Key
bindings}
Whenever the user types a character, the character is used as an index
into
the key map array. If the array value for the character is a procedure,
then
the mapping is done and the procedure is executed. For example, suppose
the
key map \italic{edit_BasicCommands} maps \italic{Ctrl-s} to the procedure
\italic{edit_SearchForward}. Then when the user types a \italic{Ctrl-s},
the
Interaction Manager maps the key to the procedure
\italic{edit_SearchForward},
which it then invokes.
\paragraph{Sub-key map bindings } \indexi{Key bindings++Sub-key map}
If the array value for the character that a user types is another key
map,
then the specified key map will be used to map the next character that
the
user types. For example, suppose that the key map
\italic{edit_BasicCommands}
maps \italic{Ctrl-x} to another key map, \italic{edit_eXtendedCommands,}
and
that \italic{Ctrl-s} in the sub-key map \italic{edit_eXtendedCommands}
maps to
the procedure \italic{edit_WriteBuffertoFile}. Then, when the user types
\italic{Ctrl-x}, the key map \italic{edit_eXtendedCommands} will be used
to
interpret the next character that the user types. If the user types
\italic{Ctrl-s}, then that will map to the procedure
\italic{edit_WriteBufferToFile}, which will be executed.
A key map that itself has sub-key maps defines an implicit tree, where
one key
map is the root keymap, and the other sub-key maps are branches. As an
applications programmer, you do not need to worry about sub-key maps.
The
Andrew Toolkit defines them automatically when you bind a sequence of
keys to
a procedure.
\paragraph{No bindings}
Finally, if the character that the user types
indicates
there is no binding, then the key map returns
that
there was no procedure bound to that sequence
\italic{Interaction Manager} then ignores the
maps to a value that
a status code indicating
of keys.
key.
The
\subsection{Key states} \indexi{Key states}
There is normally only one key map per class since the key map can
usually be
shared among all instances of a view. A major function of the key state
is to
associate an instance of a view with the key map for its class.
A second function of key states is to keep track of the state of the key
mapping. If key maps only mapped single keys to procedures, then there
would
be no need to keep track of the state of the mapping. To map
\italic{sequences} of keys to procedures, however, requires keeping
state
information. For instance, the mapping process must know which key map
is
being used to evaluate the next incoming character.
\paragraph{Keystate chains} \indexi{Key state chains}
In the Andrew ToolKit, an object can contain other objects; for example,
a
multi-media editor may contain a drawing editor. Since an object may not
know
what other objects it will contain in advance, there must be a protocol
that
allows objects to coordinate the key bindings that each object may
desire. If
the multi-media editor and the drawing editor both want to bind
\italic{Ctrl-s}, for instance, there must be a way to decide which
binding
takes priority.
\italic{Keystate chains }establish the priority of menu items. Keystate
chains work according to the following protocol: Each object posts the
keystates that it wants. If the object is contained in another object,
the
parent object determines the placement of the child's keystate in the
keystate
chain.
If a keystate ks\italic{\subscript{j }}occurs after the keystate
ks\italic{\subscript{i}} on the keystate chain, then
ks\italic{\subscript{j}}'s keystate takes precedence over
ks\italic{\subscript{i}}'s. Thus, a parent object can decide that its
keystate should have precedence over its child's, or that its child's
keystate
should have precedence. The usual way to decide precedence is based on
input
focus: the keystate of the object with the input focus should have
precedence.
\subsection{Flow of control for keyboard input}
\indexi{Keyboard input++Flow of control}
\indexi{Key bindings++Flow of control}
The \italic{Interaction Manager} sends keyboard input to the view with
the
input focus. A view can request the input focus from the
\italic{Interaction
Manager}.
\indexi{Interaction manager++Keyboard input} Only one view at a time can
have
the input focus. \indexi{Input focus}
Most application programs start up by creating a set of views and asking
the
\italic{Interaction Manager} to make one of the views the input focus. A
user
can request a view to make itself the input focus by clicking in its
visual
rectangle. If a view wishes to respond to such user requests, it should
request the \italic{Interaction Manager} to give it the input focus upon
receiving the \italic{DownTransition} in its \italic{Hit} method.
When the \italic{Interaction Manager} gives a view the input focus, it
notifies the view by calling a method, \italic{ReceiveInputFocus}. A
view's
\italic{ReceiveInputFocus} method must post a \italic{keystate} to its
parent.
The \italic{keystate} provides the \italic{Interaction Manager} with the
information it needs to interpret the user's keyboard input appropriately
for
the view. If the view is to highlight itself when it has the input
focus, its
\italic{ReceiveInputFocus} method should also request an \italic{Update}
from
its parent in order to draw the highlighting.
When the \italic{Interaction Manager} takes the input focus away from a
view,
it notifies the view by calling another method, \italic{LoseInputFocus}.
A
view's \italic{LoseInputFocus} method should do whatever it needs to do
before
losing the input focus. For example, if the view needs to de-highlight
itself, it should request an update from it parent in order to dehighlight.
In this example program, the highlighting and dehighlighting of the view
is
handled by the Hit procedure, which will be described in more detail
later in
this chapter.
\section{Modifying the class definition}
\subsection{Declaring the class} \indexi{Class++Declaring}
In order to respond to keyboard input, Example 4 overrides
\italic{View}'s
\italic{ReceiveInputFocus} and \italic{LoseInputFocus} methods and
declares
the class procedure, \italic{InitializeClass}.
\indexi{ \italic{ReceiveInputFocus}}
\indexi{ \italic{LoseInputFocus}}
\indexi{ \italic{InitializeClass}}
\indexi{Class++Initializing} \indexi{Input focus}
If you are creating a subclass of view that will respond to keyboard
input, as
in the helloworldview example program, you must override two methods:
\italic{ReceiveInputFocus} and \italic{LoseInputFocus}. These methods
provide a way for the \italic{Interaction Manager} to communicate with a
view
that it has received the input focus or has lost the input focus.
In addition, you should declare a class procedure called
\italic{InitializeClass}. \italic{InitializeClass} is called only once-the
first time the class is loaded. Thus, it is an appropriate place to do
initializations that need to be done for each class rather than for each
object in the class. The mapping of keys to command procedures typically
needs to be done once for the class rather than for each object in the
class,
so you should normally do it in \italic{InitializeClass} rather than
\italic{InitializeObject}.
Finally, you will want to add two new elements to the data structure.
The
first will be a pointer to the class' \italic{keystate,} the object that
provides the \italic{Interaction Manager} with a state-transition machine
for
interpreting the sequence of keys that the user may type. When the
view's
\italic{ReceiveInputFocus} method is called, it must post this
\italic{keystate} to its parent. The second is a \italic{boolean} to
keep
track of whether the view has the input focus or not. This will be used
to
guide the object's \italic{Hit} method about requesting the input focus,
and
by \italic{Update} and \italic{FullUpdate} methods about what
highlighting is
appropriate.
The following is the new class declaration for the example class
\italic{helloworldview}:
\formatnote{
class helloworldview [hellov]: view \{
overrides:
FullUpdate(enum view_UpdateType type, long left, long top, long
width, long height);
Update();
Hit (enum view_MouseAction action, long x, long y, long
numberOfClicks)
returns struct view *;
\bold{ReceiveInputFocus();
LoseInputFocus();}
classprocedures:
InitializeClass()returns boolean;
data:
\bold{struct keystate *keystate;}
long x,y;
long distX,distY;
long newX,newY;
boolean HaveDownTransition;
\bold{boolean haveInputFocus;
boolean blackOnWhite;
boolean newBlackOnWhite;}
\};
}
The class \italic{helloworldview} will store its \italic{keystate} object
in
\italic{keystate}. It will use the boolean, \italic{haveInputFocus}, to
keep
keep track of whether it has the input focus. The booleans
\italic{blackOnWhite} and \italic{newBlackOnWhite} will keep track of
inverting the display and are specific to Example 4.
\subsection{Describing the key bindings}
\indexi{Key bindings++Description}
\indexi{Bind description}
All key bindings for a class must be described in a \italic{bind
description}.
Binding descriptions contain the following elements:
\itemize{
\bold{User invocation name}. In addition to allowing users to invoke a
procedure by typing the sequence of keys it is bound to, many
applications
allow users to invoke a procedure by its name. For example, in the EMACS
editor, the user can save the current file by typing \italic{Ctrl-x Ctrls} or
by typing \italic{ESC-X write-current-file.} The user invocation name is
the
name the user types to invoke the procedure, for example,
\italic{write-current-file}. Unlike EMACS, the name should begin with
the
name of the object to avoid collisions among dynamically loaded objects.
For
example, \italic{helloworldview} uses \italic{helloworld-center} and
\italic{helloworld-invert} as its user invocation names.
\bold{Key sequence.} This is the \smaller{ASCII} string representation
for
the sequence of keys that the user will type to invoke the function. For
example, Ctrl-x Ctrl-s would be represented as \italic{"
\\\\030\\\\023"}.
\bold{Key data.} The data, if any, that should be passed to the
procedure
when the user invokes the procedure through the key sequence. In this
case,
the data is a "rock," or constant value.
\bold{Menu entry.} The menu, if any, that will invoke the procedure.
Menus
will be discussed in Example 5.
\bold{Menu data.} The data, if any, that should be passed to the
procedure
when the user invokes the procedure through the menu item. In this case,
the
data is a "rock," or constant value.
\bold{Menu mask.} The mask, if any, that controls the circumstances
under
which the menu item will appear. For example, EZ uses menu masks to
control
the menus that appear when a region is selected and when there is no
selected
region. It should be 0 unless you want to use them.
\bold{Procedure.} A pointer to the procedure. If the procedure is not
given,
the module name must be given. The module should supply the procedure.
For
example, EZ invokes the spelling module without supplying a pointer to
the
procedure and the spelling module supplies it.
\bold{Brief documentation.} A string that gives a brief description of
the
procedure (something suitable for a command-line description \italic{a
la}
\smaller{EMACS} \italic{apropos}). For example, \italic{write-currentfile}
might have the description "Saves current file."
\bold{Module name.}
found.
The name of the module in which the procedure can be
}
The description of the key bindings should be stored in a \smaller{NULL}
terminated array of struct \italic{bind_Description}. Each entry in the
array
should be a description of a single key binding.
For example, the two key bindings for \italic{helloworldview} are
described by
the following array, \italic{helloworldviewBindings} in
\italic{hellov.c}:
\formatnote{static struct bind_Description helloworldviewBindings[]=\{
\{"helloworld-center", "\\003", 0, NULL, 0, 0, Center, "Center the
helloworld string."\},
\{"helloworld-invert", "\\011",0, NULL, 0, 0, Invert, "Invert the
helloworld
string."\},
NULL
\};
}
The first entry in the array describes the binding of the key
\italic{Ctrl-c}
to the procedure \italic{Center}. The first item in the first entry,
\italic{"helloworld-center,"} is the user invocation name. The second,
\italic{"\\003,"} is the \smaller{ASCII} string representation of
\italic{Ctrl-c}. The next item, \italic{0}, represents the data to be
passed
to the procedure; in this example, none. The next three entries,
\smaller{NULL}, \italic{0}, \italic{0}, are the menu entry, menu data,
and
menu masks; again, in this example, none. The next item,
\italic{Center}, is
the name of the procedure that will be bound to the key \italic{Ctrl-c}.
The
next item, \italic{"Center the helloworld string,"} is the brief
documentation.
Likewise, the second entry in the array binds the key \italic{Ctrl-i} to
the
procedure \italic{Invert}. The third entry in the array is
\smaller{NULL} and
indicates the end of the description of the key bindings.
In general, a bind description must have the user invocation name of the
procedure, either the key sequence or the menu entry that invokes the
procedure, the brief documentation, and either the pointer to the
procedure or
the module in which the procedure may be found.
The rock data \indexi{Rock}
\indexi{Parameters++Rock} is not generally used, but it can be a useful
option. For example, in creating a procedure to do screen inversions,
you
could create two key command or menu options, "black-to-white," and
"white-to-black." Instead of actually writing two separate procedures,
however, you could write one general "invert" procedure, and pass the
procedure an appropriate rock value which would tell the procedure which
kind
of inversion to perform.
In this example, we do not use the rock parameter, since it is easier to
have
the Update method handle the inversions.
\subsection{Creating the command functions}
You must define a command function for each binding of a key sequence to
a
function. The name of the function must match the name that you used in
the
\italic{bind_Description} structure.
For example, \italic{helloworldview} defines two command functions in
\italic{hellov.c:} \italic{Center} for \italic{Ctrl-c} and
\italic{Invert} for
\italic{Ctrl-i}:
\formatnote{
static void Center(hwv, rock)
struct helloworldview *hwv;
long rock;
\{
struct rectangle myVisualRect;
helloworldview_GetVisualBounds(hwv,&myVisualRect);
hwv->newX = rectangle_Left(&myVisualRect) +
rectangle_Width(&myVisualRect)
/ 2;
hwv->newY = rectangle_Top(&myVisualRect) +
rectangle_Height(&myVisualRect)
/ 2;
helloworldview_WantUpdate(hwv, hwv);
\}
static void Invert(hwv, rock)
struct helloworldview *hwv;
long rock;
\{
hwv->newBlackOnWhite = ! hwv->newBlackOnWhite;
helloworldview_WantUpdate(hwv, hwv);
\}
}
The actions of these functions are specific to the
\italic{helloworldview}
example. \italic{Center} re-centers the string by calculating the center
point, and requesting a simple update redraw. \italic{Invert} looks for
the
current color of the screen (white or black), sets the
\italic{newBlackOnWhite}
variable to the opposite color, and requests a simple update redraw for
the
screen.
Note that the first argument to any command procedure must be a pointer
to the
class. In this example, \italic{struct helloworldview *hwv}.
The second parameter to the Invert procedure, rock, is a constant value
passed
along to the procedure.
\subsection{Creating the key map} \indexi{Key maps++Creating}
The \italic{keymap} represents the set of bindings from the key sequences
to
the functions to be performed. The same \italic{keymap} can normally be
shared among all instances of a view. Thus, the \italic{keymap} should
normally be created in \italic{InitializeClass,}
\indexi{ \italic{InitializeClass}}
\indexi{Class++Initializing} a class procedure that is called only once-the
first time the class is loaded. Likewise, the structure declaration for
the
\italic{keymap} can be done in the module rather
In general, to do keyboard input, you should declare an
\italic{InitializeClass} procedure that creates a new \italic{keymap} and
associates the keybindings for the class with the newly created
\italic{keymap}.
For example, the following creates a \italic{keymap} and associates the
\italic{keymap} with the key bindings for \italic{helloworldview} in the
file
\italic{hellov.c}:
\formatnote{
static struct keymap *helloworldviewKeymap;
boolean helloworldview__InitializeClass(classID)
struct classheader *classID;
\{
helloworldviewKeymap=keymap_New();
bind_BindList(helloworldviewBindings,
helloworldviewKeymap,NULL,&helloworldview_classinfo);
return TRUE;
\}
}
The line \italic{static struct keymap *helloworldviewKeymap} declares the
key
map structure for this class.
Then, \italic{helloworldviewKeymap=keymap_New()} creates a new key map
and
stores it in \italic{helloworldviewKeymap}.
Finally, a call to the \italic{bind_BindList}
\indexi{ \italic{bind_BindList}}
\indexi{ \italic{BindList}} method associates the key bindings in
\italic{helloworldviewBindings} with the newly created key map,
\italic{helloworldviewKeymap}.
The method \italic{bind_BindList} takes four parameters: The first is a
description of the key bindings, \italic{struct bind_Description};
\indexi{Key bindings++Description}
\indexi{Bind description} the second is a keymap, \italic{struct keymap};
the
third is a list of menus, \italic{struct menulist} (in this example
\smaller{NULL}); and the fourth is the class information, \italic{struct
classinfo}.
The final parameter, \italic{struct classinfo}, \indexi{Classinfo} needs
some
explanation. When the class preprocessor runs, it generates a
\italic{classinfo} structure for each class. In this example, the
preprocessor creates helloworld_classinfo, which is a static global
structure
defined in the file \italic{hellov.eh}.
For any class \italic{x}, to access the class information from the class
that
exports it, you must write \italic{&x_classinfo} (e.g.,
\italic{&helloworld_classinfo}). To access the class information from
everywhere else, you write \italic{class_load("x")} . For instance, a
file,
compile.c, which supplies procedures for use with the class
\italic{textview}
(and therefore needs to pass the address of a \italic{textview_classinfo}
structure to bind_BindList or to a proctable_DefineProc) would use
class_load("textview"), not &textview_classinfo.
\indexi{Exporting++Class
information}
\indexi{Importing++Class information}
\subsection{Creating a key state & initializing the input focus}
\indexi{Key
states++Creating}
\indexi{Input focus++Initializing}
The \italic{keystate} represents
particular \italic{view}. Thus,
requires a
\italic{keystate}, and it should
\italic{InitializeObject}
method for the class. The newly
part of
the view's data for later use in
addition, the
\italic{InitializeObject} method
does
not have the input focus.
the binding of a \italic{keymap} to a
each instance of a \italic{view}
be created in the
created keystate should be stored as
\italic{ReceiveInputFocus}.
In
should set a flag to indicate that it
In this example, the following \italic{InitializeObject}
\indexi{ \italic{InitializeObject}}
method, in \italic{hellov.c} creates a \italic{keystate} and associates
the
\italic{keystate} with the \italic{keymap} for \italic{hwv}, an instance
of
the view \italic{helloworldview}:
\formatnote{
#define POSUNDEF -1
boolean helloworldview__InitializeObject(classID, hwv)
struct classheader *classID;
struct helloworldview *hwv;
\{
hwv->x = POSUNDEF;
hwv->y = POSUNDEF;
hwv-> HaveDownTransition = FALSE;
\bold{hwv->haveInputFocus = FALSE;
hwv->keystate = keystate_Create(hwv, helloworldviewKeymap);
hwv->blackOnWhite = TRUE;
hwv->newBlackOnWhite = TRUE;}
return TRUE;
\}
}
\italic{hw->keystate = keystate_Create(hw, helloworldKeymap)} creates a
\italic{keystate} that binds \italic{hwv}, an instance of the
\italic{view}
\italic{helloworldview}, to the \italic{keymap}
\italic{helloworldviewKeymap
}and stores it in \italic{hwv->keystate}. The statement
\italic{hwv->haveInputFocus=FALSE} indicates that hwv does not have the
input
focus.
\subsection{User requests for the input focus} \indexi{Input
focus++Requesting}
By convention, the user can cause a \italic{view} to make itself the
input
focus by clicking in its space on the screen. If the \italic{view}
wishes to
respond to the user request, it in turn should request the Interaction
Manager
to give it the input focus upon receiving the \italic{DownTransition} in
its
\italic{Hit} method. In general, if you are building a \italic{view}
that
will respond to keyboard input, a request for the input focus should be
provided in the \italic{view}'s \italic{Hit} method by
\italic{WantInputFocus}
call. It should be noted that the request for input focus must be
explicit.
The following, in \italic{hellov.c}, is the \italic{helloworldview_Hit}
method. It is written so that if the view does not have the input focus,
it
requests the input focus upon a down click of the left or right mouse
button.
This is the same as in the previous example, but the input focus
discussion
is more relevant for this chapter.
\formatnote{
struct view *helloworldview__Hit(hwv, action, x, y, numberOfClicks)
struct helloworldview *hwv;
enum view_MouseAction action;
long x;
long y;
long numberOfClicks;
\{
if(hwv-> HaveDownTransition)
switch(action)\{
case view_RightUp:
hwv->HaveDownTransition=FALSE;
/* fall through */
case view_RightMovement:
hwv->newX=x-hwv->distX;
hwv->newY=y-hwv->distY;
break;
case view_LeftUp:
hwv->HaveDownTransition=FALSE;
hwv->newX=x;
hwv->newY=y;
break;
case view_LeftMovement:
/* do nothing */
break;
default:
/* re-synchronize mouse */
hwv->HaveDownTransition=FALSE;
\}
if(!hwv->HaveDownTransition)
switch(action)\{
case view_RightDown:
hwv->distX=x-hwv->x;
hwv->distY=y-hwv->y;
/* fall through */
case view_LeftDown:
hwv->HaveDownTransition=TRUE;
helloworldview_WantInputFocus(hwv,hwv);
break;
\}
helloworldview_WantUpdate(hwv,hwv);
return (struct view *)hwv;
\}
}
The case statement \italic{view_LeftDown:} requests the input focus from
\italic{hwv}'s parent in the view tree. Normally, \italic{hwv}'s parent
will
pass the input focus request to its parent and so on to the
\italic{Interaction Manager}. If the request is granted, the
\italic{Interaction Manager} will call \italic{hwv}'s
\italic{ReceiveInputFocus} method, discussed below. The method
\italic{WantInputFocus} takes two parameters. The first is the view from
which you are making the request for the input focus, in this case
\italic{hwv}; the second parameter is the view to give the input focus
to,
normally a pointer to your own view, in this case \italic{hwv}.
\subsection{Receiving the input focus}
\indexi{Input focus++Receiving}
\indexi{ \italic{ReceiveInputFocus}}
When the \italic{Interaction Manager} gives a view the input focus, it
notifies the view by calling a method, \italic{ReceiveInputFocus}. If
you are
writing a view that does keyboard input, its \italic{ReceiveInputFocus}
method
must do three things.
First, it should set a flag that indicates it has the input focus. You
will
need to write other methods (\italic{e.g.}, Hit, Update, etc.) so that
they
test this flag and act accordingly. For example, upon receiving a down
transition, a \italic{Hit} method should test the flag to check if it has
the
input focus before requesting it.
Second, since the \italic{Interaction Manager} keeps a linked list of
keystates that correspond to the keybindings that views in the view tree
have
posted, a view must set the next keystate to NULL to indicate that there
are
no more keystates in the list \italic{before} posting a keystate in
\italic{ReceiveInputFocus}.
Failure to do so will result in an infinite
loop.
Finally, the view should post a \italic{keystate} to its parent. The
\italic{keystate} provides the \italic{Interaction Manager} with the
information it needs to interpret the user's keyboard input appropriately
for
the view.
If the view is to highlight itself when it has the input focus, its
\italic{ReceiveInputFocus} method should also request an \italic{Update}
from
its parent in order to draw the highlighting.
The following, in \italic{hellov.c}, is \italic{helloworldview}'s
\italic{ReceiveInputFocus} method:
\formatnote{
void helloworldview__ReceiveInputFocus(hwv)
struct helloworldview *hwv;
\{
hwv->haveInputFocus = TRUE;
hwv->keystate->next = NULL;
helloworldview_PostKeyState(hwv, hwv->keystate);
helloworldview_PostMenus(hwv, NULL);
\}
}
The statement \italic{hwv->haveInputFocus = \smaller{TRUE}} sets a flag
to
indicate that \italic{hwv} has the input focus. \italic{hwv->keystate>next =
\smaller{NULL}} sets the next keystate to \smaller{\italic{NULL}} before
posting the keystate. Finally, the statement
\italic{helloworld_PostKeyState(hwv,hwv->keystate)} posts the keystate
\italic{hwv->keystate} for \italic{hwv} to the \italic{Interaction
Manager}.
The final statement, \italic{helloworldview_PostMenus(hwv, NULL);} is
used to
get the input focus for the Quit menu card, and will be explained in more
detail in Example 5.
If you are creating a view that is overriding view's
\italic{ReceiveInputFocus}
method, even if your view does not have a \italic{keystate} it should
post a
\italic{keystate} of \smaller{NULL}.
\subsection{Losing the input focus}
\indexi{Input focus++Losing}
\indexi{ \italic{LoseInputFocus}}
When the \italic{Interaction Manager} takes the input focus away from a
view,
it notifies the view by calling another method, \italic{LoseInputFocus}.
A
view's \italic{LoseInputFocus} method should set a flag indicating that
it no
longer has the input focus, then do whatever it needs to do before losing
the
input focus. For example, if the view needs to de-highlight itself, it
should
request an update from it parent in order to de-highlight.
You do not need to worry about \italic{un}posting a view's keystates when
it
loses the input focus. That is handled automatically. You must,
however,
repost the keystates every time the view gets the input focus back, as
discussed above.
The following is \italic{helloworldview}'s \italic{LoseInputFocus}
method:
\formatnote{
void helloworldview__LoseInputFocus(hwv)
struct helloworldview *hwv;
\{
hwv->haveInputFocus = FALSE;
\}
}
The statement \italic{hwv>haveInputFocus = \smaller{FALSE}} sets a flag
that
indicates \italic{hwv} does not have the input focus.
\subsection{Requesting the input focus upon start-up}
\indexi{Input focus++Requesting}
If you are creating an application that does keyboard input, then it
should
request the input focus after putting the object in the view tree and
before
entering the keyboard processor interaction loop. If the input focus is
not
requested, the key commands will not operate until the first call to
FullUpdate, which occurs after a mouse action.
The following statement, in \italic{helloa.c}, immediately following the
im_SetView statement, requests the input focus for \italic{hwv} upon
start-up:
\formatnote{
helloworldview_WantInputFocus(hwv,hwv);
}
\subsection{Full update and update requests}
If a view highlights when it has the input focus, its \italic{FullUpdate}
and
\italic{Update} methods must test the input focus flag and redraw
highlighted
when it is \smaller{TRUE}, de-highlighted when it is \smaller{FALSE}.
Because \italic{helloworldview} does not highlight when it has the input
focus, the following changes in \italic{FullUpdate} and \italic{Update}
methods are specific to \italic{helloworldview} and will be of interest
only
to those who wish to see the example carried to completion.
\begindata{bp,538929032}
\enddata{bp,538929032}
\view{bpv,538929032,919,0,0}
\paragraph{Full update request}
\formatnote{
void helloworldview__FullUpdate(hwv, type, left, top, width, height)
struct helloworldview *hwv;
enum view_UpdateType type;
long left;
long top;
long width;
long height;
\{
struct rectangle myVisualRect;
helloworldview_GetVisualBounds(hwv,&myVisualRect);
if (hwv->x == POSUNDEF) \{
hwv->x = rectangle_Left(&myVisualRect) +
rectangle_Width(&myVisualRect) / 2;
hwv->y = rectangle_Top(&myVisualRect) +
rectangle_Height(&myVisualRect) / 2;
hwv->newX = hwv->x;
hwv->newY = hwv->y;
\}
else \{
hwv->x = hwv->newX;
hwv->y = hwv->newY;
\}
helloworldview_SetTransferMode(hwv, graphic_COPY);
if (hwv->blackOnWhite)
helloworldview_FillRect(hwv, &myVisualRect,
helloworldview_WhitePattern(hwv));
else
helloworldview_FillRect(hwv, &myVisualRect,
helloworldview_BlackPattern(hwv));
helloworldview_SetTransferMode(hwv, graphic_INVERT);
helloworldview_MoveTo(hwv, hwv->x, hwv->y);
helloworldview_DrawString(hwv,"hello world",
graphic_BETWEENTOPANDBASELINE
| graphic_BETWEENLEFTANDRIGHT);
\}
}
We add a black/white inversion function, so that if the rectangle is
black-on-white, then the program switches it to white-on-black, and vice
versa. Also, newX and newY must be included in the rectangle
calculations so
that their initial values will not be zero. If they are, and the first
action
the user performs in the program window is a key command, the "hello
world"
string will go to coordinates (0,0) instead of staying in the center.
\begindata{bp,538928712}
\enddata{bp,538928712}
\view{bpv,538928712,920,0,0}
\paragraph{Update request}
\formatnote{
struct helloworldview *hwv;
\{
/* TransferMode is graphic_INVERT from the last FullUpdate */
if (hwv->newBlackOnWhite != hwv->blackOnWhite)
\{
struct rectangle vr;
helloworldview_GetVisualBounds(hwv,&vr);
helloworldview_FillRect(hwv, &vr,
helloworldview_BlackPattern(hwv));
hwv->blackOnWhite = hwv->newBlackOnWhite;
\}
if (hwv->newX != hwv->x || hwv->newY != hwv->y) \{
helloworldview_MoveTo(hwv, hwv->x, hwv->y);
helloworldview_DrawString(hwv, "hello world",
graphic_BETWEENTOPANDBASELINE |
graphic_BETWEENLEFTANDRIGHT);
hwv->x = hwv->newX;
hwv->y = hwv->newY;
helloworldview_MoveTo(hwv, hwv->x, hwv->y);
helloworldview_DrawString(hwv, "hello world",
graphic_BETWEENTOPANDBASELINE |
graphic_BETWEENLEFTANDRIGHT);
\}
\}
}
Again, this is similar to the previous example with the addition of the
inversion function.
\section{Importing Andrew Toolkit procedures}
\formatnote{
#include <class.h>
#include "hellov.eh"
#include "graphic.ih"
\bold{#include "rect.h"
#include "keymap.ih"
#include "keystate.ih"
#include "bind.ih"}
}
In previous examples we imported only the procedures in class.h,
graphic.ih,
and hellov.eh. The added complexity of this example requires several
additional imports: rect.h contains procedures for managing rectangles
that
are used by graphic; keymap.ih, keystate.ih, and bind.ih, contain
procedures
needed by the various calls to keymap, keystate, and bind methods.
The compilation procedures are the same as in the previous example.
\begindata{bp,538928968}
\enddata{bp,538928968}
\view{bpv,538928968,921,0,0}
\begindata{texttag,539236104}
\textdsversion{12}
ExFourListing\
\enddata{texttag,539236104}
\view{texttagv,539236104,922,0,0}
\section{Program listing for Example 4}
\formatnote{
\bold{hellov.ch}
class helloworldview[hellov] : view \{
overrides:
FullUpdate(enum view_UpdateType type, long left, long top, long
width, long height);
Update();
Hit (enum view_MouseAction action, long x, long y, long
numberOfClicks)
returns struct view *;
ReceiveInputFocus();
LoseInputFocus();
classprocedures:
InitializeClass()returns boolean;
data:
struct keystate *keystate;
long x,y;
long distX,distY;
long newX,newY;
boolean HaveDownTransition;
boolean haveInputFocus;
boolean blackOnWhite;
boolean newBlackOnWhite;
\};
\bold{hellov.c}
#include <class.h>
#include "hellov.eh"
#include "graphic.ih"
#include "rect.h"
#include "keymap.ih"
#include "keystate.ih"
#include "bind.ih"
#define POSUNDEF -1
static struct keymap *helloworldviewKeymap;
boolean helloworldview__InitializeObject(classID, hwv)
struct classheader *classID;
struct helloworldview *hwv;
\{
hwv->x = POSUNDEF;
hwv->y = POSUNDEF;
hwv-> HaveDownTransition = FALSE;
hwv->haveInputFocus = FALSE;
hwv->keystate = keystate_Create(hwv, helloworldviewKeymap);
hwv->blackOnWhite = TRUE;
hwv->newBlackOnWhite = TRUE;
return TRUE;
\}
void helloworldview__FullUpdate(hwv, type, left, top, width, height)
struct helloworldview *hwv;
enum view_UpdateType type;
long left;
long top;
long width;
long height;
\{
struct rectangle myVisualRect;
helloworldview_GetVisualBounds(hwv,&myVisualRect);
if (hwv->x == POSUNDEF) \{
hwv->x = rectangle_Left(&myVisualRect) +
rectangle_Width(&myVisualRect) / 2;
hwv->y = rectangle_Top(&myVisualRect) +
rectangle_Height(&myVisualRect) / 2;
hwv->newX = hwv->x;
hwv->newY = hwv->y;
\}
else \{
hwv->x = hwv->newX;
hwv->y = hwv->newY;
\}
helloworldview_SetTransferMode(hwv, graphic_COPY);
if (hwv->blackOnWhite)
helloworldview_FillRect(hwv, &myVisualRect,
helloworldview_WhitePattern(hwv));
else
helloworldview_FillRect(hwv, &myVisualRect,
helloworldview_BlackPattern(hwv));
helloworldview_SetTransferMode(hwv, graphic_INVERT);
helloworldview_MoveTo(hwv, hwv->x, hwv->y);
helloworldview_DrawString(hwv,"hello world",
graphic_BETWEENTOPANDBASELINE | graphic_BETWEENLEFTANDRIGHT);
void helloworldview__Update(hwv)
struct helloworldview *hwv;
\}
\{
/* TransferMode is graphic_INVERT from the last FullUpdate */
if (hwv->newBlackOnWhite != hwv->blackOnWhite)
\{
struct rectangle vr;
helloworldview_GetVisualBounds(hwv,&vr);
helloworldview_FillRect(hwv, &vr,
helloworldview_BlackPattern(hwv));
hwv->blackOnWhite = hwv->newBlackOnWhite;
\}
if (hwv->newX != hwv->x || hwv->newY != hwv->y) \{
helloworldview_MoveTo(hwv, hwv->x, hwv->y);
helloworldview_DrawString(hwv, "hello world",
graphic_BETWEENTOPANDBASELINE |
graphic_BETWEENLEFTANDRIGHT);
hwv->x = hwv->newX;
hwv->y = hwv->newY;
helloworldview_MoveTo(hwv, hwv->x, hwv->y);
helloworldview_DrawString(hwv, "hello world",
graphic_BETWEENTOPANDBASELINE |
graphic_BETWEENLEFTANDRIGHT);
\}
\}
struct view *helloworldview__Hit(hwv, action, x, y, numberOfClicks)
struct helloworldview *hwv;
enum view_MouseAction action;
long x;
long y;
long numberOfClicks;
\{
if(hwv-> HaveDownTransition)
switch(action)\{
case view_RightUp:
hwv->HaveDownTransition=FALSE;
/* fall through */
case view_RightMovement:
hwv->newX=x-hwv->distX;
hwv->newY=y-hwv->distY;
break;
case view_LeftUp:
hwv->HaveDownTransition=FALSE;
hwv->newX=x;
hwv->newY=y;
break;
case view_LeftMovement:
/* do nothing */
break;
default:
/* re-synchronize mouse */
hwv->HaveDownTransition=FALSE;
\}
if(!hwv->HaveDownTransition)
switch(action)\{
case view_RightDown:
hwv->distX=x-hwv->x;
hwv->distY=y-hwv->y;
/* fall through */
case view_LeftDown:
hwv->HaveDownTransition=TRUE;
helloworldview_WantInputFocus(hwv,hwv);
break;
\}
helloworldview_WantUpdate(hwv,hwv);
return (struct view *)hwv;
\}
void helloworldview__ReceiveInputFocus(hwv)
struct helloworldview *hwv;
\{
hwv->haveInputFocus = TRUE;
hwv->keystate->next = NULL;
helloworldview_PostKeyState(hwv, hwv->keystate);
helloworldview_PostMenus(hwv, NULL);
\}
void helloworldview__LoseInputFocus(hwv)
struct helloworldview *hwv;
\{
hwv->haveInputFocus = FALSE;
\}
static void Center(hwv, rock)
struct helloworldview *hwv;
long rock;
\{
struct rectangle myVisualRect;
helloworldview_GetVisualBounds(hwv,&myVisualRect);
hwv->newX = rectangle_Left(&myVisualRect) +
rectangle_Width(&myVisualRect)
/ 2;
hwv->newY = rectangle_Top(&myVisualRect) +
rectangle_Height(&myVisualRect)
/ 2;
helloworldview_WantUpdate(hwv, hwv);
\}
static void Invert(hwv, rock)
struct helloworldview *hwv;
long rock;
\{
hwv->newBlackOnWhite = ! hwv->newBlackOnWhite;
helloworldview_WantUpdate(hwv, hwv);
\}
static struct bind_Description helloworldviewBindings[]=\{
\{"helloworld-center", "\\003",0, NULL,0,0, Center, "Center the
helloworld
string."\},
\{"helloworld-invert", "\\011",0, NULL,0,0, Invert, "Invert the
helloworld
string."\},
NULL
\};
boolean helloworldview__InitializeClass(classID)
struct classheader *classID;
\{
helloworldviewKeymap=keymap_New();
bind_BindList(helloworldviewBindings,
helloworldviewKeymap,NULL,&helloworldview_classinfo);
return TRUE;
\}
\bold{helloa.ch}
class helloworldapp[helloa] : application[app]\{
overrides:
Start() returns boolean;
\};
\bold{helloa.c}
#include <class.h>
#include "helloa.eh"
#include "im.ih"
#include "hellov.ih"
boolean helloworldapp__Start(hwapp)
struct helloworldapp *hwapp;
\{
struct helloworldview *hwv;
struct im *im;
if(!super_Start(hwapp))
return FALSE;
hwv=helloworldview_New();
if(hwv==NULL)
return FALSE;
im=im_Create(NULL);
if(im==NULL)\{
helloworldview_Destroy(hwv);
return FALSE;
\}
im_SetView(im,hwv);
helloworldview_WantInputFocus(hwv,hwv);
return TRUE;
\}
\bold{Makefile}
SRCDIR=/usr/andrew/
INCLUDES= -I. -I$\{SRCDIR\}include/atk -I$\{SRCDIR\}include
INCLUDESRC = $\{SRCDIR\}include/atk
CC=cc
DEBUG = -g
TOOLS = $\{SRCDIR\}bin/
CFLAGS= $\{DEBUG\} $\{INCLUDES\}
CLASSFLAGS=$\{INCLUDES\}
MAKEDO = $\{TOOLS\}makedo $\{DEBUG\} -b $\{TOOLS\} -d $\{SRCDIR\}lib
CLASS = $\{TOOLS\}class
.SUFFIXES: .ih .eh .ch .do
.ch.ih:
$\{CLASS\} $\{CLASSFLAGS\} $*.ch
.ch.eh:
$\{CLASS\} $\{CLASSFLAGS\} $*.ch
.o.do:
$\{MAKEDO\} $<
all:
helloa.do hellov.do
helloa.do: helloa.o helloa.eh
hellov.do: hellov.o hellov.eh
helloa.o: helloa.c
helloa.o: $\{INCLUDESRC\}/app.ih
helloa.o: $\{INCLUDESRC\}/graphic.ih
helloa.o: $\{INCLUDESRC\}/im.ih
helloa.o: $\{INCLUDESRC\}/observe.ih
helloa.o: $\{INCLUDESRC\}/point.h
helloa.o: $\{INCLUDESRC\}/rect.h
helloa.o: $\{INCLUDESRC\}/view.ih
helloa.o: $\{SRCDIR\}include/class.h
helloa.o: hellov.ih
helloa.o: helloa.eh
helloa.eh helloa.ih: helloa.ch
helloa.eh helloa.ih: $\{INCLUDESRC\}/app.ih
hellov.o: hellov.c
hellov.o: $\{INCLUDESRC\}/graphic.ih
hellov.o: $\{INCLUDESRC\}/observe.ih
hellov.o: $\{INCLUDESRC\}/point.h
hellov.o: $\{INCLUDESRC\}/rect.h
hellov.o: $\{INCLUDESRC\}/view.ih
hellov.o: $\{SRCDIR\}include/class.h
hellov.o: hellov.eh
hellov.eh hellov.ih: hellov.ch
hellov.eh hellov.ih: $\{INCLUDESRC\}/graphic.ih
hellov.eh hellov.ih: $\{INCLUDESRC\}/observe.ih
hellov.eh hellov.ih: $\{INCLUDESRC\}/point.h
hellov.eh hellov.ih: $\{INCLUDESRC\}/rect.h
hellov.eh hellov.ih: $\{INCLUDESRC\}/view.ih
hellov.eh hellov.ih: $\{SRCDIR\}include/class.h
}
\begindata{bp,537558784}
\enddata{bp,537558784}
\view{bpv,537558784,924,0,0}
Copyright 1992 Carnegie Mellon University and IBM.
All rights reserved.
\smaller{\smaller{$Disclaimer:
# Permission to use, copy, modify, and distribute this software and its
# documentation for any purpose and without fee is hereby granted,
provided
# that the above copyright notice appear in all copies and that both that
# copyright notice and this permission notice appear in supporting
# documentation, and that the name of IBM not be used in advertising or
# publicity pertaining to distribution of the software without specific,
# written prior permission.
#
# THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD
# TO THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF
# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL ANY COPYRIGHT
# HOLDER BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL
# DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE,
# DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
# OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
# WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
#
# $
}}\enddata{text,538945640}