X Window Programming From Scratch (Jesse Liberty's From Scratch Programming Series) PDF
X Window Programming From Scratch (Jesse Liberty's From Scratch Programming Series) PDF
from scratch
PROGRAMMING SERIES
X Window
Programming
from scratch
J. Robert Brown
All terms mentioned in this book that are known to be trademarks or service Team Coordinator
marks have been appropriately capitalized. Que Corporation cannot attest to Vicki Harding
the accuracy of this information. Use of a term in this book should not be
Media Developer
regarded as affecting the validity of any trademark or service mark.
Michael Hunter
Warning and Disclaimer Interior Design
Every effort has been made to make this book as complete and as accurate as Sandra Schroeder
possible, but no warranty or fitness is implied. The information provided is on Cover Designers
an “as is” basis. The authors and the publisher shall have neither liability nor Maureen McCarty
responsibility to any person or entity with respect to any loss or damages aris- Anne Jones
ing from the information contained in this book or from the use of the CD or
programs accompanying it. Copy Writer
Eric Borgert
Production
Darin Crone
Steve Geiselman
Contents at a Glance
Introduction xvi
Parameters ......................................................................................................67
Definition ........................................................................................................68
The return Statement ....................................................................................68
Data ......................................................................................................................69
Data Types ......................................................................................................70
Next Steps ............................................................................................................81
Chapter 3 A Word on C 83
Hello World..........................................................................................................84
Comment Tokens ............................................................................................84
The Function main ..........................................................................................85
Code Bodies ....................................................................................................86
Variable Scope ................................................................................................88
Built-In Functions ..........................................................................................90
Memory Management ..................................................................................100
Dynamic Memory Allocation........................................................................105
Memory Leaks ..............................................................................................107
Definitions and Macros ................................................................................107
Conclusion ..........................................................................................................109
Next Steps ..........................................................................................................109
Managing Windows............................................................................................175
Processing Events ..............................................................................................176
Summary ............................................................................................................176
Next Steps ..........................................................................................................177
Rotating ..............................................................................................................247
Rotating a Line..............................................................................................248
Rotating an Arc..............................................................................................251
Next Steps ..........................................................................................................252
Index 741
About the Author
J. Robert Brown started his path to a career in software development by earning a
college scholarship for Performing Arts in his homeland of central Ohio, where he
held the misguided belief that he could be a movie star.
After years of either sleeping in his car or working three jobs concurrently to fund
his way through an Electrical Engineering program, he found himself in Europe in
the late 1980s working for the Department of Defense.
As a field engineer maintaining the mobile computer systems responsible for collect-
ing and processing intelligence data, he realized that the position required too much
manual labor. In 1991, he made his way through a Computer Science program at the
European Division of the University of Maryland and although he didn’t exactly fin-
ish in the top 10% of his class, he believes strongly that he helped those who did to
get there.
John was invited to join Los Alamos National Laboratory as a Computer Scientist in
1996 where he remained until only recently. He now works for GTE Data Sources
near Tampa, Florida.
Dedication
There are people who exist in the world who, once you’ve encountered them, change you for-
ever. Through the strength of their character, depth of their spirit, or simply their presence in
the world, they leave a lasting impression. I fear that we have one fewer such individual
today due to the loss of Shel Silverstein in May, 1999. I hope for everyone there is someone
who touches his or her life as Shel’s works have touched mine.
Those without whom my life would mean less and this effort would not have been possible are
my dear mother, Cindy Baker; my brother and best friend, Scott Brown; the absolute love of
my life, Mikeala Elise; and the person who gave her to me, Kinnina McCray.
Acknowledgments
X Window Programming from Scratch is the result of efforts by many people. I am
filled with awe and gratitude for the level of professionalism and quality the staff at
Que publishing brought to this effort: specifically, Hugh “Red” Vandivier, Susan “I
need this back by Monday” Moore, Cynthia “Did this change your meaning?” Fields, and
Katie “You’d better meet the deadline” Purdum. And although no one likes to be told
they have made a mistake, technical editor Ed “Um, you might want to check this”
Petron was able to point out oversights in a way that never came close to wounding
my ego. With the help of these and many others behind the scenes, this text is much
better than I could ever have made it on my own.
There are others who contributed indirectly to the project by offering their friend-
ship, encouragement, and patience as I tried to keep my head above water: namely,
Mike Koscielniak, Cindy Sievers, and Jennifer Brown.
Tell Us What You Think!
As the reader of this book, you are our most important critic and commentator. We
value your opinion and want to know what we’re doing right, what we could do bet-
ter, what areas you’d like to see us publish in, and any other words of wisdom you’re
willing to pass our way.
As an Associate Publisher for Que, I welcome your comments. You can fax, email, or
write me directly to let me know what you did or didn’t like about this book[md]as
well as what we can do to make our books stronger.
Please note that I cannot help you with technical problems related to the topic of this book,
and that due to the high volume of mail I receive, I might not be able to reply to every mes-
sage.
When you write, please be sure to include this book’s title and author as well as your
name and phone or fax number. I will carefully review your comments and share
them with the author and editors who worked on the book.
Fax: 317.581.4666
Email: quetechnical@macmillanusa.com
Therefore, the next decision to make was what operating system to use. The decision
was obvious: The targeted operating system is Linux, although the project has been
tested on several operating systems.
According to the January 2000 issue of ComputerWorld, of all PC operating systems
in use, Linux holds the lead with 38% of the market and growing. A close second is
Windows NT with 25% of the market.
Honing skills in the Linux operating system is imperative for continued success in
the software development profession.
Software Checklist
The required components for accomplishing the Graphics Editor project in this text
include
• Linux Operating System
• C Compiler
• X Window System
After several iterations and plenty of research, you’ll have a mostly functional
operating system.
Alternately, you can purchase a distribution of Linux.
Linux Distributions
Linux is a free operating system protected by the GNU General Public License.
However, many vendors offer for sale a Linux distribution.
A distribution that requires you to pay more than the cost of the media and postage
is generally enhanced in one of many ways.
Either by adding an installation program that automates the selection of the proper
Linux kernel, or by adding features to the environment such as utilities to configure
your windowing environment or system options, the vendors earn and justify the
costs of a distribution.
The vendors advancing Linux include Red Hat, Slackware, Debian, SuSE, and
others.
Having experience with all of them, I am unabashedly (and free of charge) going to
recommend Red Hat’s Linux.
The kernel (core of the operating system) packaged with Red Hat’s distribution is
not different from the version you could download from the Internet or purchase in
another distribution; however, the ease with which the Red Hat distribution installs,
configures, and updates is worth the investment.
During the authoring of this text and the development of the Graphics Editor pro-
ject, I used Red Hat 6.1. This version is packaged for very easy installation and con-
figuration with the XFree86 X Window System and the GNU C Compiler.
The default X Windowing environment provided by the installation of Red Hat 6.1
is the GNOME desktop using the Enlightenment Window Manager. This is
reflected in the screen shots used in the text.
To learn more about vendors distributing Linux as well as the availability for down-
loading the Linux operating system, issue the following command in the search field
of your favorite Internet portal:
url: Linux
Introduction xix
Because of the popularity of the Linux operating system, you will have to sift
through many hundreds of matches.
Optionally, you can go directly to http://www.redhat.com and read about their latest
release.
C Compiler
If you opt not to use a version of Linux provided by a vendor, it can be necessary to
download and install a C language compiler separately.
Packaged with most distributions of Linux (including Red Hat) is the GNU C
Compiler.
Because Red Hat’s distribution of Linux used during the writing of the text includes
the GNU C Compiler, this is the primary compiler used during the development of
the project.
The project has been tested using other compilers and the text addresses compiler
differences for managing the project, so feel free to use any C compiler available to
you.
If you are using a version of Linux (or UNIX) that does not include a C compiler, it
will be necessary to acquire one. The GNU C Compiler is available for free down-
load from the Internet.
Refer to the Free Software Foundation Web site (http://www.fsf.com or search for
url:gnu using your favorite browser) for information on sites providing the latest
version of the compiler and its associated tools.
X Window System
The X Window System is free for downloading and is also provided with every dis-
tribution of Linux purchased from the vendors mentioned above.
Visit www.xfree86.com for the latest runtime and development environments
included with most Linux distributions.
Optionally, you might be able to ftp (File Transfer Protocol) the X Window System
from a variety of sites on the Internet, including gatekeeper.dec.com and
uunet.uu.net.
xx X Window Programming from Scratch
how too
pro nouns it How To Pronounce It. You’ll see an icon set in the margin next to a box that con-
tains a technical term and how it should be pronounced. For example, “cin is
pronounced see-in, and cout is pronounced see-out.”
EXCURSIONS
Excursions. These are short diversions from the main topic being discussed, and
they offer an opportunity to flesh out your understanding of a topic.
Concept Web. With a book of this type, a topic can be discussed in multiple places as
a result of when and where we add functionality during application development. To
help make this all clear, we’ve included a Concept Web that provides a graphical repre-
sentation of how all the programming concepts relate to one another. You’ll find it
on the inside front cover of this book.
Introduction xxi
Note Notes give you comments and asides about the topic at hand, as well as full
explanations of certain concepts.
Tip Tips provide great shortcuts and hints on how to program more effectively.
Warning Warnings warn you against making your life miserable and help you avoid the
pitfalls in programming.
Code listings are provided throughout the book. Each code listing has a heading, and
these are numbered sequentially within a chapter.
In addition, you’ll find various typographic conventions throughout this book:
• Commands, variables, and other code stuff appear in text in a special mono-
spaced font.
Getting Started
If you’re motivated by the many benefits previously outlined and have assembled the
necessary software, you are ready to begin.
“Part One: Starting Points” provides the information needed to help you get started.
Starting Points
The complexity of the Graphics Editor can introduce many concepts that are 1
unfamiliar to you.
I address as well the likelihood that I write for an audience gathered from a variety
of backgrounds, interests, and experience levels. Therefore, you, the reader, must
2
choose where you enter the text.
You might be comfortable with some of the ideas and disciplines employed by the 3
Graphics Editor project and not with others. However, another reader might have
confidence in areas you have not been exposed to during your pursuits.
Therefore, the first portion of this text provides a variety of starting points. Choose 4
the one that best addresses your needs.
5
Where to Begin
I recommend that you spend sufficient time reviewing the areas that are new or less
familiar to you and perhaps give only a cursory review of the subjects in which you 6
are already confident.
7
What’s at the End
Upon completing this section of the text, I expect all readers to have the same foun-
dation, understand the same vernacular, and be prepared for the next section of the 8
text.
10
11
12
Part I
Absolute Zero
In this chapter (M04)
• The
This man
is styled
Command
M05
• You
Organization
will learn and
amazing
Navigation
things and be
Chapter 1 •
•
•
wowed
You
by the
• The C Compiler
• You
knowledge
willFiles
Object
of Que
learn amazing things
willFiles
Source learn amazing things and be
wowed by the knowledge of Que
• The vi Editor
• If this is longer please ask editorial to edit
• The make Utility
to fit
• System Tools and Useful Commands
• Box size does not get adjusted
• Next Steps
geek Examining the use of man will give insight into the way that most every command in
Linux is formed. The basic syntax follows the pattern:
command -flag(s) parameter(s)
A flag is usually a single letter following a hyphen (-) meant to instruct the com-
mand to alter its behavior. Further, the command acts upon the parameter(s).
Consider an example where man is the command, there are no flags specified, and man
is the parameter to be acted on:
bash[1]: man man
Issuing the command man man results in the man command using its default behavior
to display the man page for itself. Only a portion of the command output is shown in
Figure 1.1; however, it is important that you execute the command on your system
and review the entire output.
Figure 1.1
Sample of the man man
command.
UNIX for Developers Chapter 1 7
Note Commands distinguish between flags and parameters by use of the hyphen (-). A
hyphen indicates to the command that what immediately follows is a flag.
1
You can know what flags or parameters a command accepts by looking at its man
page. Anything placed between square braces ([ ]) in the Synopsis area of a com-
mand’s man page is optional input. Reading the man page for man, you see the flags
acdfFhkKtwW fall within an optional list; however, a topic name does not, and there-
fore must be specified.
If you use man followed by -k, the output is much different (as shown in Figure 1.2).
Instead of getting the man page for a topic, you get a list of pages containing the
string specified.
Figure 1.2
Sample of man -k
output.
EXCURSION
An Alternative to the man -k Command
A special notice of the -k option to man: On some UNIX systems man -k is a separate
command called apropos, which is…well, more appropriate! Although the man command
and its -k flag will always exist, apropos can be an abbreviated option to the man -k com-
mand on your version of UNIX.
The man command is a suitable first command to discuss because it will assist you in
learning other commands to add to your repertoire. Feel free to issue the man com-
mand for any commands or keywords that appear in upcoming sections.
8 Part I Absolute Zero
Note A UNIX family is a branch within the evolutionary development tree of the operat-
ing system (see Figure 1.3). In other words, following the inception of UNIX at
AT&T Bell Labs, UNIX has taken on a life of its own, growing like a tree where
the efforts of the original authors serve as the trunk. Further developed at both
Berkeley and AT&T, every version of UNIX falls under one of these two flavors. A
UNIX family which follows from the work at Berkeley is called BSD UNIX,
whereas those that follow from AT&T are known as SYSV. Linux happens to be a
BSD UNIX that borrowed things from SYSV.
how too
pro nouns it SYSV is pronounced as if it were written System 5.
Figure 1.3
The UNIX family tree. SYS V R4
Solaris
SYS V R3.2
Digital UNIX A/UX
(mac) SunOS
OSF/1 SYS V R3
LINUX SYS V R2
4.4 BSD
Coherent SYS V
Net 1 & Net 2
SYS III
HP-UX
BSD 2.x
Berkeley Software AT&T
Distribution
(BSD)
1969-1979
Computer Research
Group
(Bell Labs)
UNIX for Developers Chapter 1 9
If you want to affect the way your operating system works, you know (based on the
flavor of UNIX) where to find the configuration file needed (see Figure 1.4). Logical
organization and consistency mean you don’t have to search for the correct directory
and guess at the name of the file.
1
Figure 1.4 /
A sample of a UNIX (root directory)
layout.
tmp etc dev var usr lib sbin
How does something with the complexity of an operating system compare to applica-
tion development?
Professional-level software development implies a life cycle: The software has a start-
ing point, it grows (sometimes painfully), it is changed, and eventually it matures.
Even if you are the only programmer to support this cycle, you will have other pro-
jects during the life of this one. The organization and structure of the project should
enable you to find the directory, file, or function you need by a process of induction.
Using induction to find a file or function means that by observing the conventions
speak
used in any piece of the application, you can surmise (guess) the name and location of
geek
the module you are seeking. Contrast this to a deductive process where you would
examine every directory and every file one at a time, stopping only when you find
what you are looking for.
The example of poor project structure illustrated in Figure 1.5 might be an extreme,
but I’ve witnessed similar structures used in real-world projects. Clearly, moving
from one directory or subdirectory to another is arbitrary. There is no clear way to
know which place to go to find anything.
10 Part I Absolute Zero
A a A a A a
Reuse of directory names at multiple points in the poor project structure illustrated
in Figure 1.5 and the seemingly meaningless names chosen make it impossible to
induce the location of a file or function within the project. In fairness, even meaning-
less directory names could contribute to good organization if, for instance, all files
and functions named in the directory were prefaced with the directory name.
After you’ve decided on the basic organization of your project, you are ready to open
your Linux toolbox and begin. The structure of the Graphics Editor project is shown
in Figure 1.6.
gxGraphics.h
gxIcons.h
gxProtos.h
Directories
As George Carlin pointed out, having a place for our stuff is a long-standing concern
of mankind. The age of technology offers relief from this dilemma, however, because
directories provide locations for storing the files created as part of a project. In fact,
directories are the basic building blocks to a project’s structure, allowing you to apply
the organization that was carefully planned.
Note An explicit path is one where every component of the path is spelled out:
mkdir /home/users/jbrown/2d-editor
1
In a relative path, shortcuts are used to specify the location. UNIX provides two
valid shortcuts for your use. They include a single dot for referring to the current
directory (.) and two dots for referring to the current directory’s parent directory
(..). For instance, by using ../ in a directory specification, the path will be rela-
tive to the directory one level up:
mkdir ../2d-editor
In this example, the new directory 2d-editor is placed in the directory above the
current one.
Alternately, the command shell being used can provide other shortcuts.
EXCURSION
What Is a Command Shell?
A command shell (or simply shell) is assigned to every user of a UNIX system. The shell
provides a window into the system, enabling you to issue commands, view the output, and
navigate the file system.
Commands available to a user are grouped into two categories: internal and external.
External commands are provided by the UNIX operating system and will be available to all
users regardless of the shell assigned because external commands reside as executable
files on the UNIX file system.
Internal commands are shell specific, meaning that shells differ in the commands they pro-
vide internally. Generally, internal commands offered by a shell determine its suitability for
scripting as discussed in Appendix A, “Command Shells and Scripting.”
Just as shells differ in the internal commands they understand, they differ in the syntax or
conventions they accept. These differences determine, in part, the shortcuts that the shell
understands.
Table 1.1 provides a brief description of common UNIX shells. A more detailed discussion
of command shells, conventions, and scripting can be found in Appendix A.
continues
12 Part I Absolute Zero
The rmdir command, which stands for “remove directory,” only works if the direc-
tory is empty; this is important to remember. To remove a directory with something
in it, issue the rm -r command:
rm -r /home/users/jbrown/2d-editor
The rm command with the -r flag is one of the sharpest tools in our box! You use the
rm command to remove a file, but when told to be recursive (-r), directories and
their contents are considered for removal as well, even if the directory contains other
directories and those directories contain other directories, and so on. The -r flag
sends the rm command to the lowest level of the directory structure, where it begins
removing everything it finds as it works its way back up to the starting point. This is
illustrated in Figure 1.7.
When using the rm command, you must specify the name of what you want to
remove.
Note The way to specify names for command completion under Linux can be very
flexible when employing wildcards. Of course, this flexibility makes the rm com-
mand even more dangerous.
UNIX for Developers Chapter 1 13
gxGraphics.h
gxIcons.h
gxProtos.h
Unlike other operating systems, Linux has no undo or undelete command for recov-
ering files removed in error. When you unintentionally delete something (notice I
said when and not if ), remember two magic words and utter them over and over
until you get a response from your system administrator. The words are backups. I
guess that’s only one word. You may have to append please to it for it to be magic!
Wildcards for command-line completion use a single character to represent multiple
speak
characters.
geek
Commonly understood wildcards and their functions include:
* Replaces any number of characters (broadest substitution)—for example,
bash[2]: rm ca* expands to remove capable, cannibal, canister, cat, cap,
cal, and cab.
? Replaces only a single character—for example, bash[2]: rm ca? expands to
remove only cat, cap, cal, and cab.
[a-d] Replaces only occurrences of a single letter which falls between the specified
range for example, bash[2]: rm ca[a-d] removes only cab.
Not to minimize the amount of caution that should be used when exercising the rm
command, especially if using wildcards, but a safety net, called permissions, is built
into Linux. A look at permissions follows in the next section on directory listings.
14 Part I Absolute Zero
The ls Command
Requesting a directory listing determines whether anything is in a directory. The
listing command is ls, and it accepts many flags to alter its behavior or output.
The -l flag passed to ls tells it to issue a long listing. A long listing includes infor-
mation about permissions, ownership, modification date, and size. The default out-
put for ls (no flags specified) is to list only the names of the files. Any file with a
period (.) as the first character of its name is considered a hidden file, and you must
explicitly request that ls display such a file by passing the -a flag.
Note When specifying multiple flags to a command, group them together following a
single hyphen. Everything after the hyphen and until the next space is interpreted
as flags and acted on accordingly. Issuing ls -al requests that the listing com-
mand displays all files in a long listing format.
Figure 1.8 shows sample use of the ls command, including examples of the -l and
-a flags.
Figure 1.8
The ls, ls -a, and ls
-al commands.
Notice that when displaying all (ls -a) files in a directory you see . and .. listed.
speak
The . (current directory) and .. (parent directory) are special files Linux uses to
geek
maintain information about these directories. When you ask for a listing of your
current directory, ls displays some of the information contained in the . file.
how too
pro nouns it A . in UNIX is always pronounced as dot.
UNIX for Developers Chapter 1 15
Permissions
Permissions serve several functions within the Linux operating system. They provide
security, safety, and control. However, as with any tool, they must be used correctly. 1
You must give thought to the permissions that are applied to the files and directories
you create and manage.
You can find permissions for a specific file or directory in the first column of a long list-
ing (ls -l) as you saw in Figure 1.8. Several groupings make up the permission field.
Dissecting them into their groupings will make permissions very easy to understand.
drwxrwxr-x 9 jrb jrb 4096 Nov 7 05:32 ObjCntrl
The first character of permissions is the designator field. This character indicates the
type of entry in the listing. By the d in the first column of the example, you know
that the entry is a directory. Other possible characters found in this position are -
(hyphen) when the entry is a file or l when the entry is a symbolic link. I haven’t dis-
cussed links yet but you’ll soon see their usefulness to multi-platform application
development. For now, remember what it means when you see an l in the designator
field of permissions.
drwxrwxr-x 9 jrb jrb 4096 Nov 7 05:32 ObjCntrl
The next grouping to consider in the permissions is the owner access field. As the
name implies, this determines the accessibility for the owner. Clearly, owning the
entry means that as owner you can alter any access field within the permissions.
However, access doesn’t just limit; it also protects. If used properly, you will have a
safety net that prevents accidents.
The first character of the owner access field governs read permission. The presence
of the r in the first column of this field indicates that read access has been granted. If
access were not granted, there would be a - (hyphen) placeholder. The second char-
acter of the owner access field determines write permission, where a w indicates the
capability to write to the file, and a - (hyphen) placeholder means that no write per-
mission is granted. Last is execute permission as seen with either an x indicating
permission to execute or a - for no permission to execute.
Note Having execute permission is not always meaningful. For instance, writing a letter
to a loved one and assigning execute permission to the file does not cause any-
thing useful to happen. The file does not become a command by simply granting
execute permission. (If accomplishing things on a computer were so easy, you
probably wouldn’t need this book.) However, if the file you create is a series of
valid Linux or shell commands, giving execute permission creates a script or
shell script for which execute permission is meaningful. (Shell scripting is dis-
cussed in Appendix A.)
16 Part I Absolute Zero
Following the owner access field is the field governing group access. In addition to a
unique ID assigned to all Linux user accounts, everyone is placed into at least one
group. This grouping is useful for allowing files and directories to be shared.
EXCURSION
What Group Am I In?
You have several ways to determine into which groups you have been placed. For
instance, you can execute the id or the groups command. The output of the id command
provides a lot of information about your Linux account, including the group assignments.
The groups command will simply list the groups to which you are assigned.
The group access field manages sharing within a group. Applying the same
read/write/execute pattern as for the owner access field, you can quickly determine
that in the example anyone belonging to the same group as the group ownership of
this entry may read, write, and execute.
EXCURSION
How to Determine Group Ownership
The SYSV family of UNIX does not show what the group assignment for an entry in a direc-
tory listing is unless you explicitly ask. To request that group ownership be displayed in the
output of the ls command, you must pass the -g flag.
The last field represents permissions that pertain to a collection of users known as
the world. Any user who does not own the entry and is not assigned to the same
group as the group ownership is considered part of the world. Users accessing the
entry based on the merit of world permissions include anyone who shares an account
on the system (and possibly network). For this reason, world permissions are gener-
ally not very giving, as you have no way to know or control those in the world
beyond these permissions.
In the example, those in the world may only read and execute the entry. Note that a
- placeholder resides in the write column, indicating no write access is granted.
Read Permission
Granting read permission enables users not only to view what has been placed into a
file or directory, but also to copy it. In fact, you must have read permission to exe- 1
cute the cp (copy) command successfully.
Write Permission
Write permission allows users to modify any entry for which they have permission to
write. Most importantly, though, having write permission enables users to remove a
file or directory. You must have write permission to execute the rm or mv (move) com-
mand successfully. For this reason, if you are not actively changing a file or directory,
removing write permission also removes your ability to delete a file in error.
Execute Permission
Being able to execute a file, as discussed earlier, means that each of the commands
the script contains is performed as if it were typed at the command line. In the case
of a compiled program, execute permission allows the user to run the program.
However, if the entry is a directory, execute permission is required to change into
that directory. If you do not have execute permission for a directory, it is effectively
closed and the cd command will fail.
chmod
With an understanding of the power and utility of permissions, you must know how
to change them. The chmod command, which stands for “change mode,” allows you
to modify the permissions of anything you own.
how too
pro nouns it The chmod command is pronounced as if it were written change mod.
where:
Item Means
u user/owner
g group
o world/others
a all
+ add
18 Part I Absolute Zero
- remove
r read
w write
x execute
name item to be changed
For instance, the following command changes the permissions of ObjCntrl from the
examples above to add write permission for the world:
bash[5]: chmod o+w ObjCntrl
To remove execute permission for both group and world, you could use the follow-
ing command:
bash[5]: chmod go-x ObjCntrl
The cd Command
A discussion on organization and navigation would not be complete if I didn’t men-
tion something about navigation. The cd or change directory command navigates
around the Linux file system. It expects a single parameter to indicate to which
directory you wish to change. If you execute the cd command without any parame-
ters, you are returned to your home directory.
Note Unlike other operating systems, Linux is case sensitive. When trying to change to
a directory you know exists but Linux disagrees with, check the case because
./src and ./Src are unique names to Linux.
With an understanding of basic Linux commands, you are ready to advance to com-
mands specific to software development.
The C Compiler
The C Compiler, known as cc, translates source code that a programmer has written
into object code that the machine understands.
Source code can be read and written by you, a programmer. It spells out program-
ming language elements such as keywords, variables, start of body markers, and end
of body markers. A computer, however, knows nothing of the letters or characters
that are meaningful to programmers. Instead, the computer expects that when you
UNIX for Developers Chapter 1 19
enter the letter A as part of a program solution, it will be translated into the machine
code equivalent 0100 0001. Accomplishing this translation is the function of the
compiler..
1
EXCURSION
A Closer Look at Machine Code
Machine code is ultimately a collection of 0s and 1s, which is all any machine can under-
stand. At this level the collection of 0s and 1s is represented in the binary numbering sys-
tem. Unlike the everyday decimal numbering system that consists of ten digits (0–9), the
binary numbering system has only two digits, 0 and 1. Use of binary numbering to repre-
sent machine code can be very tedious to humans, so we also use the hexadecimal (16
digits) and octal (8 digits) numbering systems.
Table 1.2 shows various characters represented using the different numbering systems.
Object Files
Stating that the compiler translates the letter A from the source file into the machine
code equivalent for the object file is an over-simplification.
It is true that A is translated to its machine code equivalent, but only if it is a literal.
If it is a keyword, it is translated into an op code (code specifying an operation). If A is
part of a variable name, it is translated into an offset (distance from the value of an
internal control pointer). If part of a function name, A is translated to a jump point
20 Part I Absolute Zero
(distance from the base address of the application to where the function definition
exists). If A is a function parameter, it is translated as an offset of the stack pointer (an
internal control pointer that manages program execution), and on and on.
The complexity of the compiler is boggling, and I’ve only given a very high-level
overview of its role. Entire books are dedicated to the subject of compiler design.
For this reason, compilers are not generally given away for free, except, of course,
the GNU C Compiler (gcc), which I will discuss shortly.
To review, an object file consists of only the machine codes (op codes, jump points, off-
sets, and so forth) translated from what you’ve written in the source file using a high-
level language such as C.
Object files have the suffix .o so that you can distinguish them from files you author.
Any attempt to edit or view the contents of an object file will make it instantly clear
that there is nothing in it that you can affect.
how too
pro nouns it Object files, because of their .o extension, are referred to as dot-oh’s.
Object files are platform specific: You cannot use an object file generated on an x86
PC platform on a platform of a different architecture. This is important to know.
The object file is the translation of what was written in the source file into a format
that the computer understands. Not all computers do things in the same way. If you
had an 80286-generation computer and you now own a Pentium, it is clear to you
that processors are very different. The most notable difference in this example is
speed. Performance, however, is not always a noticeable difference, but when it is,
many factors contribute to it, such as the language set available to the processor or
the manner in which it groups data into internal representations.
The Pentium processor is boasted to be a 32-bit processor, whereas the 80286
processors were only 16-bit architecture. The different number of bits (0s and 1s)
supported by the processor means that the word size (internal bit groupings) will be
different for each. Although a Pentium may be able to understand a 16-bit word size
(grouping), an 80286 can never understand a 32-bit word.
EXCURSION
Computer Processor Instruction Sets
A processor’s instruction set consists of the op codes (operational codes) available to it.
These instructions are internal to the processor and dictate everything the processor
knows how to do. Basic processor instructions include directions for moving data, arith-
metic operations, program flow control, and more.
Instruction sets fall into two categories: Reduced Instruction Code Set (RISC) or Complex
UNIX for Developers Chapter 1 21
Instruction Code Set (CICS)). RISC-based processors know fewer commands than CICS
processors but operate more quickly as a result. In other words, the op codes contained
within one processor’s instruction set may not be the same as those of another processor’s
of a different architecture (and similar doesn’t count). The differences in instruction sets of 1
varying architectures introduce another level of binary incompatibility.
How does all this affect the object files? If you generated an object file on a 32-bit
platform, the calculated offsets to memory for representing variables, for instance,
would not align to valid memory addresses on the 16-bit system, nor would the
jump-to addresses. The distance of the jump would likely be well outside the
intended user memory area on the 16-bit platform. This illustrates, of course, one
manner in which object files are platform specific.
I’ve barely mentioned the internal representation of data by a processor. In addition
to word size (data grouping), some processors expect the order of the bytes (four-bit
grouping) of data to be formatted differently from others.
One method of byte ordering is least significant bit first, called little-endian, and
another is most significant bit first, called big-endian.
Think of little-endian as little end first, big-endian as big end first, and endian as end-ed.
speak
geek
EXCURSION
Waiter, I’d Like To Order Some Bytes.
The determination of which byte order to employ for a given platform’s architecture is
largely a decision of the design engineers. It has occurred in the history of processor
design that the decision to elect one method of byte ordering over another was based
solely on a desire to avoid patent infringement.
Source Files
Previously, I spoke at length on the complexity of an object file as generated by the
compiler. Figure 1.9 demonstrates the compile process with emphasis on the source
files as plain text, meaning that they are understandable to programmers and contain
no word processor type formatting. The figure further illustrates that the output of
the compiler is not in human readable format, but rather in a format that satisfies a
computer’s requirements.
22 Part I Absolute Zero
Figure 1.9
Source File Object File
An illustration of the
compile process.
Source File Object File
Machine Code
Plain Text
Source File Compiler Object File
Knowing the complexity of the compile process that a source file undergoes to be
translated into something the computer can understand should make you appreciate
that the compiler considers every character of a source file. In effect, as the compiler
parses the source file it anticipates the next character or token it expects. If what is
anticipated is not what it reads from the file, a syntax error is issued. For this reason,
the source files must be plain text to prevent the extra control symbols used in word
processing from confusing the very literal compiler.
Even the most basic text formatting performed by a word processor is accomplished
by inserting control characters into the document so that the word processing appli-
cation knows when to turn on and off the formatting. These control characters are,
in fact, characters (not usually printable) that would be considered by the compiler as
input. The choice of editor is important, as is how you choose to save the file if the
editor supports multiple file formats.
An editor is available in all versions of UNIX to ensure that your authored source
files contain only text. This is because it is incapable of word processing functions
such as bolding, italicizing, and underlining.
The vi Editor
A popular choice among UNIX programmers is the vi editor. The fact that it is
readily available on every version of UNIX, it does not require a lot of system
resources, and it is fairly easy to master makes it a solid choice.
Certainly, other editors exist that satisfy the requirement of being able to save source
files without any extra formatting data. A popular editor called GNU Emacs will
enable you to write and save source files as required in addition to telling your for-
tune and generating whimsical quips. However, we must concern ourselves with
availability and system requirements. Emacs is very large and will not always be
installed on the system on which you must work.
UNIX for Developers Chapter 1 23
Note Even if vi is not your choice for an everyday editor, knowing its basic operations
is useful because the day will come when you are sent to a customer site or
asked for help by a colleague and your normal editor is not available. Nothing 1
shatters confidence more than seeing someone not know how to edit a file.
To start vi, simply execute the command specifying the name of the file that you
want to edit:
bash[2]: vi gxArc.c
If the file didn’t exist previously, vi informs you that it was created. When loading an
existing file, vi informs you of the number of characters read.
There are three modes in vi: command mode, last-line mode, and edit mode. vi always
starts in command mode waiting for input. To move from command mode to edit
mode, you must enter an appropriate command. Some commands enabling you to
enter edit mode from command mode are shown in Table 1.3:
After you have entered edit mode using one of these commands, everything you
enter is inserted into the file.
To leave edit mode and return to command mode requires pressing the Esc key.
Other commands available in command mode are listed in Table 1.4.
continues
24 Part I Absolute Zero
Last-line mode of vi is identifiable by the presence of a colon at the bottom left cor-
ner of the window. This colon serves as an indicator of the last-line mode. As with
edit mode, to return to command mode, press the Esc key.
Commands available in last-line mode are listed in Table 1.5.
Learning vi simply takes practice, but these basic commands will get you started.
Now able to create a project structure and edit source files, you are ready to learn
how to manage building a project for multiple platforms.
requires that each one be passed to the compiler for translation into a corresponding
object file. Issuing the C Compiler command (cc) for each source file is a cumber-
some effort prone to mistakes and oversights. I have purposely postponed discussion
of the syntax of the cc command because of its complexity. This complexity, how-
1
ever, may be masked if viewed as a function of the make utility.
The cc Command
Note Under Linux a reference to cc is synonymous with the GNU C compiler gcc. If
you are using another version of UNIX, gcc and cc might not be synonymous at
all but might be two separate commands. Knowing which C compiler will be used
is critical because how you construct the command line to invoke them is very
different. For instance, gcc understands the flag -W as a means of determining
the level of warnings that the compiler issues. Other C compilers have different
flags for specifying warning levels. You pass gcc -ansi to instruct it to be ANSI
C-compliant, but the Sun WorkShop Pro compiler expects a -Xn (where n is a
directive for how ANSI C-compatible you want it to be).
EXCURSION
ANSI C Versus K&R C
The C programming language was written by Dennis Ritchie at AT&T in the early 1970s.
With Brian Kernighan, he continued its development under what became known as K&R
C. Unfortunately, some of the language details were unspecified, ambiguous, or incom-
plete. In 1983, the ANSI committee formed, and five years later C was standardized in
what is now known as ANSI C.
The understanding to be taken from this discussion is the complexity involved when
invoking a C compiler. If you use only Linux, the task is greatly simplified because
you only must learn the gcc command and the flags and parameters that it under-
stands. If your goal is to write software that is compatible with any family of UNIX,
you must recognize compiler differences and how they affect the configuration of the
make utility.
With an appreciation for the fact that there are differences in C compilers, we focus
now on the gcc command, identifying what it has in common with other C compil-
ers and contrasting differences where necessary.
gcc -c
The syntax of the gcc command follows most other UNIX commands with a few
exceptions. As with other commands you’ve seen, gcc accepts flags that instruct it to
26 Part I Absolute Zero
alter its behavior. Common to all C compilers is the -c flag, which tells the compiler
to generate the object file.
With the command
bash [3]: gcc -c gxArc.c
Modules such as gxColr might be invoked in one source file but actually defined in
another. Notice in gxArc.c in Figure 1.10 that the variable color is assigned the
result of module gxColr; however, the definition of gxColr is contained in
gxGraphics.c. After the compiler converts the source code to object files, gxArc.o
does not know the actual jump-to address for finding the gxColr module. Another
task of the compiler is to link these references together so the program flows without
dead ends. In gxArc.o, the jump-to address for gxColr is considered unresolved.
Objects must go through the linker phase of compiling to determine what the exter-
nal jump-to points should be.
UNIX for Developers Chapter 1 27
Note A source file invoking a function (module) contained in another source file cre-
ates an external dependency. If, at the end of the link phase, any external
dependencies are left unresolved (meaning that invocations from one file could 1
not be matched up with definitions in another), the program will not successfully
link. The error reported will be unresolved externals.
EXCURSION
A Program Must Know Where It Is Going
When a program invokes a function that doesn’t exist or can’t be found, the result is a bus
error. It is similar to getting on a bus to go somewhere and finding out that the address
you were given was incorrect. Often, bus errors are a result of corrupted memory. Rather
than the function not existing, which is something the linker must validate, the address
given as the jump-to was corrupted during execution and therefore the function couldn’t
be found.
gcc -o
Knowing that the -c flag passed to gcc instructs the command to stop after creating
the object file, another flag common to all C compilers is the -o flag. The -o flag
must be followed by a filename because it instructs the command what to name the
output.
bash[4]: gcc gxArc.o gxGraphic.o -o 2d-editor
By convention, the name of the object file is the same name as the source file, differ-
ing only by the extension. However, if you didn’t instruct the compiler to stop after
creating the object files and allowed it to continue through the link phase to produce
an executable, you would certainly want to give the executable a name. Without
-o filename, the compiler would name the program a.out instead of something
meaningful.
gcc -g
Another interesting flag instructs gcc to provide debugging information in the
objects that it creates. The -g flag is known as the debug flag. Without it, the com-
piler streamlines the object data by stripping out information that a debugger would
need to enable you to evaluate variables, set breakpoints, and examine program flow.
gcc -W
As mentioned earlier, the -W flag that instructs gcc what level of warnings to issue
while compiling the source code is a flag unique to gcc. Invoking gcc with -Wall
requests the most stringent level of warnings and aids in the prevention of some pro-
gramming errors.
28 Part I Absolute Zero
Note Programmers introduce three types of errors when writing source code: syntax
errors, semantic errors, and logic errors.
The first level of errors is syntax errors, which the compiler easily finds. Syntax errors
are created when you fail to obey the specifications of the language. For instance, in
English, failing to place a period at the end of a sentence is a syntax error.
The second type of error is a semantic error, which occurs when you don’t say what
you mean to say. The -Wall flag passed to gcc is a great aid in discovering some
semantic errors, because gcc analyzes context and usage as well as syntax.
Unfortunately, the third type of error, called logic errors, can only be found by
exhaustively testing the program.
gcc -D
The -D flag is common to all compilers. This flag requires that a directive immedi-
ately follow it. This directive is then defined as a constant throughout the source file.
Examples of how to employ compiler directives are provided in the next chapter.
bash[5]: gcc -g -ansi -Wall -c -DUSE_COLOR gxArc.c
As you can see, the examples for invoking the gcc command are becoming progres-
sively more complex. In the preceding example, you are instructing gcc to include
debugging information (-g), enforce the ANSI coding standard (-ansi), report all
warnings (-Wall), stop after generating the object file (-c), and define the directive
USE_COLOR. All of this is for the single source file gxArc.c.
The -I, -l, and -L flags are the last flags to consider before looking at how the make
utility simplifies use of the gcc command.
gcc -I
The -I flag tells gcc where to find header files. Immediately after the -I flag you
must provide a directory path that gcc will follow to find the header files that the
source code has included.
Note Like the gxColr function shown in Figure 1.10 to illustrate external dependen-
cies, not all functions invoked in a source file will be present in the same file.
These external function references must have a declaration before use; other-
wise, the compiler makes assumptions about them that may not match with the
actual definition. The declaration of a function before its definition is called a for-
ward declaration or prototype and is generally placed in a header file. A header
file is a source file that ends with a .h extension.
UNIX for Developers Chapter 1 29
how too
pro nouns it Following their naming convention, header files are called dot-h files.
1
Because header files can be placed almost anywhere the author deems appropriate,
use of the -I flag instructs gcc where to search for them.
Because header files can be used to prototype different functions from different
libraries (packages), multiple -I flags may be passed to gcc.
bash[6]: gcc -g -ansi -Wall -c -I ../src/include -I /usr/X11R6/include gxArc.c
This example instructs gcc to look in two places, ../src/include and /usr/X11/R6/
included, for the header files that are included in the source file gxArc.c.
gcc -l
The -l flag tells gcc what extra libraries to include in the link phase of compiling.
Using again the example of gxColr from Figure 1.10, not all functions that you
invoke need to be in the source file where you use them. Further, they don’t have to
be in any source file that you author.
Groupings of functions for a general purpose need only be written once. After they
are written, they may be included in a package or library for others to use.
For instance, if you want to find the square root of a number in C, you don’t have to
write the function to do it. Because it is such a common requirement, the authors of
the C language have assembled a library of mathematical functions for your use. You
can include in your source file the math.h header file which prototypes the square
root function sqrt. Then, so the link phase of the compiling process knows how to
resolve the external dependency, you must include the math library (libm.a) by use
of the -l flag passed to the gcc command.
bash[7]: gcc gxArc.o gxGraphics.o -lm -o 2d-editor
EXCURSION
How to Determine Libraries Names for Use by gcc
The gcc command expects that libraries specified with the -l command follow the naming
convention libname.a.
This explains why, in the previous example, gcc understands -lm to be libm.a (the math
library) containing the definition of the sqrt function.
30 Part I Absolute Zero
As with -I, multiple -l flags may be necessary if you are calling functions from sev-
eral different libraries.
bash[8]: gcc gxArc.o gxGraphics.o -lm -lXaw -lXt -lX11 -o 2d-editor
Based on your newly acquired knowledge of the gcc command, you should be able to
determine what is happening in the previous example. The command gcc is being
invoked with the files gxArc.o and gxGraphics.o. (As these are already object files,
you must assume that at some point prior to this command gcc was invoked with the
-c flag for each of the related source files gxArc.c and gxGraphics.c.) Further, gcc is
being instructed to consider three libraries for resolving external dependencies
(libXaw.a, libXt.a, and libX11.a). Lastly, the command specifies that the output
should be named 2d-editor (-o).
gcc -L
When instructing gcc to include libraries such as libXt.a or libX11.a, you must also
tell gcc where to find them. Informing gcc of the location of libraries is done with
the -L flag:
gcc gxArc.o gxGraphics.o -L /usr/local/lib -L /usr/X11R6/lib -lm \
-lXt -lX11 -o 2d-editor
Having only touched on the more commonly employed flags and parameters used
with gcc, it is easy to see how cumbersome the command line could become as direc-
tives, header paths, and more and more source files are added to the project. Of
course, needing to support multiple platforms where the locations of the libraries
and header files differ and where the compiler flags vary would be almost impossible
to do by invoking the gcc from the command line.
The make utility enables you to automate the invocation of the compiler (gcc) for
every source file in your project. Further, make enables you to create dependencies or
conditions that will force object files to be updated only when necessary. It also
enables you to assemble the necessary parameter lists for the gcc command based on
the operating system and platform being used. Only with the flexibility of make will
you efficiently structure support for multi-platform development.
In general, make is a scripting language. It enables you to define variables, make deci-
sions, and execute commands. The most obvious command for make to execute, of
course, is the cc (or gcc) command. The uses of decisions within make support the
task of multi-platform development. If you want the project to build under Linux and
Solaris, it will be necessary to tell make the environment differences and let make
decide which to employ based on the current platform. It is really not complicated,
as you’ll see with your first make script.
UNIX for Developers Chapter 1 31
Makefile
When invoking the make utility, you must provide it with a configuration file. If no
configuration file is specified on the command line, make looks for the presence of a 1
default file called Makefile. If you are using the GNU make utility gmake, the default
file will be named GNUmakefile.
Note Just as GNU has its own C compiler, it also has its own make utility called gmake.
Under Linux the make and gmake utilities are synonymous; however, other ver-
sions of UNIX have a different version of make. In other words, gmake is not part
of the standard UNIX environment, but it can be added to any system.
Differences exist between GNU’s gmake and the make utility available to other
versions of UNIX. For instance, gmake will look for a configuration file called
GNUmakefile. If gmake doesn’t find a GNUmakefile, it will look for a
Makefile. The make under UNIX, however, only considers Makefile. Also,
gmake is a much more capable utility than the standard version of make. Either
by employing the capabilities of gmake above make, or by choosing a configura-
tion filename of GNUmakefile, you can impose use of gmake as a requirement
for building your project. Because gmake is easily available from the Internet and
is a superior make utility, I generally do require it.
When creating your make utility configuration file GNUmakefile, several areas must
be addressed to construct it properly. Listing 1.1 contains the GNUmakefile that will
be used to build the Graphics Editor project.
Examine the listing carefully, identifying the fields and components discussed during
the coverage of the gcc command.
continues
32 Part I Absolute Zero
17:18:
19: make-target: $(PROGRAM)
20:
21:
22: $(PROGRAM): $(OBJS)
23: @echo “Building $(PROGRAM) for $(TARGET)...”
24: @(CC) -g -o $(PROGRAM) $(OBJS) $(X11LIB) $(LIBS)
25: @echo “Done”
26:
27:
28: #
29: # end of GNUmakefile
30: #
Comments
As with any program that you write, including comments is the courteous thing to
do. Whether for your fellow team member who must follow behind you, or for your-
self when returning to the code six months later, comments can be critical in effect-
ing something quickly because the clues they provide guide the analysis of what is
required.
In the Makefile syntax, a pound sign (#) is the comment token. The make utility
ignores anything following a pound sign on a line.
Variables
Variables provide a means of managing the content of your GNUmakefile by enabling
you to group and define items that are later assembled into more complex entities.
Further, make configuration files grow in size and complexity proportionately to a
project. Some elements of the GNUmakefile are repeated for varying conditions and
command assembly. The use of variables allows for a single definition of an element
that can be used multiple times throughout the file. With the use of variables, modi-
fying an element does not require that you search and replace its every occurrence.
Instead, you only have to change the assignment to the appropriate variable.
In Listing 1.1, variables are identified by the equal sign that separates the variable
name from the variable value. By convention, variable names within a Makefile are
capitalized. Following this convention in your own make configuration files will help
others decipher what you write.
Notice that the variables defined in Listing 1.1 can have a value consisting of a single
string, as in
8: PROGRAM = 2d-editor
UNIX for Developers Chapter 1 33
The make utility expects the carriage return/line feed entered when you press Enter
1
to serve as the end-of-line marker. When assigning values to variables, this end-of-
line marker implies end of input. Therefore, values of variables consisting of multiple
strings must be viewed as if they were contained on a single line. In the case of the
variables LIBS or CFLAGS in the listing, this isn’t a problem. However, the value of
OBJS is moderately large and would wrap to multiple lines if we allowed it.
Wrapping multiple lines is syntactically correct because you haven’t pressed Enter
(interpreted as end of input); however, it reduces readability and makes it more diffi-
cult to edit the field later. To indicate to the make utility that the items within the
value of OBJS are on a single line, delimit the carriage return/line feed with a back-
slash (\) character.
Delimiting instructs the make utility to ignore what immediately follows the back-
slash by not interpreting its meaning. The carriage return/line feed is still present,
but it is not considered an end-of-line marker when preceded by the delimiter:
11: OBJS = gxMain.o \
12: gxGraphics.o \
13: gxLine.o \
14: gxText.o \
15: gxArc.o \
16: gxGx.o
You should now be comfortable with creating and assigning values to variables. To
use a variable you’ve created, a unique syntax is necessary; this unique syntax allows
the make utility to distinguish your intention clearly.
Employing a variable requires that it be prepended with the dollar sign ($). In this
way, the make utility knows that you want to reference the value of the variable and
not the name of the variable. Also, because the variable may have a value with nested
spaces, you must surround the variable using parentheses to indicate the entire value
should be treated as an entity. A reference to
$(LIBS)
Targets
The use of targets within the make utility syntax enables you to have multiple entry
points into the script. The power that this provides enables programmers to support
multiple requirements from the same GNUmakefile.
34 Part I Absolute Zero
For instance, compiling the project is a key function of the make utility and the rea-
son you construct a GNUmakefile. Once the project is built, however, you may want
to create a target within your GNUmakefile to install the project. You may want to
move it from the user account level where only you (depending on permissions) can
use it to a level where everyone on the system can benefit from it.
EXCURSION
You Know You’re a Geek When…
The Towers of Hanoi is a classic programming problem within computer science used to
demonstrate computing power and speed.
The legend is that there are monks in a cave somewhere with wooden discs stacked on
one of four pegs. The discs decrease in size with the largest on the bottom and the small-
est on top (see Figure 1.11). The monks must move the discs from a peg on one side to
the peg on the other following these simple rules:
Figure 1.11
The Towers of Hanoi.
The telling of the legend ends by stating the number of discs that must be moved and
identifying that once the task is complete, the world will end.
I once met someone who accomplished programming a solution to the Towers of Hanoi by
using only the make utility syntax.
and
22: $(PROGRAM): $(OBJS)
UNIX for Developers Chapter 1 35
Targets are identifiable by a label followed by a colon (:). The first target encoun-
tered by the make utility is considered the default target. When make is invoked, if no
target is specified, it will begin at the first one it finds.
1
Issuing the command
bash[9]: gmake
causes the make utility to look for the file GNUmakefile and when found, to begin exe-
cuting at the first target defined within the file.
Whereas, the command
bash [10]: gmake 2d-editor
causes the make utility to look for the target 2d-editor within the GNUmakefile and
begin execution there.
Remember, the make utility sees $(PROGRAM) as its value 2d-editor.
The make utility considers anything on the same line, following the target label, to be
a dependency of the target. Because a dependency must be resolved first, make evalu-
ates all dependencies before evaluating the body of the target. Based on what the
dependency is the evaluation may vary.
For instance, in Listing 1.1, a dependency to $(PROGRAM) exists for the target make-
target.
The dependency declared for $(PROGRAM) is $(OBJS). Because $(OBJS) is not a target
defined within this GNUmakefile, the make utility uses built-in rules to evaluate
$(OBJS).
EXCURSION
Generating Object Files from the make Utility’s Internal Rule Set
The built-in rule governing object files is in fact a target internal to the make utility. The tar-
get uses the base name of the object files as expanded from the $(OBJS) variable.
Effectively it says, in order to get a .o (dot-oh), look for a .c (dot-see) of the same base
name and run gcc -c against it. If a .o already exists, the time stamp of the associated .c
is compared and gcc -c is run only if the .c is newer than the existing .o. Whew, what a
mouthful!
36 Part I Absolute Zero
A target body begins on the line immediately following the target label. Each line of
the body is prefaced with at least one tab character. The body ends when make finds a
line not beginning with a tab.
Note The use of tabs and spaces can be confusing to a new programmer. The places
where make syntax allows a space or expects a tab varies, depending on which
part of the make configuration file you are in. For instance, when writing a target
body, a character that is not a tab as the first character on a line is seen as ter-
minating the body. However, when assigning a value to a variable and delimiting
the end-of-line marker so the value may span multiple lines, consecutive lines
may begin either with a space or a tab.
In following the flow of make in Listing 1.1, you see that when make-target is called,
the dependency $(PROGRAM) is immediately evaluated. (As make-target does not have
a body, you can assume that its sole purpose is to ensure that $(PROGRAM) is evalu-
ated.) When the make utility evaluates $(PROGRAM), it sees that $(PROGRAM) depends
on $(OBJS). The make utility then invokes its internal rule to create the files specified
in the value of $(OBJS). When the files in $(OBJS) have been created (or updated),
make is ready to evaluate the body of the $(PROGRAM) target.
The first line of the $(PROGRAM) target body is the echo command.
23: @echo “Building $(PROGRAM) for $(TARGET)...”
24: @(CC) -g -o $(PROGRAM) $(OBJS) $(X11LIB) $(LIBS)
25: @echo “Done”
26:
A standard UNIX command, echo simply prints everything that follows on the same
line to the screen. Notice that in the $(PROGRAM) target body, the echo command is
preceded by the @ sign.
23: @echo “Building $(PROGRAM) for $(TARGET)...”
The @ symbol before a command tells make not to print the command to the screen
before executing it. When used, the @ symbol forces make to be silent and simply run
the command.
Continuing with the $(PROGRAM) target body, line 24 instructs make how to assemble
the cc command for the last phase of compiling called the link phase.
24: @(CC) -g -o $(PROGRAM) $(OBJS) $(X11LIB) $(LIBS)
Remember that the $(OBJS) dependency to $(PROGRAM) ensured that all the .o files
were constructed or updated. The last step is to link all the object files together and
resolve any external dependencies.
UNIX for Developers Chapter 1 37
In assembling the cc command, some variables are used that you haven’t yet seen (CC,
TARGET). To discuss them, we’ll look at an internal make utility command called
include.
1
include
With large-scale, professional-level development, source files will almost certainly be
grouped by common purpose and located in separate directories from source files of
differing purposes. Each directory will have its own make configuration file because
the object file list ($(OBJ)) will differ for each directory, as can the external depen-
dencies or library considerations.
When faced with a project structure of many directories or multiple GNUmakefiles, it
is not necessary to repeat variable declarations for each of the make files within your
project. Elements common to the entire project can be placed at a central location
and then included with the include command into each of the project’s make files.
Note The term make file refers to GNUmakefile or Makefile (or any make configu-
ration file). If you don’t use one of the default make filenames (GNUmakefile or
Makefile), make must be told the name of the file. This is done by specifying
the -f flag:
bash[12]: gmake -f SomeMakeFileName
Refer to Figure 1.6, Graphics Editor project layout, and notice the file make.defines
in the 2d-editor directory. This is the same make.defines file included by the
GNUmakefile in Listing 1.1.
It is the common definition file for variables and functions global to the entire
project.
When you specify a file for inclusion into a make file, the make utility inserts it exactly
as if it were typed in at the place of the include command. There is no limit to the
number of files that can be included into a make file, nor is there a limit on the num-
ber of times a file can be included.
make.defines
Analyzing the make.defines file shown in Listing 1.2 gives you a lot to consider.
However, as we break it into parts, you’ll see how easy it is to understand.
Remembering previous discussions of the gcc command will help identify compo-
nents of the make.defines file that satisfy the required elements of gcc.
38 Part I Absolute Zero
continues
UNIX for Developers Chapter 1 39
Identifying the pound sign (#) as the comment token in make file syntax enables us to
skip down to line 11 of Listing 1.2 to begin the discussion.
Beyond the comments, the file begins with a series of variable declarations. Because
you should now be comfortable with declaring variables and assigning them values,
this is review.
40 Part I Absolute Zero
This is consistent with the comment at the beginning of the make.defines file:
6: # Dependencies on the following environment variables:
7: # TARGET = machine-os
The environment variable TARGET must be set for the make.defines file to work.
Fortunately, make file syntax enables a test of this dependency.
EXCURSION
Promoting a Variable to an Environment Variable
An environment variable is much like a variable set within a make file. However, the vari-
ables set within a make file are visible only to the make utility. The issue of visibility is known
as scope and will be discussed at length in the next chapter. In the meantime, any of the
variables that you’ve defined in the GNUmakefile can be set in your environment. If
removed from the make file and placed in your environment, the variable is available (visi-
ble) to every command and utility and is then called an environment variable.
Some environment variables are provided by the operating system. For instance, to see
what command shell you are running, echo the environment variable SHELL:
bash[13]: echo $SHELL
To see all environment variables currently set, use the env command. Notice the environ-
ment variable path in the env output. The path variable informs the command shell of all
the places to search for external commands.
(See Excursion “What Is a Command Shell” under section on “The mkdir Command” for a
description of internal verses external commands, page 11.
We don’t want just any variable set as an environment variable, so choose the variables
carefully in order to not clutter the environment.
Knowing which command shell you are currently using will enable you to use the correct
syntax for setting an environment variable called TARGET. Table 1.3 is a listing of these
shells and syntax.
As discussed previously, the first target (a label followed by a colon) which the make
utility encounters is the default; it is executed if no target is specified on the com-
mand line.
1
Notice from Listing 1.1 that the include command is the first line (beyond com-
ments) of the GNUmakefile. Therefore, any target found in make.defines precedes
any target found in the GNUmakefile. As you look through make.defines, notice the
target called all.
64: all: make-env-check make-target
This target has two dependencies that are also targets. The first to be evaluated is
the make-env-check target, which ensures that the TARGET environment variable is
set. It does this with the keyword ifndef, which stands for if not defined. The ifndef
must be followed by a variable name so that the make utility knows what to test for
definition. Because the ifndef implies a conditional body (what to do if not defined),
the body must have an end. The ifndef end-of-body marker is the keyword endif.
Note To create conditional bodies in the case that a variable is defined, use the ifdef
keyword. It is used exactly like the ifndef keyword, except that the body is
acted on when the variable exists in the environment instead of when it doesn’t.
The GxHOME variable ensures that the GxSRCDIR variable is set correctly. The GxSRCDIR
variable enables us to store the source files and object files in different places. This
flexibility is crucial in multi-platform development because every platform will need
to generate private copies of the object and executable files.
42 Part I Absolute Zero
Note If necessary, review the section “Object Files” for a description of object file
incompatibility between platforms, page 19.
Refer to Figure 1.6 where the i86-Linux directory for storing object files is separate
from the src directory. Similarly, you could have an independent directory for every
platform or operating system you intend to support, as illustrated in Figure 1.12.
2d-editor/
Figure 1.12
Project structure with
multiplatform support. src/ include/ i86-linux/ i86pc-Sun0s/ sun4m-SunOs/ GNUmakefile
make.defines
gxMain.o gxMain.o gxMain.o gxMain.o
gxGx.c vfonts/ gxGx.o gxGx.o gxGx.o
gxGraphics.c gxGraphics.o gxGraphics.o gxGraphics.o
gxArc.c gxArc.o gxArc.o gxArc.o
gxText.c gxText.o gxText.o gxText.o
gxlLine.c gxLine.o gxLine.o gxLine.o
GNUmakefile 2d-editor 2d-editor 2d-editor
gxGraphics.h
gxIcons.h
gxProtos.h
EXCURSION
Building a Project for Multiple Architecture
The make file structure we’ve evaluated for the Graphics Editor project will support the
platforms shown in Figure 1.12. Through the use of the TARGET environment variable and
its successful comparison to one of the environments defined in the make.defines file
11: TARGET_SPARC_SUNOS = sun4u-SunOS
12: TARGET_i86_SOLARIS = i86pc-SunOS
13: TARGET_i86_LINUX = i86-Linux
The separation of object files from source files is possible through the use of the
GxSRCDIR variable. After the location of the source files is determined, the variable
vpath can be set for the .h and .c files of the project.
The vpath variable in the make.defines works much like the path variable in your
environment. Just as the path variable instructs the command shell where to search
UNIX for Developers Chapter 1 43
for commands, the vpath variable tells the make utility where to search for source
files.
See Excursion “Promoting a Variable to an Environment Variable” for a description of the path 1
variable, page 40.
The vpath variable informs the make utility where to look when it needs to find a .c
(represented as %.c) or a .h (%.h) file.
21: vpath %.h ${GxSRCDIR}/include
22: vpath %.c ${GxSRCDIR}
This way, the .c and .h files that make needs do not have to be present in the same
directory where the make command is executed.
By creating a directory of the same name as the TARGET value, you can issue the make
command from within this directory having the results stored in this platform-
specific directory separated from the source code.
One last detail necessary to make this separation of object files and source files work
is the fact that the GNUmakefile must be visible in each of the object directories. It is
the GNUmakefile and its inclusion of make.defines that establishes the necessary tar-
gets, defines the $(OBJS) file list, and assigns the pertinent vpath values.
To accomplish having the GNUmakefile visible to each of the object directories, you
could use the cp (copy) command and place a copy of the GNUmakefile in each direc-
tory needing it. But what happens when you need to modify the GNUmakefile? You
would either have to repeat the modification in every place where you have a copy of
the file (supposing you could remember them all) or you’d have to re-copy the file to
every directory.
In UNIX there is a way to make a single file visible in multiple places. Changing the
file at any place where it is visible is the same as changing it at its actual location.
The mechanism for making this possible is called a symbolic link.
The ln -s Command
A symbolic link can be a pointer to a file or a directory. The power of symbolic links
is that they are treated exactly like what they point to. In the case of files, you can
edit them with the changes getting stored in the actual file.
To create a symbolic link, use the following command:
bash[14]: ln -s /home/jbrown/2d-editor/src/GNUmakefile \
/home/jbrown/2d-editor/i86-Linux/
This places a pointer to GNUmakefile in the i86-Linux directory. You can then
change (cd) to the i86-Linux directory and execute the gmake command. The com-
mand will find the GNUmakefile as if it were located there.
44 Part I Absolute Zero
Note You can use the link command (ln) against directories as well as files. The syn-
tax for the command supports relative or explicit paths. The basic syntax for cre-
ating a symbolic link follows the form
ln -s what where
Specifying what you want to link (what) is always necessary. However, if the
where is the current directory maintaining the same name as what, you can
omit the where designator. As always, see the man page for a complete descrip-
tion of the ln command.
You’ve now completed a close look at the elements required to create, edit, and build
a project under the Linux operating system. However, other commands and utilities
are available under Linux that are not required but will give you an advantage when
accomplishing your development tasks.
When a match is found, grep returns (prints to the screen) the entire line that con-
tained the match. Use of the -n flag helps in reading the output because it instructs
grep to include the line numbers where the matches were found.
As mentioned earlier, UNIX is case sensitive. This sensitivity extends to the grep
command as well. However, unlike UNIX you may instruct grep to suppress this and
perform caseless searches. Passing a -i flag to grep instructs that case should be
ignored.
A common command used in a development environment is
bash[16]: grep -i something *.[ch]
that requests grep to perform a caseless search (-i) on all .c and .h files for something.
UNIX for Developers Chapter 1 45
This command is also useful when using grep is filtering out what you don’t want to
see. This can be done with an inverse search (-v flag) in which you tell grep what not
to print, or in other words, what to ignore.
1
An example of when an inverse search would be useful is in searching for the string
man where grep returns multiple lines from the input that includes the string manual.
To instruct grep not to return the lines containing manual, you would issue the fol-
lowing command:
bash[17]: grep man myfile.txt | grep -v manual
Very important to this example is the symbol linking the output of the first grep
command to the input of the second. This symbol is called a pipe (|), and it is an
extremely powerful feature of UNIX because it provides a way of chaining com-
mands together.
The result of the preceding example is that grep searches myfile.txt for every
occurrence of the string man. As I pointed out, the output also contained multiple
lines with the string manual, which was undesirable. So the output of the first grep is
made the input of the second grep by use of the pipe, allowing an inverse search of
the string manual on the output of the first command. The inverse search removes
any line from output of the first grep containing the string manual. What you are left
with (hopefully) following the second grep are only occurrences of the string man.
Now that you’re comfortable with the use of the grep command, let’s focus a
moment longer on the pipe feature of UNIX.
The capability to take the output of one command and make it the input of another
is called redirection. Through redirection, the pipe symbol (|) can chain together two
commands, as seen in the preceding example.
A very useful application of redirection is the more command. When the output of a
command or file is too large for the window, you can ask UNIX to break it into
window-sized pieces.
bash[19]: ls -l | more
or
bash[20]: more myfile.c
It is also possible to redirect the output of a command to a file. The > (greater than)
symbol works much like a pipe, but instead of sending the output of one command
to another, it sends the output to a file. If the file doesn’t exist, it will be created.
bash[18]: grep man myfile.txt > grep.output
46 Part I Absolute Zero
If the file already exists, its contents will be overwritten with the results of this com-
mand. To ensure that nothing is lost, you could redirect the output of grep so that it
appends the file by using >> (two greater than signs):
bash[19]: grep man myfile.txt >> grep.output
A common way that developers use the power of redirection is to save the output of
the gmake command so that reported errors are recorded in a file:
bash[20]: gmake > make.out
From starting point, the find command searches through the entire directory tree
for a condition of some value, and when satisfied it executes the action.
The find command requires that you provide a starting point that is a valid direc-
tory in your file system.
The conditions which find understands are numerous. The simplest is -name. As
given in the command syntax, a condition must have a value. The -name needs the
name of a file or directory to search for:
find /home -name readme.txt ...
Wildcards can be used to specify the value for the name condition; however, when
using wildcards the value clause must be contained in double quotes (“). Otherwise,
the command shell will interpret the wildcards, and the find command will never see
them.
find /home -name “*read*” ...
Knowing that find, like all of UNIX, is case sensitive, you must explicitly assign the
value string to the characters you want find:
find /home -name “*[Rr][Ee][Aa][Dd]*” ...
UNIX for Developers Chapter 1 47
This would find README as well as readme or any combination of the two (such as
ReaDme).
Having properly formed the starting point for the find command, the condition 1
and value to search for, the only remaining element is instructing find what to do
when the search is successful.
Current UNIX versions will automatically perform the -print action if no other
action is specified.
bash[20]: find /home -name “*[Rr][Ee][Aa][Dd]*” -print
The find command understands several other actions. One very useful for software
developers is the -exec command. This instructs find to execute a command against
the matches found. Clearly, in conjunction with -exec, you must tell find what com-
mand to execute.
find /home -name “*.[ch]” -exec grep MyFunc {} \;
Next Steps
With a few Linux commands under your belt, an understanding of the compiling and
linking processes, and an introduction to the vi editor for creating source files, you
are ready to begin looking at programming constructs. Chapter 2, “Programming
Constructs,” will lead you through structuring solutions to software problems by
applying common programming constructs and logic processes.
In this chapter (M04)
• Decisions
This is styled M05
• You
Loopswill learn amazing things and be
Chapter 2 •
•
•
•
wowed by the knowledge of Que
• Functions
You
Datawill learn amazing things
You
Nextwill learn amazing things and be
Steps
wowed by the knowledge of Que
• If this is longer please ask editorial to edit
to fit
• Box size does not get adjusted
Programming Constructs
Learning to properly define the problem to be solved in a software application is an
unavoidable first step of computer programming. The second step, applying the cor-
rect construct to effect the solution, lies at the heart of software engineering.
Identifying and solving a problem at this level is different from the role of a systems
analyst in the software development process. Systems analysts are concerned with
the design method, feature list, and milestones of an application.
As a software engineer, you are continually challenged with accomplishing the next
portion of the application. Perhaps the task is to read a configuration file and initial-
ize a variable set accordingly. Instantly, you should begin thinking about loops, deci-
sions, functions, and data.
This chapter focuses on modeling problems by the programming construct most
often employed to solve them. Learning syntax and convention is required to add a
new computer language to your skill set; however, programming constructs are con-
stant throughout computer programming. As the boss likes to say, “No one is
inventing a new bubble sort.” This means, of course, that there is an appropriate
way (construct) for solving specific problems when writing software. Let us review
those now.
50 Part I Absolute Zero
EXCURSION
Do People Really Sort Bubbles?
A bubble sort is a method of ordering data with neighboring data elements in a list com-
pared as the list is traversed. If the element closer to the beginning of the list is greater
than its neighbor, the elements are swapped. After multiple iteration, the smaller values
bubble to the top (beginning of the list). The list is sorted when nothing is swapped during
a traversal of the list.
Decisions
A decision tree begins with a question where the answer may present further ques-
tions or an immediate end to the tree.
EXCURSION
Do You Have Apple Pie?
“Great, I’d like apple pie a la mode with vanilla ice cream, and I’d like the pie heated. If
you don’t have vanilla, I’d like whipped cream but only if it is fresh. If it is not fresh, then
nothing. Only the pie, but then not heated.”
(“When Harry Met Sally,” Metro Goldwyn Mayer, 1989)
Figure 2.1 is an example of how to approach decisions, as each part of the process has
significant impact on what happens next. Failure on the part of the programmer to account
for a choice or condition required by the user can result in the application having a short
life span.
Decisions create execution branches, control loop conditions, and dictate overall program
flow. With the advent of object-oriented and event-driven programming methods, the
model of purely sequential program execution was replaced. New methodologies compli-
cate decision trees, because applications must be resilient enough to account for any cir-
cumstance at any moment.
Programming Constructs Chapter 2 51
Figure 2.1
Apple Yes Yes Serve
Sample decision tree. Start
Pie?
Vanilla? Heat Pie
Pie!
No
No
Fresh Yes
Cream? 2
No
Error
EXCURSION
Creating a State Machine from a Decision Tree
Referring again to Figure 2.1, each yes branch could be labeled with a number to indicate
the tree’s state. Clearly, the action of heating the pie is only valid if we reach the state
where it is allowed. This is a function of the decision construct.
The if Statement
The if statement represents a basic decision. The syntax of if follows the pattern
if condition
action
52 Part I Absolute Zero
The if statement always performs the action based on an evaluation of the terms of
the condition resulting in true.
The evaluation of a condition in the C programming language results in true for any
speak
Notice that when the condition being tested is negated, if still takes the action based
on successful evaluation of the terms of the condition:
if condition equals false
action
In other words, when it is true that the condition is false, if performs the action.
EXCURSION
A Stylistic Note…
Programmers commonly put the negative condition before the positive, directing to test for
the negated or condition equals false before testing for the true. Although semanti-
cally and syntactically correct, it is less readable for anyone who might follow behind.
Types of Conditions
Let us focus for a moment on what is referred to as the condition being evaluated by
the if statement. Because conditions depend upon the data being evaluated, it will be
necessary to look at each type separately.
Programming Constructs Chapter 2 53
Numbers
When forming test conditions for decisions, a variable representing a number can be
evaluated in many ways. For instance, evaluation can determine if the number is
equal to zero:
if ( number == 0 )
action 2
Note Notice that I’ve grouped the condition using parentheses. This representation is
more readable and helps you to start thinking in terms of the C language syntax.
Remember, the focus here is on the constructs; representing them using C syn-
tax will begin the introduction of Chapter 3, “A Word on C,” where the focus is the
C programming language.
A variable representing a number can also be tested for being less than or greater
than zero:
if( number < 0 )
action
or
if( number > 0 )
action
This example tests to see whether number is less than 20 and greater than 10.
Table 2.1 shows the symbols understood by the C language for forming test condi-
tions on numbers. Again, C syntax is not the current focus but is useful because a
means of representing the conditions is needed for our current discussion.
continues
54 Part I Absolute Zero
Characters
Conditions created using variables representing characters follow the same rules and
employ the same symbols as numbers. This is true because, as we discussed in
Chapter 1 (see the section “The C Compiler,” page 18), the computer does not
understand the letter A. The representation of a letter in a manner that the com-
puter can understand is as a number.
The letter A, as seen in Table 1.2, is the same as the number 65 when it is compiled
into machine language, allowing the same conventions used with numbers to be
applied to characters when forming test conditions.
To illustrate this, consider the following conditions:
if( character > ‘A’ )
or
if( character < ‘Z’ )
or
if( character > ‘A’ && character < ‘Z’ )
Note Treating characters like numbers requires you to know the proper order in which
the language places them. This ensures that the semantics of a condition are as
intended.
Because the letter A is a decimal 65, Z is 65+25 or 90 because the letter Z is 25
places away from the letter A. But where do a through z fit in? Is a less than or
greater than A? Also, what about other printable characters that are not letters,
such as punctuation?
A standard known as ASCII defines the representation of characters as numbers
for manipulation by computers.
To see the entire ASCII table of characters and where all printable (and non-
printable) characters fit in to it, use the man command:
bash[20]: man ascii
Figure 2.2 shows a portion of the man page for the ASCII table.
Programming Constructs Chapter 2 55
EXCURSION
ASCII (American Standard Code for Information Interchange)
ASCII is the most common format for text files in computers and on the Internet.
In an ASCII file, each alphabetic, numeric, or special character is represented with a 7-bit
binary number (a string of seven zeros or ones). ASCII defines 128 possible characters.
UNIX- and DOS-based operating systems (except for Windows NT) use ASCII for text files. 2
Windows NT uses a newer code, Unicode. IBM’s System 390 servers use a proprietary 8-
bit code called EBCDIC.
Conversion programs enable different operating systems to change a file from one code to
another. The American National Standards Institute (ANSI) developed ASCII.
how too
pro nouns it The term ASCII is pronounced as if it were spelled ask-e.
Figure 2.2
ASCII Table man
ascii.
56 Part I Absolute Zero
Strings
Strings differ from characters in that they are groupings of characters. Although this
might seem too obvious to point out, manipulation of strings requires a completely
new heuristic.
The ASCII table does not apply to groups of characters. If the test condition con-
siders the individual characters comprising a string, it is okay to apply what you’ve
learned. However, when evaluating the group of characters as an entity, you must use
other methods.
Note Strings do not employ the same symbols for comparison as numbers or charac-
ters. To do so requires that a decimal value be acquired for a string; however,
very different strings could result in the same numeric value. For instance, when
adding the ASCII values for each of the letters in the following strings, the same
value results:
FIG = 70 + 73 + 71 = 214
and
SAB = 83 + 65 + 66 = 214
The means or mechanisms used to evaluate strings are specific to a computer lan-
guage. In the C programming language, an entire library is dedicated to string
manipulation and testing. The C string library is covered in Chapter 3; however,
here we need an understanding of test conditions for strings and specifically the con-
cept of lexical analysis.
The lexical analysis of strings results in one string being evaluated as less than,
greater than, equal to, or not equal to another.
Everyone, at some point, has used a dictionary to look up a word he didn’t know how
to spell; we are all comfortable with the idea that strings are ordered. The lexical
analysis of two strings seeks to determine whether one string comes before or after
another in the dictionary. If one string precedes another, it is said to be less than the
second string. If a string appears after another in the dictionary, it is considered
greater than. The concepts of strings being equal or not equal should be immediately
apparent.
As mentioned, string manipulation is language specific; therefore, examples of how to
form test conditions for strings are detailed in Chapter 3.
With a clearer understanding of test conditions for different data types, you are ready
to form the construct to support multiple test conditions.
Programming Constructs Chapter 2 57
The shortcoming of this approach, however, speaks not only to performance but also
to the lack of a default condition in the case where the user misses the number 5 key
and presses 6.
Simply adding an else to the list as in
if( input == ‘1’ )
open action
if( input == ‘2’ )
close action
if( input == ‘3’ )
delete action
if( input == ‘4’ )
rename action
58 Part I Absolute Zero
is not valid because the error action is taken not only when the user enters an
invalid entry, but also when he enters any valid response other than 5. This, of
course, ignores the fact that every if statement is evaluated regardless of the input or
when a match is found.
Use of the else if statement resolves both problems:
if( input == ‘1’ )
open action
else if( input == ‘2’ )
close action
else if( input == ‘3’ )
delete action
else if( input == ‘4’ )
rename action
else if( input == ‘5’ )
print action
else
error action
For each of the conditions in the switch, the case statement equates to an entry
point for the condition and the break defines where to exit the structure. The flexi-
bility this provides is that case statements can be grouped to share actions.
Note Notice that the examples are becoming more and more rich with the C program-
ming language syntax. The switch statement requires that start- and end-of-
body markers show where the valid entry points begin and end as in the
following example:
switch( var ) {
case 1:
break;
case 2:
break;
default:
}
The open ({) and close (}) curly braces are used in C to mark the beginning and
end of a body of code. We’ll see more of this in Chapter 3.
Consider again the if else solution to the menu problem. If you decided to accept
either a 1, O, or o as valid input to invoke the open action you would have to expand
the test of if ( input == ‘1’ ) to a significantly more complex condition.
60 Part I Absolute Zero
The flexibility of the case statement requires only that we add a condition (entry
point) for the new input:
case ‘o’:
case ‘O’:
case 1:
open action
break
The case statement provides a decision construct that remains clear, succinct, and
easily maintained.
Before leaving the case statement, notice the default condition in the previous
examples. As expected, this is the condition executed when no other match is found.
Loops
Whether you are trying to find a match to an item in a list, count the number of cus-
tomers in a database, or endlessly display a menu soliciting user input, doing things
multiple times is often necessary in a computer program. A construct to make repeti-
tion possible is the loop.
Four ways to perform looping are available, and which one to choose does, of course,
depend on the problem being solved.
EXCURSION
An Array Does Not Live in the Ocean
An array is a storage mechanism best thought of as a filing cabinet. A filing cabinet has a
fixed number of drawers after it is created. Each drawer of an array is a storage location
called an element, and the number of elements make up the array’s length.
Like the number of drawers in a cabinet, the number of elements in an array cannot be
changed after creation. Indexing into an array always begins with zero: array[0] is the
first element of any array. Therefore, if an array has four elements, array[0] is the first and
array[3] is the fourth and last element.
how too
pro nouns it Indexing into array[0] is spoken as if written array sub zero. Following this pat-
tern, array[1] is array sub one, and so on.
Programming Constructs Chapter 2 61
Although there are several ways to construct a for loop, it generally expects that
things will be done a finite number of times. The layout of the for loop looks like
the following:
for ( initialize - test - increment ) {
action
}
2
EXCURSION
How to Define Bodies Using C Syntax
As seen with the switch statement, C syntax uses the curly braces ({}) as a means of
grouping lines of code into a body. Syntactically, they are not required with the for loop as
they were with the switch statement. However, without the start and end body markers,
the for action could be only a single line of code.
Consider again the if statement where an action was associated with the true and
false conditions. That was an easy way to conceptualize that the successful evaluation of
the test results in something being done. The same could be said for looping, as the fol-
lowing shows:
for( initialize -- test -- increment )
take action
However, the action taken may not be a single line of code but rather a series of lines.
If the action spans multiple lines, you must identify that they are all conditional, either
conditional on a test associated with an if or conditional on the number of times you want
to perform a loop.
Placing the conditional lines within a code body allows for the necessary grouping to iden-
tify them as a single conditional action.
To review, the C programming language uses the { as the start of a body of code (begin
body marker) and the opposing } as the end of the body of code.
Notice the correct syntax is to have the initialize, test, and increment fields of the
for loop separated by a semicolon (;).
Having some variable, you set its initial value in the initialize field of the for loop.
In the next field, you test it to see whether it has reached some limit or satisfied a
condition that will stop the looping. Finally, you increment the variable so that it
approaches the value needed to satisfy the test:
for( i = 0; i < 10; i++ ) {
some action
}
62 Part I Absolute Zero
Note In the previous example, if you omit the increment field of the for loop, you
never move the value of i closer to the condition of i equal to 10, which ends
the loop. If this happens, i always equals zero, which is forever less than 10. This
is called an endless loop because the loop never ends.
Optionally, you can place the increment field in the body of the for loop if you
leave a placeholder:
for( i = 0; i < 10; ) {
i++;
some action
}
Of course, the test field of the for loop could be adjusted according to the length of
any array.
or more specifically
while( !found ) {
...
}
how too
pro nouns it The statement reads while not found.
Programming Constructs Chapter 2 63
Note Referring to Table 2.1, the not symbol (!) literally inverts the value of the vari-
able. In order for the loop to execute, the value of found must initially be set to
false as not false is true. As long as the condition of the while is true, the
loop continues to execute.
2
The condition to end the loop must be created within the body of the loop to avoid
the endless looping discussed earlier. For example,
while( !found ) {
found = (item == list_entry );
...
}
In the previous example, the value of the variable found is assigned the result of the
test ensuring that when item is found, the loop ends.
This would be equivalent to
while( !found ) {
if(item == list_entry ) {
found = True;
}
...
}
Note the ..., signifying that some critical elements of this fragment are missing to
make this example fully functional. Specifically, the code necessary for traversing the
list of unknown length is not shown. However, as this type of list management is
used heavily to parse the objects within the Graphics Editor, I will visit it again in
more detail in Chapter 6, “Components of an X Window Application.”
While the input from the user is not equal to the exit condition, continue displaying
the menu.
64 Part I Absolute Zero
Certainly the body of the do while wants to employ the case statement for per-
forming the actions consistent with the user’s selection.
Having thus far only generalized about the actions associated with decisions and
loops, constructs are required to support branching program execution for valid
actions. These constructs, which make branching possible, are called functions.
Functions
For the same reasons that you don’t store all your files in a single directory, you don’t
put all your source code into a single routine. This amounts to poor organization,
which makes program maintenance, advancement, and support extremely difficult.
Further, without grouping code into functions, sharing functionality within a project
requires typing it in again and again.
Functions, also known as modules, are the constructs that enable you to organize and
group your source code based on the tasks it performs. The more precise the task,
the more concise the code, the easier it is to maintain, and the greater chance you’ll
be able to reuse it elsewhere in your project.
Within a project, performing a task more than once should indicate the need to write
a function. For instance, the requirement to open files is common to many programs.
Instead of placing the code necessary to accomplish this in multiple places within
your project, it is appropriate to add a function that accepts a filename, determines
whether the file exists, opens the file, performs the necessary test for success, and,
finally, returns the handle acquired by the open.
Many aspects are critical to understanding and using functions: declarations, return
types, parameters, and definitions. After we’ve discussed each of these, you’ll be pre-
pared to look more closely at the C programming language.
Declarations
To use a function, whether it’s one that you’ve written or one that is provided by the
language or environment in which you are working, it must be prefaced with a decla-
ration.
Declaring a function enables the compiler to validate it by looking for obvious viola-
tions of syntax, and perhaps warning about semantic errors.
Syntax violations checked by the compiler include ensuring that the proper numbers
of arguments (parameters) are passed at invocation. Examples of parameter usage are
provided in the section “Parameters.”
Programming Constructs Chapter 2 65
If a function expects two arguments and you’ve only specified one, serious and
potentially fatal results can occur. This is not fatal to the programmer, luckily, but it
could very well cause the program to crash.
Note The incorrect number of parameters specified when calling a function is a syntax
error that causes the compiler to stop and insist that you fix it. Detection of this 2
syntax error, however, is only possible if the compiler knows the number of para-
meters to expect for a function. This information is provided by the function dec-
laration.
Semantic errors considered by the compiler include comparing the data type of items
passed to those required. The type of the data implies an associated size, as discussed
in the section “Data Types,” later in this chapter. A mismatch in the type of data
passed and expected can cause data to be lost or corrupted because size differences
force the data to be expanded or truncated to satisfy the expected size.
Note Type checking compares what the function expects to what is passed. For
instance, if the function requires a number (integer), the compiler ensures that
you passed a number.
More detail on data types is provided later in this chapter. However, it is important
to know that a function’s declaration ensures that the function is used properly.
Without the function declaration, the compiler must make assumptions about the
function, and the compiler always assumes anything unknown is an integer because
this is the default type in the C programming language.
Two ways to declare a function for use in your source code are by prototyping the
function and by defining the function.
When a function is defined, it satisfies as a declaration because the compiler learns
everything it needs to by the function’s definition. If a function definition appears
before its first invocation, there need not be a separate declaration.
A function’s definition is, in fact, the function, and a prototype is the declaration of a
speak
function before its definition in order to provide the compiler with the following
geek
necessary information:
<return data type> Function Name ( parameter data types );
66 Part I Absolute Zero
Although I’ve not discussed the various data types at length, you should be comfort-
able with the idea that variables must have a type associated with them. Some types
available in C are listed in Table 2.2.
There is much more to know about data types, but we’ll postpone the discussion
until we visit them more fully in the section, “Data Types.”
Return Type
The first thing to decide when writing a function, or to determine when employing
an existing function, is the function’s return type.
The function can return an integer specifying some success or failure. Perhaps there
is data formed and returned by the function. Determining the return type will enable
you to satisfy the first field of your function definition.
The return type of a function will be one of the data types from the first column of
Table 2.2.
Note You usually don’t need to prototype an existing function because this is the
responsibility of the author of the function. This is true in the C language
because the functions provided for use have a corresponding prototype in a
header file provided with the library. A challenge of learning C is becoming aware
of the functions provided by the language and the correct header file to include
when employing them.
Function Name
Following the return type of the function, you must include the function’s name in
the function’s prototype. No matter what you’ve learned about computer program-
ming, choosing names for variables and functions is the hardest part.
When choosing a function name, try to be as clear as possible without overdoing it.
A function name too terse is as bad as a name that isn’t sufficiently succinct.
Programming Constructs Chapter 2 67
EXCURSION
Naming Functions and Variables Is More Difficult Than Naming a Pet
Valid function and variable names must start with either a character or an underscore (_)
followed by more characters and/or numbers. A function name cannot contain spaces or
punctuation and should be descriptive of the task it performs.
For instance, addTwoNum is an appropriate name for a function that adds two numbers. 2
Because function and variable names are allowed to contain numbers, add2Nums is also
acceptable.
Parameters
Continuing to form the function prototype, following the function’s name, the pro-
totype is completed by providing a comma-separated list of the data types expected
as arguments (parameters).
A parameter list is the data provided to a function.
speak
geek Because the return type associated with a function provides the caller with a means
of getting data out of a function, the parameter list is a way to pass data into a func-
tion.
Consider the example where you have a function that adds two numbers together.
The prototype for this example would be the following:
int addTwoNum( int, int );
Although I have yet to write the function (that is to define it), you know already what
it looks like.
The function adds two numbers together so that we must pass the two numbers to
be added into the function. As the purpose of the function is to sum two numbers,
the return type should be the result of the addition. The only thing not immediately
evident is what to call it (the hardest part). Choose a name as descriptive as possible
without needing punctuation.
Notice that in specifying the data types required by the function (the return type and
parameter list) you were not concerned with the variable names associated with the
types. This is true for a function’s declaration: It cares nothing for variable names.
However, including them is not a violation of syntax, as in
int addTwoNum( int num1, int num2 );
Even if the compiler does not know the contents of the function (its definition), you
are still able to use it by merit of its prototype.
Definition
To define a function is to write it. The syntax for writing a function is similar to
declaring it with two notable exceptions. Although optional in our prototype, we
must include variable names in the parameter list, and we must specify the start-
and end-of-body markers.
int addTwoNum( int num1, int num2 )
{
function stuff goes here
}
The parameter list is the method of passing data into the function. In this example,
you are passing two integers num1 and num2 into the function.
Presumably (based on the function name), the two numbers will be added together
and the sum returned to you by the return statement. The data that the function
returns dictates its return type.
From this you can infer that if your function does not return anything, its return
type must be void.
The return statement for the function addTwoNum has the value of the sum of the
numbers passed:
return sum;
Similarly, if the function does not receive anything, meaning that it accepts no data
in the parameter list, the parameter list is void.
void SomeFunction( void )
The necessity of a return statement within the body of the function is important to
remember. It must return data consistent with the return type of the function.
If the function has a void return type, no data is returned and the return statement
is optional. If an optional return statement is present in a function, it cannot return
data, as in the following:
void SomeFunction( void )
{
return;
}
Programming Constructs Chapter 2 69
Note All C programs must have a function called main because this serves as the
entry point into the program. When the program is executed, the function main is
called. Examples of defining main with the required parameter list are covered in
Chapter 3.
2
Having alluded to different data types and their associated size to satisfy elements of
our discussion on functions, we are now ready to turn the focus to data.
Data
Perhaps the most important aspect of computer problem-solving is the understand-
ing of data and its representation within the computer. Knowing differences between
a byte, an integer, and a float is imperative for a computer programmer.
The understanding of data is even more crucial with the C programming language
because C allows the representation of all data by reference.
Accessing data by reference means referring to its address (location) in memory. A data
speak
element used by reference is also called a pointer because you are pointing to the data
geek
instead of pointing to the value of the data.
EXCURSION
Declaring Variables in the C Language
In the C programming language a variable is declared as follows:
int sum;
Any reference to the variable sum after this declaration is a reference to its value.
{
int sum = 0;
sum = sum + 1;
}
In the previous example, you are adding 0 (the current value of sum) and the constant 1
together, storing the result in sum, which is an integer (int).
The C programming language permits referencing data (variables) by their addresses.
This is used extensively throughout the Graphics Editor project. Employing variables by
reference is a very useful and powerful feature of the C language.
70 Part I Absolute Zero
A clear benefit of variable pointers (variables by reference) is realized when passing data
to a function’s parameter list. When data is passed as a parameter to a function, it must be
copied to a temporary storage location called the stack in order for the function to see it.
As will be evident when we define the data structures used in the Graphics Editor project,
the data being copied to this temporary location can be very large. The larger the data
being copied, the larger the stack must be, and the longer it takes for the computer to
copy the data.
However, if the data passed to a function is a pointer to where the data already resides,
only the pointer is copied to the stack. Relatively speaking, pointers are small and quickly
copied. Further, passing the address of where the function can find the data enables the
function to modify the data with the results visible throughout the program.
Declaration of the variable sum as a pointer would appear as the following:
int * sum;
Notice the asterisk (*), which wasn’t in the declaration of sum in the previous example. In
this declaration, sum is declared as a pointer to an int and not itself an int.
A pointer refers to an address in memory.
speak
Demonstrating the danger of pointer manipulation, consider that the variable sum is a
geek
pointer (reference to an address of where an int can be stored), and therefore, an assign-
ment such as
sum = 0;
sets the address where an int would be stored to 0x0000, which is invalid. The next cor-
rect assignment to sum would result in data being placed at address 0x0000 in memory
and a crash (segmentation violation) would soon follow because the assignment was
beyond the bounds (segment) allowed.
The correct method of accessing and assigning values of pointer variables is discussed
later in this chapter; however, it is important to appreciate the flexibility of the C program-
ming language in its representation of data.
Pointer manipulation is one of the most powerful (as well as one of the most danger-
ous) features of the C language. The authors of Java thought so ill of it that the Java
programming language does not allow pointer references.
The goal of this section is to gain an understanding of the data types available in
the C programming language. After discussing data types, focus will shift to under-
standing data by reference, as our Graphics Editor project uses this feature of C
extensively.
Data Types
The first consideration when deciding what data type to assign when declaring a
variable is the information to be stored by the variable. A second consideration is the
precision (or space) required for the anticipated maximum and minimum values.
Programming Constructs Chapter 2 71
Deciding the type of information stored by a variable is an easy first step because
whether the variable will be used for whole numbers, characters, or floating point
digits should be clear from the context the variable is used.
Refer back to Table 2.2 for the data types available for representing information held
in a variable.
When selecting a data type from Table 2.2 based on the criteria of the type of infor- 2
mation being represented (stored), you must appreciate that the data type has an
associated size.
The size of a variable determines the limit of information that can be stored in it.
Trying to fit more data into the storage space allotted by the implicit size of the data
type will either result in corrupted data, or worse, a segmentation violation. (See
Chapter 3, Figure 3.2 for an explanation of a segmentation violation.)
Figure 2.4
Size of char.
C 0100 1010
ONE
BYT
E
Note Although there are eight bits within a byte, the most significant bit (furthest left in
our representation) is used as a sign bit (+/-) and does not count in the value.
Instead, the sign bit determines whether the data value represented is negative
(bit set to 1) or positive (bit set to 0).
72 Part I Absolute Zero
H
Figure 2.5 U
Decimal column N
weights. D
R T O
E E N
D N E
S S S
102 101 100
$ 1 9 8
Reading from right to left, number the columns starting with zero. For each column,
raise the number of digits available for the numbering system by the column number.
Notice above the number 8 in Figure 2.5 that 10 is raised to a power of 0. There are
10 digits available to the decimal numbering system and 0 is the column number,
making the weight of the first column 1 (any number to the power of zero is 1).
After you’ve determined the weight of each column, multiply the number in that col-
umn by the weight of the column and add it to its neighbor:
(1×100) + (9×10) + (8×1) = 198.
We are so used to the decimal numbering system that we might have forgotten how
it works.
Apply the example of the decimal numbering system to the binary column weights
shown in Figure 2.6.
Programming Constructs Chapter 2 73
1 1 1 1 1 1 1 2
The maximum value a variable of type char can represent is 127.
Referring again to the ASCII table (see Note in previous section “Characters,” page
54), you see that a value of 127 is sufficient for representing any character found in
the ASCII table.
To determine the minimum value that can be assigned to a variable of type char, set
all bits available for data to 1 and set the sign bit as well.
Setting the sign bit simply makes the entire value negative. Therefore, a variable of
speak
type char can represent any value from –127 to 127. However, what happens if you
geek
assign a value greater than 127 to a variable of type char?
If you’ve done the math, you’ve discovered 27 (the column weight of the sign bit) is
128. Therefore, representing any value greater than 127 using the data type char
requires setting the sign bit. Although you think you are assigning, for instance, 129
to a variable of type char, the value it holds after the assignment is really –127
because you’ve exceeded its maximum value and data has overflowed to the sign bit
column.
Note The overflow condition is evidence of the necessity for type checking as dis-
cussed previously. If a character (char) variable is expected as a function para-
meter, but a data type with a greater maximum value is passed, the likelihood of
exceeding the maximum value of the char is high.
How do you store a value greater than the 127 allowed by a char data type?
One way to exceed the maximum value of the char data type is to modify the char
with the C keyword unsigned.
The unsigned modifier preceding a data type instructs the compiler to use the most
speak
significant bit for data instead of the sign bit. Clearly, a variable declared as unsigned
geek
is not capable of representing negative numbers. However, depending on the use of
the variable this might not be an issue.
74 Part I Absolute Zero
Figure 2.7
Size of int. S111 1111
BYT
E 1111 1111
BYT
E 1111 1111
BYT
E 1111 1111
BYT
E
ONE ONE ONE ONE
Figure 2.7 gives instant appreciation that the maximum value able to be represented
by an int is significantly larger than that of a char.
Note The size difference between a char and an int is not linear because the col-
umn weights in the binary numbering system increase exponentially.
How many data bits are available within the four bytes of the int?
4 (bytes) × 8 (bits per byte) – 1 (sign bit) = 31
There are 31 data bits in the data type int (integer). Table 2.3 shows the column
weights of each of the 31 bits.
220 1,048,576
219
524,288
218
262,144
217
131,072
216 65,535
2 15
32,768 2
2 14
16,384
2 13
8,192
212 4,096
2 11
2,048
2 10
1,024
2 9
512
28 256
2 7
128
2 6
64
2 5
32
24 16
2 3
8
2 2
4
2 1
2
20 1
Total 2,147,483,647
and 2,147,483,647.
geek
As with the char data type, the modifier unsigned can be applied to the int. An
unsigned int has a range of values from 0 through 4,294,967,295.
Because the data type int is able to represent much larger values than the char, why
select the char?
Just as the size of the data type enables it to represent larger data values, it also
requires more space in memory. Based on the data requirements, a programmer must
decide the appropriate type to assign a variable. It is poor design to always use the
largest data size available.
A programmer must give careful thought to how the data will be employed, correctly
anticipate its maximum and minimum values, and choose a data type to represent it
best.
76 Part I Absolute Zero
Having covered the data types int and char and the modifier unsigned, let’s continue
by looking at other data types for representing numbers in the C programming lan-
guage.
–32,767 to 32,767. Applying the unsigned modifier to the short (unsigned short)
geek
changes the range to between 0 and 65,535.
EXCURSION
A Caveat to the long Data Type…
The 64 bits required by the long must be supported by the platform. As reviewed in
Chapter 1, “UNIX for Developers,” PC platforms (Intel 80x86 architecture) are 32-bit
machines. When only 32 bits are available to the long, it mirrors the int data type.
Other architectures exist that support the full 64 bits (for example, DEC Alpha).
A third data type provided by the C language allows for representation of real numbers.
Consistent with other data types, choosing between the float and double depends
on data requirements of the application and what is being represented.
In an application that, for instance, maintains data representing currency, a float can
be adequate for representing fractions of dollars. However, if the task is to calculate
world or geospatial coordinates, a double can be necessary for pinpoint accuracy.
Having covered the basic data types of the C programming language, we are ready to
create our own.
Programming Constructs Chapter 2 77
Defining Structures
Structures are groupings of data types used to create a new entity.
The syntax for defining a structure requires use of the keyword struct followed by
the name to be assigned to the new entity. Begin ({) and end (}) body markers
enclose the fields of the structure.
struct mystruct {
2
int count;
char flag;
};
The example structure has two fields: an integer named count and a character flag.
Separating the name of the structure and the name of a field with a period is one way
to access fields internal to the structure.
mystruct.count = 1;
Defining mystruct this way makes it available for use in your program as well as for
declaring other occurrences of it.
Declaring another occurrence of mystruct is accomplished with the syntax
struct mystruct newStruct;
where struct mystruct is treated as a data type and newStruct as the variable name
of the new occurrence.
Note Remember that struct mystruct must be defined before a declaration that
employs it.
Optionally, C provides the keyword typedef for defining new data types.
The modifier typedef instructing the compiler to define a new data type is different
for this definition of mystruct. Also, the name of the structure follows the structure
definition.
Following the typedef of mystruct, variables are declared in the following form:
mystruct newStruct;
Data by Reference
As discussed in the introduction to data types, the C language has the capability of
referring to variables by their location in memory.
A variable, which refers to an address where the data is stored, is called a pointer. A
variable of any data type can be declared a pointer.
Syntax for declaring pointers uses the asterisk (*) to indicate that the variable is an
address.
int *intPtr;
At the moment of this declaration the value of i is unknown and considered garbage;
therefore, the variable must be initialized.
The same is true for the value of intPtr. The address where an int may be stored is,
at the moment of intPtr’s declaration, unknown and intPtr must be given a known
value.
EXCURSION
Automatic Variables Employ Volatile Memory
Variables declared within a function are called automatic variables. The memory an auto-
matic variable uses for storing its value is provided from the stack.
The stack was said earlier to be temporary storage used by the program. In addition to
being temporary, the memory associated with the stack is very volatile: It is changing con-
stantly.
Programming Constructs Chapter 2 79
Every time a function is called, a new frame is placed on the stack. A frame is a structure
containing fields the program needs to store information about the function call. This infor-
mation includes the function’s parameter list, its automatic variables, and the point to
return to when the function completes.
When a function finishes, the frame is removed from the stack and replaced with a frame
for the next function called.
The contents of the memory where a stack frame is placed to support a function call is un- 2
initialized except for the values assigned by the system to the fields that it controls. These
fields include the return point and parameter data (assuming that the correct number of
arguments was passed). Although memory is reserved for automatic variables, the content
of this memory is entirely unknown and referred to as garbage.
Note An assignment can be made to any variable, but only pointers can be assigned
into, or have a value placed in the location (address) pointed to by the variable.
Before an assignment can be made into intPtr, a valid address must be
assigned to it.
In this example, a variable of type int (i) and a pointer to an int (*intPtr) are
declared. The variable i is initialized to 0 and the variable intPtr is assigned the
address of i. Notice the use of the ampersand (&) for obtaining the address of i.
Following this example, intPtr is assigned a valid address for storing values (the
address of i). It is now possible to store values into intPtr. To do so requires the fol-
lowing syntax:
*intPtr = 1;
intPtr points to a memory address for storing an integer. De-referencing the variable
speak
with an asterisk (*) indicates the contents of the storage location or, literally, the value
geek
stored there.
80 Part I Absolute Zero
1: {
2: typedef struct {
3: int count;
4: char flag;
5: } mystruct, *mystructPtr;
6:
7: mystruct newStruct;
8: mystructPtr newStructPtr;
9:
10: newStruct.count = 0;
11: newStruct.flag = ‘a’;
12: newStructPtr = &newStruct;
13: }
Varying from the previous example of typedef, this example declares two new data
types:
5: } mystruct, *mystructPtr;
Applying what has been learned, you should realize that the variable newStruct is an
occurrence of the structure mystruct. However, newStructPtr is a pointer to where a
structure can be stored.
Just as the contents of newStruct are unknown at its declaration, meaning the value
of the fields newStruct.count and newStruct.flag are considered garbage, the
address pointed to by newStructPtr is also unknown at its declaration:
7: mystruct newStruct;
8: mystructPtr newStructPtr;
newStructPtr->count = ‘1’;
newStructPtr->flag = ‘b’;
The dash followed by an arrow (->) is the correct syntax for referencing the fields
pointed to by newStructPtr.
Next Steps 2
The discussion of programming conventions and data types throughout this chapter
employed many elements of the C language syntax; however, we have not seen many
subtleties and nuances of the language. Further, it is important that the requirements
of the language be specified. The next chapter, “A Word on C,” provides a thorough
look at the syntax and conventions of C.
In this chapter
• Hello World
• Conclusion
A Word on C
Having been exposed to the C language syntax in Chapter 2, “Programming
Constructs,” we now focus full attention on the structure and convention of the lan-
guage.
Every year the C community runs a contest to see which participant can write the
most obfuscated C code.
obfuscate: -cated, -cating, -cates. 1. a. To render obscure. b. To darken. 2. To confuse.
speak
geek The point of the contest is to show the importance of programming style in an
ironic way and to emphasize subtleties of the C programming language. Although
other structured programming languages have intricacies of their own, C inherently
lends itself to the contest by offering features that are often confusing.
This chapter will lead you through a review of the syntax seen in the examples of
Chapter 1, “UNIX for Developers,” and Chapter 2, “Programming Constructs,”
and expand your exposure to the C language.
If you are already comfortable with your understanding of the C programming lan-
guage, feel free to proceed to the discussion in Chapter 4, “Windowing Concepts.”
The C programming language has a significant evolutionary history in the world of
computer science. It was derived from the B language written by Ken Thompson in
the late 1960s. (The predecessor of B was BCPL written by Martin Richards.)
The importance of this history is to recognize that C did not appear as a new lan-
guage but was adapted from an existing language.
C’s predecessor, B, was a language without types wherein it was up to the program-
mer to ensure that variables were used in a valid context.
84 Part I Absolute Zero
The previous chapter discussed data types and the challenge they bring to computer
programming. On the heels of this, you have to wonder how confusing the B lan-
guage could have been.
Dennis Ritchie wrote C in the early 1970s, keeping most of B’s syntax. Two elements
that are characteristic of C, and often cause the most confusion with the language,
are the relationship between pointers and arrays and the similarities between declara-
tion syntax and expression syntax.
Developing the necessary awareness of C pitfalls while learning correct program-
ming structure and C syntax is a goal of this chapter.
Hello World
A constant when teaching C is that at the end of the exercise the student is able to
print Hello World. Using this as our structured example and bringing together lessons
from previous discussions, let’s begin with a file to hold the C source code.
A file can be created using the vi command (refer to “The vi Editor” in Chapter 1,
page 22 for a review of the command):
bash[1]: vi first.c
Because every C program must define a function called main, begin by inserting the
following code into the file:
0: /* the start of the program */
1: int main( int argc, char *argv[] )
2: {
3: printf(“Hello World”);
4:
5: return( 0 );
6: }
Comment Tokens
Use of the comment token recognized by the C language starts our example:
0: /* the start of the program */
The slash asterisk (/*) combination begins the comment and an asterisk slash (*/)
ends it. Anything contained within the start and end comment tokens is ignored by
the compiler and does not get placed in the object file. The GNU C Compiler also
accepts the token slash slash (//) for support of a comment contained on a single
line:
// everything after this comment token is ignored
A Word on C Chapter 3 85
Because the compiler requires the function main, it does not need a prototype (decla-
ration) before its definition. The compiler expects the function and therefore dictates
its parameter list and return type.
3
EXCURSION
A Syntax for Representing Arrays of Strings
The declaration of the variable argv uses a syntax that you’ve only seen in part.
char *argv[];
Use of the char data type as a pointer (char *) is for storing a string or group of charac-
ters.
char *str = “abcdefghij”;
Further, the syntax of following a variable name with square brackets indicates that the
variable is an array (argv[]) (see the section “Data Types” in Chapter 2, page 70 for a
review of arrays).
Defining a variable as
char str[10];
requests the compiler create an array with enough space in memory to hold 10
characters.
str[0] = ‘a’;
str[1] = ‘b’;
.
.
.
str[9] = ‘j’;
Combining the two in a single declaration as with argv from the example, we see
char *strArray[3];
strArray[0] = “abc”;
strArray[1] = “def”;
strArray[2] = “ghi”;
86 Part I Absolute Zero
Reviewing the definition of main, you see that many elements have already been
discussed.
Identifying the data types in the function definition
1: int main( int argc, char *argv[] )
reveals the return type of the function main is the data type int. Further, main
expects two parameters, an int named argc and an array of char * (character
pointer) called argv.
EXCURSION
Compiler Differences Affecting the Declaration of the Function main
The C compiler used imposes the return type and parameter list of the function main. The
arguments argc and argv are consistent with all C compilers. These parameters enable
the program to access the command-line parameters the user passes.
For instance, in Chapter 1 we used man to illustrate passing flags and parameters to UNIX
commands:
bash[1]: man ascii
With the execution of the man command, the operating system responds by calling the
function main defined by the program’s author. When main is entered, the first parameter,
argc, specifies the number of arguments placed on the command line when the user
invokes the program and argv holds the value of each of them.
Invoking man ascii results in the value of argc being set to 2 where argv[0] contains man
and argv[1] ascii. A way of representing this is
char *argv[] = { “man”, “ascii” };
Notice that in this example, argv contains two elements, which correspond to the value of
argc.
The return type expected from main is not consistent between compilers. Unlike Linux,
most SYSV versions of UNIX allow a return type of void when defining main.
A return type is beneficial because it gives you the ability to test for the program’s success
or failure. By convention, a program exits with a value of 0 to indicate that no errors
occurred. If the program fails to complete, a value greater than 0 is generally returned.
Think of argc as the argument count and argv as the argument values.
speak
geek
Code Bodies
Now that we’ve evaluated the definition of the parameter list and return type for
main, let us consider the function’s body:
A Word on C Chapter 3 87
2: {
3: printf(“Hello World”);
4:
5: return( 0 );
6: }
With any body of code, you must inform the compiler where the body begins ({) and
ends (}).
In the previous example, no automatic variables are defined in the body of main;
however, when automatic variables are defined, they must be placed immediately
after the open brace marking the beginning of the code body. 3
EXCURSION
Where to Define Code Bodies in the C Language
Code bodies can be placed anywhere within a function and they can be nested. Code
bodies define functions and associate a body of code with a loop or decision; however,
code bodies can also be unconditional within a function. For instance, it is valid to have
code bodies within bodies:
1: int main( int argc, char *argv[] )
2: { // start of function body
3: int done = 0;
4: printf( “Hello World” );
5: { // start of unconditional body
6: while( !done ) { // conditional body
7: /* do something */
8: } // end while
9: } // end of unconditional body
10: return( 0 );
11: } //end function body
Notice the automatic variable done in this example. The declaration of done occurs imme-
diately after the start of a code body, specifically, the function’s body; however, variables
can be declared after any start-of-body marker.
EXCURSION
A Stylistic Note…
When code bodies are added to functions (as in the preceding code), it is a courtesy for
those who follow behind you, and it also increases the readability of the code and adds a
level of indentation to the code contained in a body. For instance, the code in the first
body would be tabbed once, the code in the second body twice, and so on, with the asso-
ciated begin and end body markers always in the same column. Consider the previous
example without using any indentation and without the benefit of comments:
88 Part I Absolute Zero
Only by carefully studying the sample can you determine the relationships between the
various code bodies illustrating the utility of proper indentation.
When automatic variables are declared within a new body of code, they are governed
by rules of visibility known as scope.
Variable Scope
Automatic variables declared in a body of code are visible from the moment of the
declaration until program execution reaches the end of the body.
1: int main( int argc, char *argv[] )
2: {
3: int cnt;
4: /* sum is not visible here as it has not been declared */
5: {
6: int sum;
7: /* cnt is still visible here as
8: execution has not reached the body
9: in which it was declared */
10: }
11: /* sum is no longer visible as the body
12: in which it was declared has ended */
13: }
A variable is said to be in scope when it is visible and out of scope when it is not.
Variables defined outside a function are called global variables.
speak
geek If a variable declaration occurs outside a function, its scope is extended to all func-
tions and code bodies that are defined after the point of the declaration.
1: int sum; // a global variable visible
2: // until the end of file
3:
4: int main( int argc, char *argv[] )
5: {
6: sum = add2Nums( 1, 2 );
A Word on C Chapter 3 89
7: }
8:
9: int add2Nums( int num1, int num2 )
10: {
11: return( num1 + num2 );
12: }
This example takes into account nearly everything discussed so far. As required by
the GNU C compiler, the function main is defined as returning int and expecting
the parameters of argc and argv. When main is called, it assigns the value of the
global variable sum the result returned by the function add2Nums.
3
The function add2Nums expects two arguments of type int, which main provides by
passing 1 and 2.
Notice that add2Nums does not have to return anything but could make the assign-
ment directly to sum.
9: void add2Nums( int num1, int num2 )
10: {
11: sum = num1 + num2;
12: }
As the variable sum is defined before the function main, it is visible to main as well as
to add2Nums. Notice that as add2Nums now has a void return type the return state-
ment may be omitted. Further, main would simply invoke add2Nums as
6: add2Nums( 1, 2 );
Figure 3.1 shows the error issued when an attempt is made to compile this sample.
Figure 3.1
An example of a compile
error resulting from a
lack of function proto-
type.
90 Part I Absolute Zero
The GNU C compiler is relatively clear on what is wrong with the sample code.
The compiler makes up an implicit declaration when no actual declaration is found
before a function is called. Implicit declarations are avoided by explicitly defining
functions.
Notice the portion of the error output in Figure 3.1 that reads
sample.c:11: warning: ‘add2Nums’ was previously implicitly declared to return ‘int’
Knowing how to define, declare, and employ functions and variables correctly is crit-
ical in advancing our knowledge of the C programming language. Building on this
knowledge, it is important to become familiar with the functions that C provides us.
Built-In Functions
You will not have to author all the functions that your application employs because
the C programming language provides many functions for you. When you become
aware of functions inherent to a language, you increase your proficiency in it.
The availability of functions provided by an environment such as X Window or a
third-party package adds to this challenge.
Third-party packages are ones that you add to your system and are generally not part
speak
of the standard installation process. These packages are usually purchased separately
geek
and selected for the functionality they add to your development or runtime environ-
ment. The OSF/Motif Widget Set is an example of this; it compliments the X
Window System because it provides elegant three-dimensional features to X
Window-based application development.
A Word on C Chapter 3 91
EXCURSION
3
Terminals Aren’t Just Where Planes Depart
A terminal may be your screen or a terminal emulator (window) such as the xterm
depending on whether your system is running in graphics or text mode.
Generally, printf places its output on the command line following where the program was
executed. This destination is known as standard out and represented in C as stdout. In
addition to standard out, the C language also provides the destination standard error
(stderr) for output.
Standard out and standard error start as the same destination; however, they can be
altered through the use of redirection as discussed in the section “grep, Pipes,
Redirection, and more” in Chapter 1, page 44.
Chapter 4 covers windowing concepts and terminal emulators in detail.
As with the function add2Nums defined earlier, there must be a forward declaration
(prototype) for the function printf. Because the C language provides the function, it
also provides the prototype.
The printf function’s declaration (and many other functions performing input and
output) is found in the file stdio.h, which is part of the standard C header files.
EXCURSION
Another Look at the Use of Header Files
Header files, as discussed in the Note found in the section “Definition” in Chapter 2, page
68, are source files often containing prototypes and other declarations shared by multiple
files within a project.
You include a header file by using the include compiler directive. As the name implies, a
compiler directive directs the compiler to perform a task or make a decision.
In C, all compiler directives are prefaced with the pound sign (#).
#include <stdio.h>
Used in the same manner as the include statement from Makefile syntax as discussed in
Chapter 1, section “Include” on page 37, the compiler directive to include a header file
92 Part I Absolute Zero
performs the inclusion at the point of the directive. The effect is that the compiler sees the
contents of the file as if replicated by the directive.
The syntax when employing the include compiler directive offers a hint to where the file
can reside. Specifically, when the header is part of the standard C library the < and > are
used to enclose the filename. However, when the header file is part of a third-party
package or one that you have authored as part of the project, the filename is enclosed by
double quotes.
#include “gxGraphics.h”.
The C compiler first looks in a standard directory such as /usr/include for files enclosed
with the < > symbols and then it considers the paths specified by the -I flag passed when
the compiler was invoked.
To review the gcc command and use of the -I flag, refer to Chapter 1, section “gcc -I”,
page 28.
To properly declare the printf function prior to its invocation, add the following line to the
“Hello World” code sample:
To accomplish the formatting recognized by the printf function, the format string
ranges from a constant such as “Hello World” to accepting a variety of formatting
tokens.
Note Notice in the syntax of the printf that the args are enclosed in square braces
([]), indicating that they are optional. An argument (arg) is only required if a for-
mat token is nested in the format string.
Table 3.1 shows some common formatting tokens recognized by printf that can be
embedded in the format string.
continues
A Word on C Chapter 3 93
3
All formatting tokens are prefaced with the percent sign (%), indicating to printf that
what follows is for argument substitution. Further, for each token in the format
string there must be a corresponding argument to satisfy the substitution.
Multiple arguments are comma separated and substituted in the order they are
placed.
printf( “String %s and Char: %c”, “Hello”, ‘A’ );
Note As demonstrated in the previous example, C uses double quotes (“”) to rep-
resent multiple characters (strings) and single quotes to represent a single
character (‘’).
Note printf’s capability to perform type checking is limited. A token nested in the for-
mat string expects a complimenting argument of a specific type. If an argument
of a differing type is placed in the argument list, the results cannot be predicted.
The printf statement will attempt to cast the argument, but because this is
done at run-time (while the program executes) there is no recovery if the types
are not compatible.
EXCURSION
Promoting Variable Data Types to Satisfy Type Checking
Casting from one data type to another is a way to promote variables to satisfy type require-
ments and avoid compiler warnings. For instance, a character (char) is easily promoted to
an integer (int), which has a larger storage capacity.
The code fragment
char chr = ‘A’;
int num = (int)chr;
94 Part I Absolute Zero
assigns the letter A to the variable chr. Because the letter A has a decimal equivalence of
65, casting chr to an int would assign the value 65 to the variable num.
Caution must be used when casting from a larger data size to a smaller, however, because
data could be lost. Consider the fragment
int bigNum = 999;
char chr = (char)bigNum;
Because the maximum value of a character is 256, the assignment of bigNum to chr,
although made legal by the cast, results in the new value of chr being –25. Clearly, this is
not the expected result.
See the section “Data Types” in Chapter 2, page 70 for a review of valid value ranges and
the implicit size of recognized data types.
Looking again at the format string accepted by the printf function, it should be
clear why printf is said to perform formatted output. Additional formatting tokens
and token modifiers are available to printf for outputting data types in varying
forms as well as controlling field widths, alignment, and more.
Review the printf man page for a full description of its capabilities.
The printf is one of several functions that C provides for performing formatted out-
put. Similar to printf are the functions fprintf and sprintf.
Both fprintf and sprintf enable the output to be directed some place other than
standard out. For instance, fprintf may be used to send the formatted output to
standard error.
fprintf( stderr, “%s %s”, “hello”, “world” );
Differing from the syntax of printf, fprintf requires as its first parameter the desti-
nation designator for the output. This destination can be one that C provides, such
as stderr used in the example, or it can be one created by using the fopen (file open)
function.
Before looking at the use of fopen, consider sprintf as it relates to the printf and
fprintf functions. Use of sprintf enables a programmer to format output for place-
ment in a buffer.
A buffer is a character array used for intermediate storage during input or output
speak
messageis declared as an array of 25 characters and could be used to satisfy the first
parameter required by sprintf.
sprintf( message, “Error occurred at line %d”, lineno );
A Word on C Chapter 3 95
EXCURSION
Apply Great Caution when Determining the Correct Size of an Array
In the sprintf example, message was declared with a length of 25. Count the characters
in the format string passed to the function:
“Error occurred at line %d”
Including spaces, there are 23 characters in the format string, excluding the value of the
formatting token %d.
The C function sprintf null terminates the output that it formats: It inserts a null character 3
(\0) at the end of the string. This termination is important for other C functions that can act
on the string and it consumes one place in the buffer.
With the 23 characters in the format string and one character for null termination, a total of
24 characters are placed in the buffer message before the argument substitution for the
token %d. If the value of lineno substituted in the message is only one digit (0–9), you have
exactly filled the 25 character spaces available to message. However, if the value of lineno
is greater than 9 (two or more digits), you will exceed the length of message because the
null termination will be placed outside the valid memory associated with the buffer.
Figure 3.2 illustrates the effect on memory when the boundary of an array is exceeded, a
condition known as a segmentation violation.
The value 69 placed in the buffer exceeds the allowed space for message. The owner of
the memory that the \0 (null) overwrites is unknown. The memory can be unused or it can
be a critical part of the program. Depending on the importance of the unknown space, the
program might crash instantly, or it might only corrupt the value of the neighboring space,
leading to a crash much later in program execution.
Note Improper use of arrays is one of the most common causes of program bugs.
resulting in a crash.
geek
Now that we’ve reviewed the built-in functions of printf, fprintf, and sprintf for
formatting and outputting data, we can return to the use of the fopen function for
creating destination designators to be passed to fprintf.
Incorporating everything discussed thus far, consider the following code sample illus-
trating the use of the fopen function:
1: #include <stdio.h> // for printf and fopen function prototypes
1a: // and FILE structure definition
2: FILE *openFile( char * filename )
3: {
4: FILE *fp;
5:
6: fp = fopen( filename, “w+” );
7:
8: if( fp == NULL ) {
9: fprintf( stderr, “Unable to open file %s”, filename);
10:
11: }
12: return( fp );
13: }
The code sample defines a function named openFile that accepts a single parameter
filename and a character pointer, and returns a pointer to FILE.
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
Note The FILE data type is a structure that C provides for referencing files opened
with the fopen function. The structure is considered opaque, meaning that the
fields defined within the structure are not to be accessed. The structure exists
only to serve as a handle for manipulating files.
The standard out (stdout) and standard error (stderr) references provided by
C are pointers to the FILE structure (FILE *).
In the code sample demonstrating use of the fopen function, notice the test on the
value returned by the fopen.
8: if( fp == NULL ) {
cast to a void pointer ((void *)0), it is returned in cases of failure to create a valid
geek
reference to a return value by functions such as fopen.
A Word on C Chapter 3 97
The fopen command expects two parameters. The first, evident by the example, is
the name of the file to open:
6: fp = fopen( filename, “w+” );
The second parameter is the mode in which fopen should open the file.
Table 3.2 shows the valid modes that can be passed to the fopen function and
describes their effect.
Upon successfully opening a file, fopen returns a file pointer (FILE *) reference
which can be passed to functions requiring a destination designator such as fprintf:
fprintf( fp, “Line entered into file referenced by fp” );
Now that you’re comfortable with the code sample illustrating the fprintf com-
mand, the use of NULL, function bodies, rules of scope, and variable and function
declarations, we will consider another family of functions provided by C.
Notice that this code sample uses the include compiler directive to include the file
strings.h:
1: #include <strings.h>
This is the header file that C provides to satisfy the built-in string functions’ forward
declarations.
EXCURSION
Combining Declarations and Assignments Using C Syntax
A variation on variable declaration appears in this code sample as the declaration of str1
and str2 are combined sharing the data type char.
3: char *str1 = “tag”,
4: *str2 = “day”;
Syntactically correct, multiple variables of the same type can be comma separated at their
declaration.
Seen previously but not explicitly noted is the combination of a variable’s declaration and
its initialization as shown in the previous example.
A sound programming habit is to initialize a variable prior to the variable’s use. It saves
you from having to scour code later looking for the obscure bug that lack of initialization
might cause if the variable is employed in an expression before being assigned an initial
value.
As implied by using strcmp in the previous code sample, the comparison of the two
strings returns 0 if the strings are equal. The strcmp function returns a value greater
than (or less than) 0, depending on the lexical analysis of the two strings.
EXCURSION
The Lexical Analysis of Two Strings of Varying Length
If the two strings being compared by strcmp are not the same length, as in
char *s1 = “act”, *s2 = “abra”;
then the function compares the strings up to the point of finding the NULL termination or
end-of-string marker. (See the Excursion in the section about sprintf for a description of
string termination performed by C).
A Word on C Chapter 3 99
In this example, you’ve explicitly said to stop comparing after three characters.
3
how too
pro nouns it The function strncmp is read like it is written stir-n-compare.
Table 3.3 shows several string functions that C provides; they are common in pro-
grams employing the language.
continues
100 Part I Absolute Zero
Table 3.3 shows that dangers are associated with string manipulation because the
space available when copying strings must be sufficient to hold the new contents.
The idea of space in a computer program always translates to memory.
The caution extended in the introduction to pointer manipulation, variables by refer-
ence, (see the section “Data Types” in Chapter 2, page 70) must be applied to strings
as well. In fact, it is the same warning described in the Excursion in the section
“sprintf” earlier in this chapter. The common thread is a necessity for proper mem-
ory management.
Memory Management
Proper memory management is critical in application development. Careful attention
has been paid in previous code samples to ensure that the compiler always implicitly
provided the memory space necessary.
Memory management can be implicit or explicit. C provides built-in functions
enabling a programmer to allocate and free memory explicitly. It is also possible that
the necessary memory is implied by the manner in which a variable is declared.
Samples of associating memory implicitly with a variable include
char str1[10];
A Word on C Chapter 3 101
where the variable array str1, upon declaration, has sufficient space for storing 10
characters.
Consider also
char *buf = “No error”;
where the variable buf is declared with space enough to hold 8 characters as implied
by the initialization combined with the declaration.
Memory associated with variables can be taken from one of several areas available to
an application. Variables declared after a start-of-body marker reside on the stack. 3
(See Chapter 1 for a review of automatic variables and the stack.) The stack is
volatile memory changing constantly during program execution.
Note A programmer affects the stack through the manner in which variables are
declared. In other words, the programmer can never explicitly allocate or free
stack memory.
The memory associated with the variable buf in the previous code fragment resides
on the stack until it is out of scope. After it is no longer visible, the stack frame hold-
ing the reference to buf is removed from the stack and a new frame uses the mem-
ory.
Contrast the declaration of the two character pointers in the following example:
char *buf = “No error”,
*token;
The variables buf and token both point to characters with the contents of buf being
initialized at the moment of its declaration. Based on buf’s initialization, you know
both its size (length) and contents. Because no memory is associated with token, it
has neither a length nor valid contents. Token is a character pointer, but as of yet
points to nothing (garbage).
The cumbersome task of memory management thus begins.
A valid statement is to assign the memory associated with buf to token.
token = buf;
Because the variables are compatible (both character pointers) the assignment is legal
and logical; now, token refers to valid memory (the implicit memory given to buf
from the stack). Employing token follows the same rule as using buf, namely, the
size of the memory provided cannot be exceeded (eight characters).
102 Part I Absolute Zero
Furthermore, when the variables go out of scope, their contents are no longer valid
because a new stack frame will begin using the memory that was once reserved for
them.
Consider this incorrect code sample in which the memory associated with an auto-
matic variable is returned to the calling function:
1: // forward declaration
2: char *someFunc( void );
3:
4: // entry point of the program
5: int main( int argc, char *argv[] )
6: {
7: char *badString = someFunc();
8: printf(“The value of str is: “ );
8a: sleep( 1 ); // new stack frame
9: printf( “‘%s’\n”, badString );
10: return( 0 );
11: }
12:
13: char *someFunc( void )
14: {
15: char buf[15];
16: strcpy( buf, “Error ahead” );
17: // return the memory provided from the
18: // the stack for the variable buf
19: return( buf );
20: }
As described in the previous example, when the function main calls someFunc, a new
frame is placed on the stack to manage all aspects of the function call. Specifics such
as the function arguments, the return address of the calling function, the return
value, and the local (automatic) variables, as well as temporary storage needed during
function evaluation, are part of the stack frame.
Therefore, the memory used to store the value of buf in the function someFunc resides
in the stack frame managing the function call. When the function returns, the frame is
removed and all associated memory is made available for subsequent function calls.
Note A distinction must be made between returning pointers (addresses) from a func-
tion and returning values.
The caution being issued in the previous example is for returning addresses to
data values whose location is on the stack and therefore in volatile memory.
The variable buf is a pointer to a value and not the value itself. Returning the
value presents no danger because a copy of the data is made before returning it
to the calling function.
Returning a value is not always practical, however, because the size of a data
structure can adversely affect program performance.
A Word on C Chapter 3 103
Warning Returning and employing memory from the stack creates memory errors that will
likely result in a program crash when the stack frame is no longer active.
In the previous example, if main did not exit immediately but instead invoked another
function, a new frame would be placed on the stack and the memory previously asso-
ciated with buf (still pointed to by badString) would be reused. The new function
owning the memory location would store a new value, possibly a value that is not
character data. This would ensure that the value of badString is not as expected and 3
a program crash could soon occur.
To avoid the problem of returning memory contained on the stack, you must explic-
itly allocate memory associated with pointer variables or reserve them in non-stack
memory if their use extends beyond a single function.
Applying this to the previous example to correct the imminent program crash would
require that the declaration of the variable buf change from
15: char buf[15];
to
15: static char buf[15];
As stated previously, the static keyword informs the compiler that the memory asso-
ciated with the variable must not be taken from the stack. Instead, persistent memory
is used and therefore the contents of buf are maintained during the life of the pro-
gram. In addition to the memory address being safely returned to the calling func-
tion with the line
19: return( buf );
the contents of the variable will be maintained in consecutive calls to the function.
More examples of static automatic variables will be seen in the Graphics Editor
project.
104 Part I Absolute Zero
Note Memory associated with static variables can never be returned or unreserved
by a program. Therefore, memory reserved as static permanently increases
the size of a program.
Note You can also use the static keyword beyond making a variable’s memory per-
sistent to limit the variable’s scope.
Global variables are visible (in scope) from the moment of their declaration until
the end of the file. Global variables can be made visible to other files through use
of the keyword extern.
tells the compiler that use of the variable sum is by merit of a previous declaration in
another file. By using extern, the variable is global (visible) to multiple files.
However, if the original (actual) declaration employed the static keyword, visibility
would be limited to the file in which it was declared. In other words, when you use
the keyword static on a global variable, the variable cannot be declared externally
through use of the extern keyword in another file.
Similarly, when functions are prefaced with the static keyword, their visibility, too,
is limited to the file in which they are declared even if a prototype for the function
exists elsewhere.
A function defined as
static int add2Nums( int num1, int num2 )
{
return( num1 + num2);
}
can only be invoked by functions in the same file, which contains this definition
because the static keyword limits its visibility.
A Word on C Chapter 3 105
Using the static keyword in the context of ensuring that a variable’s memory is per-
sistent during program execution is one method of managing memory that doesn’t
employ the stack. Another method of managing memory is to dynamically request it
from an area known as the heap.
gram use. Nothing automatically happens to heap memory as it does to the stack
geek
with frames added and removed constantly; this makes the heap a preferred memory
location for data and structures with a life span greater than a single function.
Only heap memory can be dynamically allocated and freed using the built-in
functions for memory management.
To dynamically allocate memory, C provides several functions. The first to consider
is the function malloc.
If the function malloc is not able to reserve the requested memory, it returns NULL.
Testing the return of the function ensures that a valid memory block exists and
diverts a program crash.
if( buf == NULL ) {
printf( “Fatal Error: Failed to malloc memory” );
exit( 1 );
}
except that the memory returned from the malloc is heap memory and will exist after
the function containing the allocation returns. This complicates memory manage-
ment slightly because any memory allocated from the heap must be explicitly
returned to the heap when the program is finished with it.
To return memory to the heap, making it available for subsequent allocations, C pro-
vides the function free.
106 Part I Absolute Zero
have caused a memory leak. Memory leaks cause programs to suffer in performance as
geek
well as face a potential failure.
The memory associated with a program grows in direct proportion to the size of the
speak
program’s heap.
geek
A program that never returns memory to the heap continues to grow in size until no
further growth is possible; either the system runs out of memory or a maximum pro-
gram size is reached, at which time further calls to malloc fail. Also, the greater the
amount of memory consumed by a program the slower its execution.
The syntax for performing a free follows the form
free( (char *)mem );
where mem is the memory gained by a dynamic allocation function such as malloc.
Note The function free expects a parameter of the char * type to be passed to it.
Because the function is used to return the allocation of any type of data to the
heap, it is often necessary to cast the memory being returned to a pointer of the
proper type (char *).
Table 3.4 shows several functions provided by C for performing dynamic memory
allocation.
Let me give you a final word of caution for developing proper memory management
skills: Care for the relationship of automatic variables placed on the stack and refer-
ences to dynamically allocated memory, which at some point must be returned to the
heap. 3
Memory Leaks
Consider the following code fragment:
void someFunc( void )
{
char *buf;
// do something
return;
// returning from the function results
// in the frame being removed from the stack
}
The variable buf is local to someFunc and its value is lost when the function returns.
The value, however, is a reference to memory allocated from the heap. If the func-
tion returns without an appropriate call to free, the memory can never be returned
to the heap and is an illustration of a memory leak.
Finally, in our discussion of C syntax, style and convention are mechanisms for
defining constants and macros within a program.
Recognizing that variables declared external to extend their scope to multiple files
must have an actual definition, the macro GLOBAL is a graceful way to accomplish it.
The first time the compiler sees this code fragment, GLOBAL is not defined, thus the
compiler directive ifndef results in true and the compiler defines GLOBAL—albeit
with an empty value.
101: #define GLOBAL
After you’ve defined GLOBAL to an initial empty value, line 105 appears as the actual
definition for lineno.
105: int lineno;
Subsequent inclusions of this code fragment by other source files result in the test
for GLOBAL as not defined (ifndef), resulting in false because GLOBAL was defined with
the initial inclusion as the empty value. Therefore, the compiler directive else is
performed, setting GLOBAL to extern. This time, when line 105 is reached, the vari-
able lineno is defined as external.
105: extern int lineno;
The actual definition of the variable lineno is effectively in the first source file,
which included the header containing this fragment. Subsequent inclusions result in
lineno being referenced as an external variable.
More examples of macros and constants are seen in the Graphics Editor project.
A Word on C Chapter 3 109
Conclusion
The C programming language is a vastly complex language, and it literally takes
years to master.
This chapter attempted to give you an introduction to the language syntax and style
through example. You should now have sufficient exposure to face the more
advanced issues that follow in the text, namely, the functions made available through
the X Window System environment, and ultimately the challenge of creating the
Graphics Editor.
3
Next Steps
At this point, you should have a level of confidence in the topics discussed so far,
either through experimenting with the code samples provided or through indepen-
dent study.
In the next chapter, focus shifts quickly from the introductory material consisting of
use of the Linux operating system, understanding programming constructs, and
employing the C programming language, to applying these concepts at an advanced
level as we begin looking at the pieces that comprise the X Window System.
Part II
The Pieces of X
In this chapter (M04)
• Origin
This is styled
of the M05
X Window System
• You
The will
Pieces
learn
of Xamazing things and be
Windowing Concepts
how too
pro nouns it The X Window System is called X for short.
Despite the similarity in names between X Window and Microsoft Windows, there is
little consistency. The most notable difference is the separation that X maintains
between the windowing environment and the operating system. Microsoft Windows,
on the other hand, is a proprietary environment closely tied to the DOS operating
system.
Many proponents of the Linux community want to adopt a naming convention that
clearly illustrates the separation of the two systems. However, the course has been
set, and there is little left to do beyond educating users.
Until recently, driven by a consortium of businesses such as Digital Equipment
Corporation, Hewlett-Packard, Sun, IBM, and AT&T, the X Window System was
given much focus to further its development and guide its evolution. With the
release of revision 6, X has reached a level of maturity that will see it through the
next few decades.
114 Part II The Pieces of X
Although the products produced at MIT form the core of the X Window System,
others in the industry have emulated, ported, and furthered it. Examples include
companies such as Metro X, Hummingbird, Xi Graphics, and those involved with
the efforts of XFree86, which is the X Window System port used in the Linux oper-
ating system.
The Pieces of X
This chapter focuses on gaining an understanding of windowing concepts necessary
to program in the X Window System environment. If you are already acquainted
with client/server models, immediate graphics, window hierarchy, window clipping,
and event propagation and queuing, feel free to proceed to the next chapter.
Client/Server Model
The X Window System follows a client/server model that is unique to any win-
dowing system. The model meets one of the goals set by the authors of the X envi-
ronment in that it is fully extensible.
The X Window Server is responsible for managing all resources available to a display.
speak
geek
Note The term display in X Window vernacular does not refer only to the monitor
associated with a workstation. A display is everything having to do with input and
output for a specific system.
A typical display is a single monitor, keyboard, and mouse. However, it can be
simply a touch-type plasma screen, or, it can consist of several monitors, a
graphics tablet, and a keyboard.
Whatever the hardware configuration for your workstation, the X Server is
responsible for its management.
Due to the relationship of the X Server and the specific hardware comprising a
workstation, X Servers are not very portable. To manage the resources available to a
particular video driver or monitor properly, the X Server must be very finely tuned
at the hardware level.
Clearly, generic servers exist for many devices, but they will not maximize features or
capabilities as a device-specific server would.
Windowing Concepts Chapter 4 115
Note For major platform support, manufacturers of the hardware generally author the
X Server because they hold the details of the devices needed to do it properly.
This is certainly the case with product lines produced by Sun Microsystems,
Digital Equipment Corporation (Compaq), and Silicon Graphics.
The PC market has been the exception to this rule, because the vendors were
not as interested as consumers in having X Servers for the myriad of device con-
figurations available to this class of machine.
The manner in which you start the X Server varies slightly from system to system.
Under the SYSV family of UNIX, the X Server is generally added to an initlevel.
Initlevel stands for initialization level as defined in the system file /etc/inittab
speak
(initialization table). 4
geek
Effectively, the system is set to initialize to a specific level. A corresponding entry in
the initialization table instructs the system, which proceeds to start to satisfy the
desired level. If configured to start automatically, the X process will be added to an
initlevel.
Under the BSD family of UNIX, an entry can be made in the /etc/rc.local file to
start the server.
When the system is configured to automatically run X, the command for starting the
server is xdm.
Executing the xdm command starts the X Display Manager, which enforces the user
login process. After a user has successfully entered a login ID and password, xdm
launches the X Server.
If the system is not configured to run X automatically, you can, after satisfying the
normal UNIX login requirements, issue either the startx or xinit command to start
X manually.
Once started, the X Server for the display hardware is responsible for servicing all
requests made by an X client. For instance, a request by a client to draw a line or
render a window is communicated to the server, which manages the hardware to sat-
isfy the request. Also, any event generated by the hardware is queued for the client.
EXCURSION
The Flow of Events in the X Client/Server Model
The X Window System is entirely event driven. The X Server communicates events gener-
ated in the display hardware to the X application (client). Events acted on by a client in
turn generate requests of the server. Because the client may need time to process an
event sent by the server, event queues are maintained by the server for all clients. The
client is responsible for removing the events from the queue and processing them.
Figure 4.1 illustrates the flow of events and requests in the X client/server model.
116 Part II The Pieces of X
X Server X Client
As the mouse cursor (pointer) is moved across the screen in Figure 4.1, the X Server com-
municates the motion event (PointerMotion) to the X Client. Based on the client’s function,
the event can be acted on or ignored. In this illustration, the X Client is drawing a line to
connect the previous location of the pointer (implied by the variables prev_x and prev_y) to
the current pointer location. (This is the behavior of the pencil object in the Graphics Editor.)
Notice that the X Server in Figure 4.1 entirely separates the X Client from the display hard-
ware. An X Client that attempts to communicate directly with the display sacrifices porta-
bility.
Events exist to represent all actions possible in the display hardware managed by the X
Server. Table 4.1 shows some events understood by the X Server that will be processed by
the Graphics Editor.
Understanding that an X Server must exist for the display in which the X Window
System will run, we now introduce the X Client in more detail.
X Clients
A critical step when structuring an X Client application is establishing a connection
to the X Server. (The X library call to perform this task is discussed in Chapter 6,
“Components of an X Window Application.”) The communication between an X
Client and the X Server occurs using a standard network protocol such as TCP/IP.
Windowing Concepts Chapter 4 117
speak An X Client is any X-based application that communicates display requests through
an X Server.
geek
Note Understanding the details of the network over which the client and server com-
municate is not necessary for creating X clients.
What is important is that a network layer exists on the system on which you are
running the server and clients. Because TCP/IP is inherent to UNIX, and specifi-
cally Linux, this should never be a problem. If you feel it is, consult the Linux
setup to ensure that network services are started by the system.
4
Note When running an X Client, the server and the client processes typically execute
on the same system. However, because the communication between the two
processes is network based, it is possible to run a client on one machine and
have it displayed on another. Examples of this will be covered in Chapter 6
where we demonstrate how clients choose to which server to connect.
Table 4.2 shows several X Clients provided by the Linux operating system and a brief
description of their functions.
Many X Clients exist to perform a variety of tasks ranging from image manipulation
to integrated application development. Further, entire environments are provided by
packages such as CDE, KDE, and GNOME. These packages determine the look
and feel of a tailored X session and provide embedded clients and utilities specific to
the environment. Included in these environments are X Clients to manage files and
printers, access the Internet, or manage your Internet service provider dial-up.
EXCURSION
The Critical Role of the Window Manager
An X Client known as a window manager is key to any desktop environment. A window
manager is a special purpose application that provides the capability of X applications to
be moved, resized, minimized, and restored dynamically by the user.
The window manager applies decorations to an application that enable the user to access
these features. Figure 4.2 shows the window decorations and their purpose.
window
Figure 4.2 menu
close
Window decorations
provided by the
Enlightenment window
resize width resize width
manager. (stretch left) (stretch right)
Note Many resources exist for becoming acquainted with the clients available under
the Linux operating system. With the current focus on the structure of the X
Window System and not on specific clients, realize that many clients exist to
serve a variety of needs. Any need recognized as yet unanswered is a call for
your contribution.
To review, the X Server manages all display resources and relays events that occur in
the hardware to the clients it serves. Further, the clients process the events and gen-
erate server requests.
Requests instruct the X Server to perform tasks such as creating, positioning,
coloring, destroying, rendering, drawing, and clearing windows. 4
Windows
The task list that the X Server understands is quite extensive. Every request made to
the X Server is relative to a window within the application.
Every component of the xcalc application consists of a window. Every one of the
buttons forming the keypad of the calculator is a window with another window
acting as the display panel.
As the cursor moves through the windows of the application, it generates events and
places them in a queue that the application processes. For instance, as EnterNotify
events occur for the different windows, the application processes it by highlighting
(increasing the border width) of the window that was entered. When the
LeaveNotify event is processed, the window border returns to its original width.
The ButtonPress event is important to the xcalc application. Many things must
occur within the program when a ButtonPress event is seen.
120 Part II The Pieces of X
DEG
e EE log In y^x
PI x! ( ) /
STO 7 8 9 *
RCL 4 5 6 –
SUM 1 2 3 +
EXC 0 . +/– =
1. A request is made to the server for a change in the window colors. The applica-
tion requests that the foreground color be used for the background and the
background color of the window be used in the foreground. The inverted
colors give the appearance of a button being pushed, as seen in Figure 4.4.
Figure 4.4 7 8 9 *
ButtonPress event, 4 5 6 –
step 1.
1 2 3 +
0 . +/– =
2. Another request is then made to the X Server to display in the status window
the value represented by the window in which the ButtonPress occurred, as
seen in Figure 4.5.
Figure 4.5 7
ButtonPress event,
DEG
step 2.
Windowing Concepts Chapter 4 121
When the button is released and the ButtonRelease event is generated, the colors
must be returned to their original values, as seen in Figure 4.6.
Figure 4.6 7 8 9 *
ButtonRelease event. 4 5 6 –
1 2 3 +
0 . +/– =
As demonstrated in the previous example, processing the many events that occur in
the various windows of an application, and subsequently generating requests from
them, is the nature of event-driven programming. 4
In addition to understanding the purpose of processing events communicated by the
X Server, it is important to recognize the relationship between the multiple windows
of an application.
Window Hierarchy
The xcalc application, as with all X Window applications, consists of a hierarchy of
windows. The more sophisticated the application the greater the complexity of the
hierarchy.
Window hierarchy is used to group windows based on common function. This is
particularly useful when a window grouping is conditionally mapped to the screen.
For instance, a file selection dialog box only appears when the user prompts to save
or load a file.
Note The term mapping a window refers to a request for the X Server to display it or
make it visible on the screen. To unmap a window means to remove it from view.
The programmer, by the parent specified to the window creation function, deter-
mines the window hierarchy of an application.
EXCURSION
The Effect of Window Parenting on Window Visibility
Window creation is covered in Chapter 6. However, every window has a parent and the
relationship between a window and its parent imposes a behavior that must be understood
to ensure proper management.
122 Part II The Pieces of X
Specifically, if a parent window is not visible on the screen, its children (descendents) will
not be visible either.
Further, any portion of a window extending beyond the dimensions of its parent will be
clipped by the parent and not appear onscreen.
Figure 4.7 illustrates the concept of window clipping, as the portion of the child not fully
contained within the parent window is not visible.
Figure 4.7
Window clipping forced Parent
by a child extending
beyond a parent’s Child
bounds.
Not visible
as it is clipped
by the parent
window
You can further understand the relationship between a parent and a child window by
describing window origin.
The origin of any window is its upper-left corner. When placing a window within its parent,
the position (location) specified is always relative to the parent’s origin.
Examples of how to manage the placement of windows are provided in Chapter 6.
Figure 4.8 shows conceptually the relationship between the multiple windows of the
xcalc application.
The toplevel window in Figure 4.8 is the window decorated by the window manager
as a means of providing the application with capabilities such as resizing, minimizing,
and positioning.
The toplevel window also has the responsibility of managing all the application’s
child windows. This responsibility includes ensuring that the windows are visible and
correctly placed and that the events sent to the program are dispatched to the appro-
priate window.
The usefulness of several of the events dispatched was illustrated with discussions of
ButtonPress, ButtonRelease, EnterNotify, and LeaveNotify. We will now consider
the Expose event as shown in Table 4.1.
Expose
Processing the Expose event is crucial to X-based applications due to the immediate
graphic nature of X.
Windowing Concepts Chapter 4 123
Figure 4.8
Children of
xcalc window hier- Toplevel Window
archy. 0 0
AC AC
/C /C
G G
DE DE CE CE G G
RT RT DRDR
SQSQ
tan tan
x^2x^2 y^xy^x
Toplevel Window coscos
1/x 1/x In In
sin sin / /
log log
INVINV ) )
EE EE * *
( (
e e 9 9
x! x!
PI PI
8 8
6 6
– –
4
7 7 + +
5 5
STO
STO 3 3
4 4 = =
L L 2 2
RCRC +/–+/–
1 1
. .
M M
SU SU
0 0
EXC
EXC
Note The X Server does not retain a window’s contents, but places them in the win-
dow when the program makes an explicit request. After this they are forgotten. If
a window is unmapped or obscured by another window, its contents are lost.
124 Part II The Pieces of X
Rather than attempting to retain and replace the contents of all of the windows in an
application, the X Server will send an Expose event to notify the program that a
redraw is necessary. Details within the Expose event structure will indicate which
window requires updating, the position and dimensions of the region affected, and
the number of Expose events pending in the event queue.
The immediate graphic nature of X adheres to a sound principle of software devel-
opment: 90 percent of the effort should not be extended to benefit 10% of the users.
The task of maintaining and replacing the contents of all windows serviced by the X
Server is daunting. Each X Client could conceivably have dozens of windows associ-
ated with the application. Further, the X Server is servicing dozens of applications,
amounting to many windows. The computing power and memory required to
accomplish this task by the server could make the environment unusable.
However, the application that owns the window has already assigned the initial win-
dow contents. The task of the application updating the window as necessary is trivial
in comparison. For that reason, X sends the Expose event notifying the program
when an update is necessary.
Next Steps
With the understanding of windowing concepts gained in this chapter, you are ready
to learn how aspects requiring constant management in an X-based application can
be automated through the use of objects known as widgets.
Chapter 5, “Widget Sets,” will lead you through a discussion of the X Intrinsic
Toolkit and the widget objects it manages.
In this chapter
• The Power and Convenience of Using
Widget Sets
Widget Sets
Authoring an X Client requires constant attention to many details. The relationship
between using windows within the application’s hierarchy, processing events in the
queue (ensuring critical events are processed quickly), and anticipating and respond-
ing to user input are a few of the considerations when writing an application for the
X Window environment.
Much of the management necessary within an X-based application is greatly simpli-
fied through use of the X Toolkit Intrinsics, known as Xt.
As Chapter 4, “Windowing Concepts,” led a discussion of X at the lowest level, this
chapter demonstrates the relationship between widgets and windows, and the toolkit
that manages them.
If you are already familiar with the X Toolkit Intrinsics and the Athena widget set,
feel free to continue to the next chapter where you will build your first X Window
application.
X Server
Device Drivers
As demonstrated in Figure 5.1, the application employs the X library to form the
requests made of the server.
Specifically, the application invokes functions from the X library, which formats the
request into a network call for communicating them to the server. The X Server, in
turn, is responsible for translating the request to the specific devices it is responsible
for managing.
By ensuring that all communication to devices targeted by an application is accom-
speak
plished solely through the X Server, the application maintains maximum portability.
geek
Many details accounted for in the application in Figure 5.1 are left to the imagina-
tion. As discussed in Chapter 4, elements of event management, refreshing graphics,
and responding to user input add complexity to the program.
However, through use of a toolkit and a set of widgets the tasks common to most
application are simplified. Figure 5.2 shows the relationship of an X-based applica-
tion to the X libraries and toolkit.
A widget adheres to the principles of object-oriented methodology and is, in fact, an
speak
object.
geek
X
Network
Protocol
X Server
Device Drivers
Widget Sets Chapter 5 127
In Figure 5.2, side relationships illustrate the association between each component of
the architecture. The application in this figure is employing the X library (Xlib) as
well as the X Toolkit Intrinsics (Xt) and a widget set.
Note In studying the side relationships of the components in Figure 5.2, notice
Intrinsics supports the widget set and Xlib supports Intrinsics. These relation-
ships are critical in the X environment because a widget set would be of little use
when writing an X-based program without the use of Xt.
Because Xt is a toolkit, its purpose is to simplify the use of Xlib and to manage a
widget set. The relationship between Xlib, Xt, and a widget set are intricate.
Consistent with Figure 5.1, however, is that all communication with the devices
displaying the application are managed by the X Server in order to maintain
portability.
user interface. Elements such as menus, buttons, scrollbars, and text fields are entities
geek
provided by a widget set.
As demonstrated with the xcalc application in Chapter 4, section “Windows,” page
119, processing all the events pertinent to a window within an application is the
responsibility of the programmer. An example is acting upon EnterNotify and
LeaveNotify for modifying the border width of the window as the cursor moves
through the application. Another example is managing ButtonPress and
ButtonRelease for inverting the colors of the window to give the appearance of a
button being pushed. A final example of an area that an Xlib programmer must
address is invoking the necessary procedure for responding to the user’s selection of a
value or function associated with a window.
A button component that monitors and processes all the events pertinent to changing
colors and border widths or invoking functions is provided by a widget set.
As a programmer, placing the button widget provided by the widget set on the inter-
face of the application frees you from the mechanics of making a window look like a
button. Because a window is still the basic component of any X application, it should
be understood that the widget creates and manages a window.
128 Part II The Pieces of X
The actual components provided within a widget set vary based on the set being
used. Two common widget sets are Athena and Motif. Widget sets, in general, pro-
vide the programmer with a variety of objects to employ for accomplishing a graphi-
cal user interface.
Note Because the X Toolkit supports any widget set used within an application, mi-
grating from one widget set to another is largely an exercise in editing. In other
words, the naming conventions differ between widget sets but the underlying
mechanics are similar. To move from one widget set to another can be as simple
as editing the names of the widgets and their resources to those understood by
the new widget set.
Choosing between one set of widgets and another is largely a matter of deter-
mining the desired look and feel for the application.
Another deciding factor is the sophistication of the components provided by the
widget set. Dictated by the purpose of the application, components of certain
function or capability may be required within the application, forcing the selection
of one widget set over another.
A look and feel is the appearance and layout of the application. Consistency in the
speak
user.
geek
Resources are the attributes of an application at the component (widget) level. For
instance, it may be possible through the resources available to a widget for the user to
select the font, color, and placement of a button within an application.
The caveat may be possible is applied because the user cannot alter any resource speci-
fied by the programmer (hard coded into the application).
Examples of specifying resources are provided in the next chapter, “Components of
an X Window Application.”
The Athena widget set is available as part of the standard release of X and provides
basic components for addressing nearly every requirement of a graphical user inter-
face. It lacks the sophistication of other widget sets, but this simplicity makes it an
easy first widget set to learn.
Because the Athena widget set is used in the Graphics Editor project, it will be the
primary focus of the remainder of this chapter.
Widget Sets Chapter 5 129
archy implies, there is inheritance from one class in the hierarchy to its descendents.
geek
Widgets inherit resources (attributes) and functionality from their ancestors.
Knowing a widget’s position in the class hierarchy aids in determining the capabili- 5
ties of the widget and the appropriate time to employ it.
Figure 5.3 shows the class hierarchy of the Athena widgets.
Grip Box
Stripchart
List Viewport
Scrollbar
Label Shell Constraint
Text TransientShell
Command Toggle VendorShell WMShell Form
ToplevelShell
Dialog
ApplicationShell
Panel
SimpleMenuShell OverrideShell
Note The widgets within the class hierarchy shown in Figure 5.3 provided by Intrinsics
are underlined, but those included as part of the Athena widget set are plain text.
Intrinsics provides several basic widgets to serve as super-classes for vendor
specific widgets. Also, some widgets are either so general or so special in pur-
pose that it shouldn’t be necessary for them to be repeated by every vendor who
wants to create a unique widget set.
Every widget set, despite the vendor, will fold into the widgets provided by
Intrinsics.
130 Part II The Pieces of X
Note A super-class is any widget designated as the direct ancestor for subsequent
widgets. For instance, in Figure 5.3, the super-class of the Command widget is
the Label widget and the super-class of the Simple widget and Composite
widget is the Core widget.
Consider again the idea of inheritance as we review the Athena class hierarchy shown
in Figure 5.3.
Any resource available to a widget is available to its descendants. For this reason,
resources are not repeated in the structure of the descendants. Similarly, the func-
tions or methods available in the ancestor are available for inheritance as well.
Some widgets within the hierarchy exist for no other purpose than to provide a com-
mon set of resources or functionality to their descendants. The Core widget that
heads the hierarchy is an example of a widget not meant to be instantiated directly
but instead provides features to its descendants (which you will note extends to all
other widgets).
The term instantiation refers to a widget being created within an application. Think
speak
By inheriting all methods from its ancestors, widgets in the descending layers of the
hierarchy grow in complexity.
The following sections provide a description of the widgets used in the Graphics
Editor project and the resources available to each. Although there are many more
widgets available from the Athena widget set, for the sake of brevity only those
needed by the Graphics Editor project are discussed in this text.
Widget Sets Chapter 5 131
Note The Core widget is sometimes used as a drawing area, but otherwise it is rarely
instantiated.
Table 5.1 shows the resources defined by the Core widget class.
y Position Position 0
132 Part II The Pieces of X
Table 5.2 shows the descriptions of each of the items in Table 5.1.
EXCURSION
The Vernacular of an X Window Programmer
A number of terms and types referenced in the Core widget’s resource list have not been
previously introduced.
The following list is a mini-glossary to explain these new concepts.
Boolean Data type used to represent a True (defined as 1) or False (defined as 0) value
Callback A function registered with the widget by the programmer after instantiation—
callbacks are specific to a reason such as ButtonPress, EnterNotify, CreateNotify,
DestroyNotify, and more. The function is invoked by the widget when an event corre-
sponding to the callback reason is received.
Widget Sets Chapter 5 133
EXCURSION
The ApplicationShell Widget
Returned by the Intrinsics function call that opens a connection to the X display
server, the ApplicationShell widget serves as the root widget for the instance hier-
archy of an application.
Note Differing from the class hierarchy shown in Figure 5.3, an application’s instance
hierarchy is dependent on the order in which the program’s author creates the
widgets.
An instance hierarchy indicates the relationship of the widgets created in the
application and, specifically, the manner of parenting specified by the pro-
grammer.
134 Part II The Pieces of X
Note The utility of X knowing the parameters made available to the program (argv) will
become clear in Chapter 6 when we take a detailed look at the function used to
connect to the X Server.
In brief, a standard set of parameters is accepted by all Xt-based applications.
The presence of the accepted parameters is made known by providing the vari-
ables argc and argv to the server initialization call.
Referring again to the Athena class hierarchy shown in Figure 5.3, the
ApplicationShell is a descendant of the ToplevelShell widget class. As is true for
all shell widgets, it is only able to parent a single child. For this reason, a class of
widgets known as Composite widgets exists in order to serve as managers of other
widgets.
Box widgets are most commonly used to manage a related set of buttons, but their
children are not strictly limited to being only buttons.
Table 5.6 shows the resources introduced by the Box widget.
Table 5.8 shows the resources inherited by the Form widget as a descendent of the
Composite widget. These resources enable children of the Form to specify individual
layout requirements.
Allowable values for the bottom, left, top, and right resources include
XawChainBottom Ensures the corresponding edge of the widget remains a
fixed distance from the bottom of the Form widget
XawChainLeft Ensures the edge of the widget remains a fixed distance
from the left edge of the Form widget
XawChainRight Ensures that the edge of the widget remains a fixed dis-
tance from the right edge of the Form widget
XawChainTop Ensures the edge remains a fixed distance from the top of
the Form widget
XawRubber Enables the corresponding edge to move a proportional
distance relative to the new size of the Form widget
138 Part II The Pieces of X
Examples of the semantics for employing the constraint resources of the Form widget
are provided in Chapter 13, “Application Structure.”
The final two Athena widgets to consider are the Label widget and the Command
widget.
Table 5.10 shows descriptions of the resources introduced by the Label widget.
font The font to use when displaying the text string specified by the label
resource
Widget Sets Chapter 5 139
foreground The pixel value used to index into the widget’s colormap to determine the
foreground color of the widget’s window
internalHeight The minimum space left between the label or bitmap resource value and
the vertical edges of the window associated with the widget
internalWidth The minimum space left between the label or bitmap resource value and
the horizontal edges of the window associated with the widget
justify Specifies the justification of text displayed in the Label widget—allowable
values are XtJustifyLeft, XtJustifyCenter, and XtJustifyRight.
label Sets the character string to be displayed in the window associated with the
widget
resize Specifies whether the widget should attempt to resize to its preferred
dimensions anytime its resources are modified
Last to consider is the Command widget. As described earlier, the Command widget
extends the capabilities of its super-class, the Label widget.
5
The Command Widget
The Command widget is a rectangular area containing a character string or Pixmap
image much like the Label widget; however, it accepts input for selection.
how too
pro nouns it Although spelled c-o-m-m-a-n-d, it is pronounced as if it were written button widget.
When the mouse cursor enters a Command widget, the widget highlights by increasing
the width of its window border. This highlighting indicates that the widget is ready
for selection. When the left mouse button is pressed, the Command widget responds by
reversing its foreground and background colors. Upon the release of the left mouse
button, the colors are returned to the normal values and a notify action is invoked
that will call all functions registered on the widget’s callback list. If the mouse cursor
leaves the widget’s window before the button is released, the button reverts to its
normal colors and does not invoke the notify action.
Table 5.11 shows the resources introduced by the Command widget. Remember that all
resources associated with the Command widget’s super-class are available as well.
Table 5.12 shows descriptions of the resources introduced by the Command widget.
another.
geek
Mastering Motif resources requires sifting through volumes of information.
Widget Sets Chapter 5 141
To complete this brief introduction of Motif, the following section shows the similar-
ities and differences between Motif and what you now know about the Athena wid-
get set.
Like the Athena Box widget, the Motif XmRowColumn widget provides direct resources
for controlling its layout, orientation, borders, and spacing. However, making it
more extensible than the Box widget, the XmRowColumn enables the programmer to
specify the number of columns for vertical orientation or number of rows for hori-
zontal orientation in which to arrange its children. As was mentioned of the Box wid-
get, it always packs its children as tightly as possible; the Motif XmRowColumn widget,
however, allows the packing style to be set.
Consider now the Athena Form widget and the Motif equivalent.
The XmForm widget provided by Motif, like the Athena Form widget, enables the chil-
dren to specify attachments for the edges of its widgets, but it also enables edges to
be positioned to occupy a percentage of the parent window.
Next Steps
With an understanding of widget concepts, resources, and the Athena class hierarchy,
the discussion will advance to a step-by-step approach for creating the necessary ele-
ments of an X Window application.
Applying everything discussed thus far in the text, Chapter 6 will demonstrate the
required elements for connecting to an X Server, creating widgets, and modifying
resources.
In this chapter
• Connecting to the X Server
• Creating the Application Interface
Components of an X Window
Application
Having established a sound foundation in preceding chapters, the aim now is to
apply everything discussed concerning programming constructs, C language syntax,
windowing concepts, X Toolkit Intrinsics, and widget sets to create an X-based
application.
This chapter demonstrates the required elements of an X Window application as it
leads you through connecting to the X Server and creating and modifying widgets to
form a graphical user interface. If you are already acquainted with the components
required to program an X Window application, feel free to proceed to the discussion
of “Xlib Graphic Primitives” in Chapter 7.
The steps to create an X Window-based application read like a recipe. The follow-
ing sections describe the code fragments that accomplish each step involved, with
final assembly of these fragments at the end of the chapter to create a functional
X Window application.
The necessary components in the creation of an X-based application include
1. Connecting to the X Server
2. Creating the application interface
Creating buttons
Creating pixmap icons
Assigning actions
3. Managing windows
4. Processing events
Starting at the beginning, you must establish a connection to the X Server.
144 Part II The Pieces of X
Note Of course, anything the X Toolkit Intrinsics (Xt) provides functions for can be
accomplished by using only the Xlib or basic X library.
However, employing the Intrinsics layer of X for the conveniences it provides is
common to software development. For this reason, focus throughout this chapter
is on using the X Toolkit and the Athena widget set.
Chapter 7, “Xlib Graphic Primitives,” demonstrates when it is unavoidable to
employ only the lowest layers of X.
Two of the most common functions used to establish a connection with an X Server
are XtAppInitialize and XtVaAppInitialize.
The functions are closely related except that the first requires that a preformed
resource list is passed as a parameter and the second enables a variable argument list,
accounting for any number of resources.
data type is defined in the X Toolkit to support specifying resources and values.
geek
typedef struct {
String name;
XtArgVal value;
} Arg;
The structure of the Arg data type contains two fields: one for specifying the resource
name and the second for specifying the resource value.
The data type XtArgVal differs depending on the architecture where X is installed. It
makes the specifications of the value field as generic as possible for portability
between platforms of differing architectures.
Components of an X Window Application Chapter 6 145
Generally, an array of Arg elements is defined along with a counter for use with the
Xt-provided macro XtSetArg. This macro is used for filling elements of the array to
form the predefined resource list required by functions in the Xt library.
Listing 6.1 illustrates use of the Arg structure and the macro XtSetArg.
When reading this code fragment, it is important to note use of the ++ operator on
the variable argCnt. The ++ operator increments the variable by one and is equiva-
lent to explicitly performing the following statement:
argCnt = argCnt + 1; 6
Also important to the use of the increment operator on the variable argCnt is that it
is performed external to the macro XtSetArg.
As discussed in Chapter 3, “A Word on C,” in the section “The define Directive” on
page 108, macros operate by substitution by the compiler. Because this substitution
can be quite complex, surprising results can occur if you are not careful. Consider
the following example:
#define SQR(x) x*x
It is expected that using SQR in a body of code will double any number passed to the
macro. For instance,
{
val = SQR(5);
}
would be expanded by 5×5 and the value 25 assigned to the variable val. However,
what would the result of the following be?
{
val = SQR(5+1);
}
The expansion of the macro in this example is not as expected. Instead of 5+1×5+1 or
36, which is intended, it would be seen as 5+(1×5)+1 or 11 because the multiplication
operator has a higher precedence than the addition operator and is therefore evalu-
ated first.
146 Part II The Pieces of X
The macro SQR in the previous example is called an unsafe macro. To make it safe
would require use of parentheses to force the intended evaluation, as in
#define SQR(x) ((x)*(x))
Any macro you did not author should be treated as unsafe, and operators should not
be nested in such macros. The result might not be simply an unexpected evaluation
of the expression, but potentially incrementing a variable more times than expected.
Improper control of variables tends to lead to segmentation violations or other
equally unwelcome side effects.
EXCURSION
Understanding Argument Lists of Varying Sizes
A variable argument list is, as the name implies, an argument list of unknown or varying
length. You’ve already witnessed use of variable argument lists in the discussion of the
printf command in Chapter 3.
The printf function expects a varying number of arguments as determined by the num-
ber of substitution tokens nested in the format string passed to the function.
Similarly, Xt provides a variety of variable argument functions. These functions are consis-
tently used for specifying resource names and resource value pairs.
As is true with the format string passed to printf, functions allowing a variable argument
list can have a number of required arguments.
For the discussions in this chapter, if functions exist in the Xt library that accept a variable
argument list then they will be used over functions that don’t. Variable argument functions
eliminate some overhead within the application and provide an elegant programming
solution.
The function performs several actions that are critical to the execution of the appli-
cation.
The first parameter passes the address (&) of an XtAppContext structure I’ve called
appContext. The XtAppContext is a structure Xt uses to maintain information associ-
ated with the application. Use of the appContext variable, once filled by the
XtVaAppInitialize function, satisfies parameter requirements of subsequent Xt func-
tion calls.
From the second parameter, XtVaAppInitialize reads the class name of the applica-
tion and queries the server’s resource database for any values that may need to be
applied to components of the application.
EXCURSION
Tailoring Widgets in an Application 6
To provide the extensibility required for meeting the varying needs of a large user commu-
nity, widgets are highly configurable.
Configuring widgets entails changing the value of the resources available to them. As dis-
cussed in Chapter 5, resources are introduced by the individual widget and inherited
based on their position in the class hierarchy.
A widget’s configuration can be hard-coded by the programmer, in which case it is abso-
lutely unalterable by users of the application.
Optionally, a widget’s configuration can be entirely determined by the user. This requires
that the user know some information, namely the instance hierarchy and the class name of
the application. Remember that the source code is not always provided for determining
this information. A user is usually given clues of the instance hierarchy by the presence of
a default resource file provided by the author of the application.
In the absence of a default resource file, a user can employ the X tool editres for viewing
the structure of Xt-based applications. The tool is useful for finding and setting resources
with instant capability to view the results. Further, editres is invaluable for creating an
application resource file when necessary information is not otherwise provided by the pro-
grammer.
The widget configuration (resource file) for an application can exist in several places in the
X environment.
Linux commonly places a file corresponding to the class name of the application in the
/usr/X11/app-defaults directory. Each line of the file contains a widget-specific
resource/value pair to specify an element of the desired application configuration.
148 Part II The Pieces of X
In the previous example illustrating the use of the XtVaAppInitialize function, the arbi-
trary class name assigned to the application is 2D-Editor. Therefore, a file of the same
name could be placed in the app-defaults directory for specifying the widget configura-
tion for the application. Because this file is read and loaded by the XtVaAppInitialize
function at run-time, the configuration of the resource values is entirely at the discretion of
the person who maintains the file.
Optionally, the contents of the file .Xdefaults placed in a user’s home directory will over-
ride entries in the class name files found in the app-defaults directory for any X applica-
tions.
Finally, the resource values specified on the command line and passed to the
XtVaAppInitialize through the argc and argv parameters are applied to the application.
All resources and their values expressed externally to an application (meaning other than
the ones hard-coded) are maintained in a database within the X server known as the X
Resource Manager Database. To see the values currently contained in the database for
your server, use the command
The XtVaAppInitialize function merges any options specified on the command line
with those found in the X Resource Database for this application.
Table 6.1 shows the command-line options that the XtVaAppInitialize function
understands. All these options are therefore available to any X applications that use
XtVaAppInitialize to initialize a connection to the X server.
EXCURSION
Uniquely Specifying Widget Paths
The syntax of the resource/value pair eligible for entry into the application’s app-defaults
file or a user’s .Xdefaults file is in the middle column of Table 6.1.
Notice the wildcard prefacing each of the resource names and the colons separating the
names from the resource values. The wildcards are optional syntax but the colons are
required.
In lieu of wildcards, the explicit widget path referencing the placement of the widget in the 6
instance hierarchy for the application can be used. (This information is obtainable from the
editres client.)
An explicit widget path is a period-separated list of the instance names (or optionally
speak
class names) of all widgets preceding the widget in the instance hierarchy.
geek
For instance,
2D-Editor.Form.Box.Command.foreground: red
refers to all Command widgets, which are contained by a Box widget held on a Form widget
within the application with the class name 2D-Editor.
Wildcards and portions of a widget’s instance path can be combined as well:
*Command.foreground: red
The previous command refers to all Command widgets within the application.
Finally, as shown in the second column of Table 6.1,
*foreground: red
affects the foreground color of the entire application, which has the same effect as spe-
cifying the command-line parameter:
-foreground red
The final and most critical action XtVaAppInitialize performs is opening a connec-
tion with the X Server and creating the ApplicationShell widget.
150 Part II The Pieces of X
Failing to test for a valid widget returned from XtVaAppInitialize results in a pro-
gram crash in the instance that the connection failed.
How does XtVaAppInitialize know with which X server to seek a connection?
The function XtVaAppInitialize first considers the presence of the command-line
parameter -display displayname for determining with which X server to establish a
connection.
If the -display parameter is not specified, the presence of an environment variable
called DISPLAY is consulted. If the variable is set in the environment, its value is used
for determining with which server to attempt communication.
Barring the presence of the DISPLAY variable in the environment and the -display
parameter on the command line, a display server on the local host is sought as a last
resort.
The syntax for specifying displayname to the -display parameter or DISPLAY envi-
ronment variable follows the syntax
machine:server.screen
where the screen number is omitted and 0 refers to the only server present on the
machine.
What causes an attempt by XtVaAppInitialize to connect with an X server to fail?
Components of an X Window Application Chapter 6 151
The two most common reasons for an attempted connection to fail are either a mal-
formed displayname or access being denied, meaning that a server can deny the
request because the host attempting the connection is not in the server’s access control
list.
An access control list is managed by the xhost command and determines which
machines can connect with a given X Server syntax.
Access is given by using the syntax
xhost +hostname
or denied by
xhost –hostname
Enabling all connect requests to the server effectively turns off the access control list.
Issuing the xhost command without any host specified does this.
xhost +
means of tying the application to the window manager. The ApplicationShell wid-
geek
get will be given the window manager decorations that enable the application to be
resized, moved, and iconified.
Because shell widgets are able to parent only one child, the child will be the root of
speak
difficult to refer to them in a resource file when attempting to use an explicit widget
geek
path.
The second parameter to the function XtVaCreateManagedWidget is the class name of
the widget to create. This variable is defined in the header file included specifically
to employ widgets of this class. For instance, refer again to this excerpt from
Listing 6.2:
5: #include <X11/Xaw/Form.h>
Header files exist to support all widget classes available within the Athena widget set.
The third argument to the function is the parent widget, which in this case is the
shell returned from XtVaAppInitialize.
The final argument terminates the variable argument list.
Optionally, hard-coded resources could be specified for this widget, as shown in
Listing 6.4.
Notice the addition of lines 20a–20c. Each line is a combination of a resource name
and value. When resource values are specified in a program (hard-coded), changing
the value in the code and recompiling is the only way to alter them.
Components of an X Window Application Chapter 6 153
Note Removing the user’s ability to specify resource values for an application is rid-
dled with pros and cons.
The list of cons includes loss of extensibility to suit a wider user base. Having
user preferences always honored ensures the best display results on a greater
number of platforms.
The pros extend to removing the dependency on user know-how, which can ren-
der the application unusable, and minimizing the necessary level of support and
documentation to prevent user mistakes.
There is much to say in the way of personal commentary on this issue, but I will
limit it to my own rule of what to hard-code and what to leave configurable.
Anything I feel strongly about will get hard-coded and I feel strongly about most
everything. However, the needs of your users can vary; clearly individual judge-
ment must be applied.
Notice in Listing 6.4, the NULL termination is still required to mark the end of the
variable argument list.
Note When specifying widgets internal to an application as shown in Listing 6.4, the
6
naming convention of the resources varies slightly.
Compare the resources used in Listing 6.4 to the resource names specified for
the Core widget in Table 5.1 of Chapter 5.
When resources are specified external to the program such as through entries in
the app-defaults or the .Xdefaults file, the naming convention used is as
seen in Chapter 5. However, internal to the application, you must tell Xt who has
named the resource by prefacing the resource name with XtN.
The term managing a widget refers to making the widget eligible for display. The
speak
widget will actually become visible when all its ancestors are managed and it is not
geek
obscured by other widgets.
The contrast to this is creating a widget using the Xt function XtVaCreateWidget,
which expects the same parameter list as XtVaCreateManagedWidget but will not man-
age the widget it creates. Instead, Xt must be told explicitly to manage a widget
154 Part II The Pieces of X
The creation of a Form widget with a call to XtVaCreateWidget is familiar from pre-
vious examples; however, in Listing 6.5 a number of resources have been specified in
Components of an X Window Application Chapter 6 155
the variable argument list of the function call. In this example, the creation function
is setting the background color, several edge attachments, and the width and height
dimensions. The setting of the size of the Form is useful for initial values but will not
be retained or enforced by the parent of the Form widget (also a Form) because the
parent’s management style is to honor the relative placement as indicated by the spec-
ified attachments.
Note The semantics of the Form widget edge attachments are perhaps instantly con-
fusing or less than intuitive; however, understanding them is imperative for prop-
erly using the Form.
Correct use of the Form widget ensures that the placement of widgets within an
interface is consistently maintained by the application. Other manager widgets
will rearrange (pack tightly) the child widgets they maintain, giving a less profes-
sional appearance to your interface.
The edge assignments in Listing 6.5 specify that all four edges (top, right, bot-
tom, and left) for the child being created are to be chained (locked) to the corre-
sponding edge of the parent.
7: XtNtop, XawChainTop,
8: XtNleft, XawChainLeft, 6
9: XtNbottom, XawChainBottom,
10: XtNright, XawChainRight,
The means of referring to the edge for which an attachment is being specified
(XtNtop, XtNleft, XtNbottom, and XtNright) is clear from the example.
What varies is the way to specify the attachment for a given edge. An edge can
be placed relative to an edge of the parent Form using the XawChainEdge, as
seen in the previous example where Edge specifies what edge of the Form the
placement is relative to. When chaining to an edge of the parent, the distance
separating the child’s edge from the parent is maintained when the application is
resized, meaning the child’s edge will follow the edge of the parent, keeping the
relative distance assigned at creation.
When placing a child widget, remember that the resources used to express
attachments are provided by the Constraint widget. In other words, the child is
specifying how it wishes to be constrained by the parent. Therefore, the edge
specifications refer to the child’s edge and the values given for the child’s edges
are relative the parent.
Optionally, an edge can be relative to another child of the parent by using the
XtNfromHoriz or XtNfromVert resources. These resources expect a reference to
an existing sibling widget for placing the edge of the widget being created.
When a widget’s edge is chained to the edge of a sibling, the relative distance
between them is maintained during a resizing of the application.
Finally, an edge can be loosely placed relative to the edge of the parent or sib-
ling using the edge value XawRubber where the distance is not maintained, but
will vary.
156 Part II The Pieces of X
Note You have not yet seen the specification of color with the use of the
XtNbackground resource in Listing 6.5.
X provides several macros for specifying black and white colors or Pixel values,
two of which are WhitePixelOfScreen and BlackPixelOfScreen. The macros
require as a parameter the Screen structure, easily obtained from any existing
widget with the macro XtScreen.
Color specification in X beyond black and white can be a tedious task. Therefore,
an entire section in Chapter 7 is dedicated to creating and employing colors.
You have seen the creation of the GxDrawArea widget in Listing 6.5 and the widget’s
placement relative to the parent. Notice that the variable GxDrawArea is not declared
in the code listing. As will be demonstrated shortly, the variable is declared globally
for use throughout the application.
Continuing with Listing 6.5, focus on the introduction of a new Xt function
XtAddEventHandler.
14: XtAddEventHandler(
14a: GxDrawArea, /* widget to send events to */
15: PointerMotionMask|ButtonPressMask, /* events to send */
16: False, /* non-maskable events */
17: (XtEventHandler)drawAreaEventProc, /* function to call */
18: (XtPointer)NULL); /* data to pass to function */
EXCURSION
Manipulating Variable Values One Bit at a Time
Bit field operations are common in X as an efficient means of combining unique values into
a single variable. Elements of bit fields are called masks or flags.
Effectively, each binary position within the variable represents a unique value. A declara-
tion such as
char fields:4;
would divide the variables fields into 4 bits. Setting a bit would represent a distinct value in
the variable, as in
fields = (1<<3); /* sets the value of field to 1000 */
fields = (1<<2); /* sets the value of field to 0100 */
fields = (1<<1); /* sets the value of field to 0010 */
fields = (1<<0); /* sets the value of field to 0001 */
The left shift operator (<<) simply says to shift the 1 n times to the left (1<<n).
Using the OR operator (|) enables values to be combined.
fields = (1<<3) | (1<<1); /* sets the value of field to 1010 */
6
The values of PointerMotionMask and ButtonPressMask represent unique positions in the
bit field of the second parameter to XtAddEventHandler for representing the
PointerMotion and ButtonPress events, respectively.
Table 6.2 shows some of the X-defined event masks and the event types that they select.
KeyReleaseMask KeyRelease
ButtonPressMask ButtonPress
OwnerGrabButtonMask (none)
KeymapStateMask KeymapNotify
PointerMotionMask MotionNotify
ButtonMotionMask MotionNotify
Button1MotionMask MotionNotify
Button2MotionMask MotionNotify
Button3MotionMask MotionNotify
Button4MotionMask MotionNotify
Button5MotionMask MotionNotify
continues
158 Part II The Pieces of X
LeaveWindowMask LeaveNotify
FocusChangeMask FocusIn
FocusChangeMask FocusOut
ExposureMask Expose
Some event masks in Table 6.2 select only a single event, whereas others select multiple
events. Also, some event masks select the same event type. Table 6.2 also provides
examples of events that have no corresponding event masks, called nonmaskable events.
The third parameter in the call to XtAddEventHandler informs Xt whether the event
handler should be invoked for nonmaskable events. A nonmaskable event cannot be
specifically selected because it does not have an associated mask. In Table 6.2, the
example passes False for this parameter because our event handler is interested only
in the events that have been registered.
The fourth parameter, the event handler function, specifies the function that Xt will
call when the widget receives the events you’ve added to the notify list. The actual
definition of the drawAreaEventProc event handler will appear later in this chapter.
The fifth and final parameter instructs Xt of client data to pass to the event handler
function when it is invoked.
Note Client data is a parameter included with every function registered with Xt for
invocation.
If there is data to be passed to the function, include it in the client data field and
Xt will pass it to your function. If no data is desired, specifying NULL will act as a
placeholder and no data will be passed to the function.
Having completed the description of creating the GxDrawArea widget and registering
events that will make it a useful drawing canvas, you are ready to expand the inter-
face to include controls for drawing actions.
Components of an X Window Application Chapter 6 159
Creating Buttons
Creating a place for the user to draw is an important element of the application’s
interface. However, the buttons used to request a drawing function are equally
important.
The first consideration when creating the buttons for invoking drawing actions is
where to place them.
Note When laying out an application you are authoring from scratch, you should either
have a good mental picture for how the application should look or actually doodle
a sketch of it.
The mental picture I hold for the Graphics Editor application is shown in Figure 6.1.
Figure 6.1
My mental image of the
Graphics Editor layout.
Supported by the mental image I have of the application layout, a box to contain the
drawing buttons will be required. Listing 6.6 shows a function that satisfies this
requirement.
continues
160 Part II The Pieces of X
Again, you see the use of the XtVaCreateWidget. This time a Box widget is instan-
tiated to contain the application’s drawing buttons.
The position of the Box widget specified by its attachments is for the top and bottom
edges of the widget to follow the bottom edge of the parent widget and the right and
left edges to maintain a relative distance from the parent’s right edge.
This placement ensures that resizing the application by moving the bottom or right
resize decorations applied by the window manager will move the Box widget without
resizing it because both the top and bottom edges of the widget move in unison, as
do the right and left edges.
Both edge pairs (top/bottom and left/right) will retain their relative placement to the
parent’s edge, preventing the Box from resizing, which would ruin the aesthetics of
the interface. The only resizing that is desirable is for the canvas (GxDrawArea) to
grow or shrink with the ApplicationShell. Buttons bigger than the icon they hold
serve no purpose, whereas a larger canvas means more room for additional drawing.
Before considering the function that actually places Command widgets (buttons) into
the Box widget, consider the creation of the Exit button.
Components of an X Window Application Chapter 6 161
Refer again to the familiar routine for creating a widget in Listing 6.6, lines 26–34.
You see some differences to identify; specifically, the Exit button is placed relative to
two other widgets.
The use of XtNfromVert tells Xt that the top and bottom edge attachments are inter-
preted relative to the widget specified as the value of the XtNfromVert resource or the
butnPanel Box widget. Similarly, the use of XtNfromHoriz instructs Xt that the left
and right widget edges are relative to a widget called GxStatusBar, which you must
assume has already been created for it to be used as the value of the fromHoriz
resource.
Note The GxStatusBar variable refers to a Label widget that the application uses to
display instructions or statuses to the user. Creation of the widget is reviewed in
the final code listing at the end of this chapter. The GxStatusBar variable, like
GxDrawArea, is global because it is used throughout the application.
Comparing Figure 6.1 with the code fragment that creates the Exit button, you will
notice that the Command widget takes as its default label value the widget’s instance 6
name. (If necessary, review Command widget resources in Chapter 5, section “The
Command Widget,” page 139.) In other words, if you do not specifically employ the
XtNlabel resource assigning a character string value for use in the label field of the
Command widget, the name assigned to the widget at creation is used.
The XtAddCallback function accepts four parameters. The first specifies the widget
to which the callback will be registered. The second parameter instructs Xt the rea-
son for invoking the callback function. The third is the function to invoke for the
specified reason. The fourth is the client data to pass when the callback function is
invoked.
Referring again to the resources available to the Command widget, the callback reason
XtNcallback occurs when the user activates the widget, or clicks and releases the
right mouse button while the mouse cursor is within the window associated with the
widget.
We now focus on the function that places the buttons into the Box widget created in
Listing 6.6.
23: create_icons( butnPanel, gxDrawIcons, draw_manager );
162 Part II The Pieces of X
The function that creates the buttons for invoking drawing actions expects three
parameters: the butnPanel Box widget to hold them, a gxDrawIcons pointer that con-
tains elements necessary for creating the icons, and finally, a callback function to
invoke when the button is clicked.
The gxDrawIcons pointer is a pointer to an array of elements of the data type
GxIconData, defined as
The first parameter, display, is a pointer to the structure created when a connection
to the X Server was established. It can be obtained from any existing widget using
the Xt macro
XtDisplay( GxDrawArea )
The second parameter is generally the window in which the Pixmap will be placed.
However, in our example, the window for the button has not been created (the win-
dow associated with the Command widget) so any existing window of the same depth
can be used instead.
Components of an X Window Application Chapter 6 163
A macro provided by X enables us to get the root window associated with the display
where the application is being displayed, which we are assured will be an existing
window; otherwise, the connection request made by XtVaAppInitialize would have
failed.
DefaultRootWindow(XtDisplay(GxDrawArea))
Notice that DefaultRootWindow expects a pointer to the display structure as its only
parameter, which is satisfied with the macro XtDisplay.
The third parameter passed to the XCreatePixmapFromBitmapData is the data bits
representing the icon.
X provides a standard client called bitmap for creating this data. Other environments
can provide superior tools for creating icons. For instance, Sun System’s Common
Desktop Environment includes a client called dtpaint. Similar to this is the dxpaint
client, which was included with Digital Equipment Corporation’s now-defunct Ultrix
operating system.
The bitmap client is sufficient for creating simple icons if you are a patient indi-
vidual. Figure 6.2 shows a sample of the bitmap client interface.
6
Figure 6.2
The bitmap client for
creating icons.
By setting cells within the grid defined to be a specific width and height, the icon is
created.
164 Part II The Pieces of X
Note Notice that the bitmap client does not enable specifying colors for the icon
image’s cells. This is because by definition a bitmap has a depth of 1. It is a map
of bits, as suggested by its name. A single bit can only represent on or off.
More complex icon images can be created using clients that support the creation
of a Pixmap directly. Because a Pixmap has a depth greater than 1, it is able to
represent colors.
A way to understand this is to think of a monochrome monitor where the pixels of
the monitor could only represent an on or off condition. Either the pixel is lit or it
is black. The monochrome monitor in this context would have a depth of only 1.
Color monitors, however, are comprised of planes of bitmaps where a single cell
is the alignment of the planes to express a more complex value than simply on
or off.
Using this expression of color representation, the rule for determining the num-
ber of colors able to be presented on a monitor is understood. The formula is
2depth (2 raised to the depth of the screen) because the screen’s depth represents
the number of bit planes and each bit plane is capable of representing 2 values
(on and off).
Figure 6.3 shows the output of the file created when the icon in Figure 6.2 is saved.
Figure 6.3
sample.xbm file con-
tents.
Notice that the file contains the required fields for defining a unique icon.
The XbmData data type provides a structure for storing these unique fields and satis-
fying the requirements for creating an icon within the Graphics Editor application.
typedef struct _xbm_data {
unsigned char *bits;
int w, h;
} XbmData;
Components of an X Window Application Chapter 6 165
Listing 6.7 shows the definition of the bitmap data used to represent the various
drawing icons.
continues
166 Part II The Pieces of X
97: 0x1f,0x00,0x80,0x83,0x1f,0x1c,0x00,0x80,0x01,0x0f,0x18,
98: 0x00,0x80,0x00,0x0f,0x10,0x00,0x00,0x00,0x0f,0x00,0x00,
99: 0x00,0x00,0x0f,0x00,0x00,0x00,0x00,0x0f,0x00,0x00,0x00,
100: 0x00,0x0f,0x00,0x00,0x00,0x00,0x0f,0x00,0x00,0x00,0x00,
101: 0x0f,0x00,0x00,0x00,0x00,0x0f,0x00,0x00,0x00,0x00,0x0f,
102: 0x00,0x00,0x00,0x00,0x0f,0x00,0x00,0x00,0x00,0x0f,0x00,
103: 0x00,0x00,0x00,0x0f,0x00,0x00,0x00,0x00,0x0f,0x00,0x00,
104: 0x00,0x00,0x0f,0x00,0x00,0x00,0x00,0x0f,0x00,0x00,0x00,
105: 0x80,0x1f,0x00,0x00,0x00,0xe0,0x7f,0x00,0x00,0x00,0x00,
106: 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
107: 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
108: 0x00,0x00,0x00,0x00,0x00,0x00};
109: icon_static( text_icon, text_bits, 36, 32 );
which defines each of the icons used to represent the draw functions.
EXCURSION
Understanding Bit-Mapped Data
The characters in the bit data represent the cells of the bitmap grouped into 16-bit entities 6
and converted to hexadecimal representation using Binary Converted Decimal (BCD)
notation, where groupings of 8 bits are converted at a time.
Consider the following example of an 8×2 bitmap where a 1 indicates that the cell is set
(on).
0 0 0 0 0 0 0 0
0 0 0 0 1 1 1 1
0 0 0 0 0 0 0 0
0 0 0 0 1 1 1 1
0x 4
or
is equivalent to
XbmData text_icon = { text_bits, 36, 32 };
Therefore, Listing 6.7 declares and fills the following XbmData structures:
line_icon
pen_icon
arc_icon
box_icon
arr_icon
text_icon
These variables are capable of satisfying the first field of the GxIconData structure
defined earlier.
typedef struct _gx_icon_data {
XbmData *info;
void (*func)(void);
char *mesg;
} GxIconData;
Listing 6.8 shows the use of the XbmData variables to define the gxDrawIcons array
needed for creating the buttons that will fill the Button box of Listing 6.6.
Looking closely at the definition of the gxDrawIcons array, you see that the array ele-
ments hold a GxIconData structure. Further, there are seven array elements defined,
with the last one being set to NULL.
Components of an X Window Application Chapter 6 169
The first six elements, however, are set as the value of the info field, a reference (&)
to a XbmData structure, which satisfies the type requirement of XbmData *.
Assigning Actions
The elements then each assign as the value of the func field a function appropriate to
the action implied by the icon. Shortly, I’ll introduce the header file where these
functions are prototyped and therefore eligible for referencing.
Note A new syntax is introduced with the func field of the GxIconData structure.
void (*func)(void);
The third and final field being set in the initialization of each of the array elements is
the GxIconData mesg field. The mesg field is a character pointer, which is used in
conjunction with the GxStatusBar described earlier for providing statuses and mes-
sages to the user.
Having understood the definition of the structures that form the gxDrawIcons array
passed as the second parameter to the function create_icons in Listing 6.6, we are
now able to introduce that function.
It was invoked on line 23 of Listing 6.6
23: create_icons( butnPanel, gxDrawIcons, draw_manager );
continues
170 Part II The Pieces of X
Listing 6.9 presents a weighty example, reviewing everything we’ve discussed so far.
The function create_icons, as discussed previously, expects three parameters. The
sample of Listing 6.6 satisfied them by passing the Box widget as the first parameter,
the gxDrawIcons array as the second, and the function draw_manager as the third.
Note Although I’ve not introduced the function draw_manager, you should be com-
fortable with referring to functions as variables in the sense that they hold an
entry point for interpretation.
The create_icons function parses the iconData array (passed as gxDrawIcons), loop-
ing until it discovers the NULL termination included in the array definition.
For each element found in the iconData array, a Pixmap is created based on the con-
tents of info field stored in the array element. (Remember that the info field corre-
Components of an X Window Application Chapter 6 171
sponds to the bit data, width, and height information for each of the icons created by
the bitmap client.)
The create_pixmap function is shown in Listing 6.10.
With a valid Pixmap named pix created from the info field of the iconData array, the
create_icons function creates a Command widget setting the value of the resource
XtNbackgroundPixmap to the Pixmap pix.
Note For this reason, no instance name is assigned to the buttons created by
create_icons.
Before incrementing the array to reference the next element, seek the NULL termina-
tion with the following statement:
29: iconData++;
The create_icons function installs two event handlers and a callback function to the
newly created Command widget.
172 Part II The Pieces of X
EXCURSION
Referencing Array Elements with Pointer Arithmetic
The ++ operator was described earlier as incrementing a variable by 1. How, then, does it
work to advance an array to the next element?
When the variable being advanced is a pointer (flagging again the dangers of pointer
manipulation in C), the variable is advanced by the size of the thing to which it points.
In the example of iconData++, the statement is equivalent to
iconData = iconData + sizeof(iconData[0]);
The address to which iconData points is moved forward the distance in memory equal to
the size of one element of the array.
The first element (0) is used because 1 is the minimum length of a valid array.
The first event handler monitors for the cursor entering the window associated with
the widget. When the EnterNotify event occurs, the event handler statusProc func-
tion will be invoked and passed as client data to the mesg field of the iconData array.
The purpose of statusProc is to update the GxStatusBar Label widget with the mes-
sage string passed. In the case of the LeaveNotify event where the client data is NULL,
the content of the GxStatusBar is cleared.
The callback assigned to the Command widgets created by the create_icons routine is
the same for all, namely, the draw_manager function passed to the function pointer
callback.
Evaluating the declaration of the function pointer callback provides insight into
how to declare a XtCallback function.
2: void (*callback)( Widget, XtPointer, XtPointer ))
Note The data type XtPointer is defined in the X environment to be something similar
to
typedef void *XtPointer.
Listing 6.11 shows the draw_manager function passed to the create_icons function as
the function pointer callback.
These functions will grow in complexity as more features and capabilities are added
to the Graphics Editor application.
The draw_manager function demonstrates that when a Command widget is activated,
the draw function is specified in the iconData array (the second field of the
gxDrawIcons array) and is assigned as client data to the draw_manager callback
function.
24: XtAddCallback( btn, XtNcallback,
24a: callback, (XtPointer)iconData->func );
The previous command is cast from being an XtPointer to a function pointer and
then invoked, thus enabling the draw action.
The draw_manager function then assigns the draw_func function pointer to a global
variable draw_mgr_func, which is visible to the drawEventProc. When the
drawEventProc event handler is invoked due to a PointerMotion or ButtonPress
event occurring in the window of the canvas, it is able to invoke the last draw_func
174 Part II The Pieces of X
which employs the Xt function XtVaSetValues for changing the resource values of
widgets that have already been created. In the setStatus routine, the XtVaSetValues
is used to alter the XtNlabel resource value of the GxStatusBar to be the status mes-
sage passed to the function.
Finally, Listing 6.12 shows the definition of the event handler.
27: void statusProc( Widget w, XtPointer msg, XEvent *xe, Boolean *flag )
Like XtCallback functions, event handlers are invoked by Xt and therefore must
have a predictable parameter list. Effectively, the functions you define as callbacks or
event handlers will honor the parameter list expected by Xt.
Because we’ve already reviewed the parameter list associated with the XtCallback,
consider now the event handler statusProc.
The first parameter is the widget in which the event occurred.
The second parameter is the client data specified to the function call
XtAddEventHandler that registered the event handler.
The third argument is the actual XEvent structure definition for the event that trig-
gered the invocation of the event handler. 6
Finally is a pointer to a Boolean variable that the event handler could set to False in
order to prevent this event from being dispatched to other widgets that might have
registered to receive it. Setting this flag to False is not recommended.
Having completed the steps of creating the application interface, adding buttons with
Pixmap icons, and assigning actions to widgets, you are ready to realize the
ApplicationShell to have it displayed on the screen and, lastly, process events.
Managing Windows
You have seen that all widgets have an associated window. A window follows rules for
being displayed that are identical to displaying widgets. Specifically, for a window to
be displayed, all its ancestors must be displayed, and it must not be obscured by
another window.
In widget terms, the widget must be managed, as must its ancestors, and the win-
dows associated with the widgets must be realized or produced. The act of creating a
widget does not necessarily create its associated window. Xt must be instructed to do
so for all widgets in the application.
A function exists within Xt for the purpose of realizing all the windows associated
with the widgets parented by the ApplicationShell returned by the call to
XtVaAppInitialize.
176 Part II The Pieces of X
Processing Events
The function for processing events in an application is XtAppMainLoop. It is invoked
by specifying the XtAppContext variable filled in by the call to XtVaAppInitialize as
illustrated in the following.
XtAppMainLoop( appContext );
geek From this point on, Xt will be responsible for sending events to the widgets that
have requested them. The widgets, in turn, process the events by invoking the call-
back functions, event handlers, or the widget’s internal methods.
Only through dispatching events and responding to the generated requests will Xt
communicate with the application.
Summary
A good deal of material was introduced in this chapter. For clarity and brevity, the
code samples provided are not entirely complete. A point that should be evident to
you is that many of the widget creation examples are contained in functions that
were never invoked. Other details have been intentionally omitted to eliminate
redundancy.
Appendix B, “Application Layout Code Listing,” provides a complete code listing for
laying out this phase of the Graphics Editor Project. Building the code in Appendix
B will yield the image found in Figure 6.1.
Components of an X Window Application Chapter 6 177
The listing is arranged in logical functions contained within appropriate source code
files. Further, the creation routines are linked to the function main, making this phase
of the project functional. Issues such as function prototypes have been addressed
as well, with the hope of making the code a solid example of good programming
practice.
You are encouraged to dwell on the code listing. In doing so, you will notice that
there are some slight differences (improvements) to what was introduced in this
chapter.
Next Steps
The next chapter introduces the only time the lowest level of X (Xlib) must be
invoked. The X Toolkit Intrinsics provide simpler methods for accomplishing every-
thing X is capable of except for performing X graphic primitives.
As introduced in Chapter 7, Xlib graphic primitives are the basic drawing functions
provided by X.
6
In this chapter
• The Graphics Context
• Graphic Primitive Functions
Focusing on the Xlib call for creating a GC, consider the following function
prototype:
GC XCreateGC( Display *, Window, XtGCMask, XGCValues )
Note The Xt function for creating a GC is very similar to the Xlib call except that the Xt
call ensures that GCs are shared within the application.
Depending on the role of the application and the number of GCs being created,
this may be important, as there is a certain amount of overhead associated with
the GC. Creating too many GCs in an application may degrade the program’s per-
formance. What qualifies as too many, of course, is system dependent.
Because the Graphics Editor application employs only one GC at a time and the
attributes of that GC vary with every draw request, it is better suited to use the
Xlib call for creating GCs.
The XCreateGC function that Xlib provides creates a unique GC for defining a draw
request’s attributes.
The first parameter is a pointer to the Display structure created by the call to
XtVaAppInitialize (or equivalent). The Display pointer can be obtained from any
existing widget using the macro
XtDisplay( widget )
The second parameter refers to a window. Because every widget has a window, the
macro
XtWindow( widget )
The third parameter is the value mask of the fields you’ve set in the XGCValues struc-
ture reference passed as the fourth and last parameter.
Xlib Graphic Primitives Chapter 7 181
EXCURSION
Streamlining Field References by the Use of a Value Mask
The use of a value mask in X is very common. As shown in Chapter 6, a mask is a method
of using a single variable to represent multiple values by setting specific bits within bit
fields. (See Chapter 6, in the section “Creating the Application Interface,” page 151.)
For the third parameter of the XCreateGC function, the mask informs the X Server which
fields to use from the XGCValues structure and which can be ignored.
Therefore, for fields in which an assignment is made in the XGCValues structure, the corre-
sponding value mask must be ORed (|) together and passed as the value mask to the
creation routine. Values not set in the XGCValues structure will be set to their default value.
Listing 7.1 shows the definition of the XGCValues structure and the corresponding
value mask to signify when value is set as indicated in the comment adjacent to each
field.
In the following sections, the fields of the XGCValues structure used in the Graphics
Editor will be discussed with illustrations of valid field assignments.
182 Part II The Pieces of X
The GC Function
The XGCValues function field has several possible values defined by Xlib. This field
specifies how the draw request is accomplished, or how the pixels are drawn in the
destination window.
Note Although I keep using the term window when referring to graphic primitive func-
tions, the correct term is drawable.
A drawable is either a Window or a Pixmap.
speak
geek
The utility of drawing in a Window should be immediately evident; however,
equally important is the ability to perform graphic operations against a Pixmap.
Because Pixmaps differ from Windows only in that they aren’t capable of request-
ing and receiving events, they are frequently used as off-screen buffers for
graphic operations. Anything drawn in an off-screen Pixmap cannot be affected
by Expose events or other Windows obscuring their contents, as is the case with
Windows.
Table 7.1 shows the possible XGCValues function field values and a description of the
type of operation each performs.
Note All the GC functions are specified in terms of the value of the source foreground
color and the destination pixel value.
In other words, if GXand is specified as the GC function, a request to draw a line
sets the pixels (points) in the source covered by the line to the foreground color
of the GC. A Boolean AND operation is then applied between the pixels in the
source and the current value of the corresponding pixels in the destination to
determine the new state of the destination pixels.
Function Description
GXandReverse If source pixels are set and not the destination pixels then set destination pixels.
GXcopy Any pixels set in the source will be set in the destination.
GXandInvert Pixels not set in the source and set in the destination result in destination pixels
being set.
GXnoop Destination pixels left unchanged.
GXxor A Boolean EXCLUSIVE OR operation is performed between source and destination
pixels. This function is used to create a rubber banding effect.
GXor Source pixels or destination pixels result in the destination pixels being set.
GXnor If the source pixels are not set and the destination pixels are not set, the pixels
will be set in the destination.
GXequiv The source pixels are inverted and EXCLUSIVE ORed with the destination pixels to
determine the state of the destination pixels.
GXinvert The destination pixels are inverted.
GXorReverse The source pixels or the inverted destination pixels result in pixels being set in
the destination.
GXcopyInverted The source pixels are inverted and copied to the destination.
GXorInverted The source pixels are inverted and ORed with the destination pixels.
GXnand If the source pixels are not set or the destination pixels are not set, the destina-
tion pixels will be set.
GXset All corresponding pixels from the source are set in the destination. 7
The default value for the GC function is GXcopy. To create a GC using a value other
than the default would require the declaration and manipulation of a XGCValues
structure as shown in the following code fragment:
{
GC gc;
XGCValues values;
XtGCMask value_mask = 0; /* clear the mask */
Note The assignment to value_mask in this previous example uses a syntax not yet
introduced.
value_mask |= GCFunction;
Combining an operator such as OR (|) with the assignment operator (=) is com-
mon in C syntax. These combined operations are semantically equivalent to
value_mask = value_mask | GCFunction;
Because the GCFunction mask is the only flag assigned (ORed) to the value_mask vari-
able, the X Server will create a unique GC using default values for all fields of the
XGCValues structure except the function field, which is set to the GXxor function.
The next elements of the XGCValues structure important to the Graphics Editor pro-
ject are the foreground and background fields.
color is no longer referenced by any X Client, the color cell in the colormap is made
geek
available for allocation by other clients.
Figure 7.1 shows a sample colormap obtained using the Linux X client gimp.
Figure 7.1
Sample colormap.
The size of the colormap array, as alluded to in the Note following Figure 6.2 in
Chapter 6, in the section “Creating the Application Interface,” page 151, equates to
the number of colors available to the display as calculated by the function
max colors = 2depth of screen
.
Xlib provides several functions for allocating a color cell in a colormap. Color alloca-
tion can be performed by specifying a color name or an RGB (red, green, and blue)
color component.
Xlib Graphic Primitives Chapter 7 185
EXCURSION
A Closer Look at Color Management
A closer look at color management in the XA display server automatically creates a default
colormap for use by all X clients it serves. However, based on the requirements of the
client, there might not be a sufficient number of color cells available in the shared col-
ormap. When this happens, an application is free to create a private colormap.
Only one colormap can be active in a server at a time.
speak
Window focus then determines which colormap is active in the server at any given
geek moment. A client with a private colormap gaining focus will have its colormap installed by
the server.
This causes other applications running on the desktop to display in colors that might not
be aesthetically pleasing or even make sense, because the pixel values referenced by a
client using the default colormap are then interpreted relative to the private colormap.
For instance, the default colormap shown in Figure 7.1 has at index 0 the color red. An
application creating a private colormap can allocate in its first cell the color black.
When the private colormap is active in the server, all applications employing the pixel
value of 0 will display black because that is the color corresponding to index 0 in the
active colormap.
Returning focus to a client using the default colormap returns the default colormap as
active in the server and index 0 again refers to red.
Performing color allocation by specifying named colors is generally the best way to 7
ensure that your X application obtains a valid color value.
Note The caveat generally is applied because the internal methods for color allocation
are server dependent.
If all the color cells in the default colormap are filled by the applications using it,
the method of satisfying subsequent allocation requests or performing a closest
match algorithm differs from server to server.
The function expects as its first parameter a pointer to the Display structure created
when a connection to the X Server was established.
The second parameter refers to the colormap where the color will be searched for to
determine whether it has already been loaded. If it is not found in the colormap, and
if there are empty color cells available for allocation, the color will be loaded.
The third parameter is the name of the color to be allocated.
186 Part II The Pieces of X
Note Color names are not arbitrary. A file called rgb.txt exists on every system for
specifying the named colors available to the X Server. The file also contains the
RGB values that are used to create the color when it is requested.
The location of the rgb.txt file varies between systems, but is found in the
directory /usr/lib/X11 under the Linux operating system.
The function returns a non-zero value to indicate that the named color was found in
the rgb.txt file, and zero to indicate that it wasn’t.
The X Server cannot allocate a named color not found in the rgb.txt file because the
RGB components will be unknown.
If the color is found in the rgb.txt file, the exact_color XColor structure filled by
speak
GC gc;
XGCValues values;
XtGCMask value_mask = 0; /* clear the mask */
DefaultScreen(XtDisplay(GxDrawArea))),
“lightblue”,
&exact, &closest );
if( status == 0 ) {
printf( “failed to alloc color” );
return; /* nothing more can be done */
}
values.foreground = closest.pixel; /* index where match was found/created */
values_mask = GCForeground;
gc = XCreateGC( XtDisplay(GxDrawArea), /* pointer to Display */
XtWindow(GxDrawArea), /* window reference */
value_mask, /* indicate fields set in values */
&values ); /* value structure */
}
Notice in the previous example that the default colormap can be obtained using the
macro
DefaultColormap(XtDisplay( widget ), DefaultScreen(XtDisplay( widget )))
which expects two parameters, the Display pointer and a screen number obtained
with the macro DefaultScreen.
Also, note that the pixel field of the closest XColor structure is the valid reference
to the color allocated by the function call.
The next field from the XGCValues structure to consider is the attribute for setting
the line width of a draw function. 7
GCLineWidth
The simplest XGCValues field to discuss is the line_width field.
As implied by the name, this field sets the width of lines drawn by the graphic primi-
tive. If the value of line_width is 0 (the default), the fastest method known by the
server for drawing a line of a pixel width of 1 is used.
The following example illustrates setting the line width for a graphics context:
{
GC gc;
XGCValues values;
XtGCMask value_mask = 0; /* clear the mask */
values.line_width = 3;
value_mask |= GCLineWidth;
The final XGCValues field used in the Graphics Editor project is the tile field.
188 Part II The Pieces of X
GCTile
The tile field of the XGCValues structure assigns a Pixmap for use by the GC function
as the source Drawable for graphic operations.
When a background pixmap of the destination Drawable is used as the tile value,
the operation effectively performs an erase.
The effect of tiling is to use the bits set in the tile pixmap that correspond to a
specified graphic function for placement in the destination.
Because this is the method of removing objects from the canvas in the Graphics
Editor application, the subject will be visited again in more detail in Chapter 16,
“Object Manipulation,” in the section “Deleting an Object” page 321.
A complete discussion of the Graphics Context value structure involves much more
than the elements used in the Graphics Editor. Table 7.2 shows a summary of the
fields and their allowable values for reference.
cap_style CapNotLast, CapButt, Manner in which the corners of line and rectangle
CapRound, primitives are drawn
CapProjecting
fill_rule EvenOddRule, Rule for performing fill primitive request when the
WindingRule points of object being drawn are not ordered
arc_mode ArcChord, ArcPieSlice Describes whether the end points of an arc connect to
each other (ArcChord) or to the center of the arc that
defines them (ArcPieSlice) .
Xlib Graphic Primitives Chapter 7 189
A number of convenience routines exist in Xlib for altering the values of a GC after its
creation. Some of these routines include XSetForeground, XSetBackground, and
7
XSetFunction. They will be introduced in the context of the Graphics Editor
application in later chapters.
Knowing how to specify the values desired for a graphics context and the Xlib
method of creating them, let us shift our focus to the functions that employ GCs.
XDrawPoint
The Xlib graphic primitive for drawing a single point to the screen is XDrawPoint.
The first three parameters the XDrawPoint function expects should be familiar to you
already.
They are
XDrawPoint(display, drawable, gc, x, y)
a pointer to the Display structure, the destination Drawable, and a Graphics Context.
Unique to the XDrawPoint are the final two parameters used to specify a coordinate.
Remember that the origin of a coordinate system is the upper-left corner of the
Drawable with the X-Axis increasing to the right and the Y-Axis increasing downward
as demonstrated in Figure 7.2.
Figure 7.2
Pixel coordinate system.
The XDrawPoint graphic primitive draws a single point at the specified x, y location
using the foreground color of the Graphics Context.
To draw multiple points, an array of XPoint structures is defined and passed to the
function XDrawPoints.
An XPoint structure is defined as
typedef struct {
short x, y;
} Xpoint;
XDrawLine
The XDrawLine function, like all other graphic primitives, expects as the first three
parameters the Display pointer, Drawable, and Graphics Context to be used for the
operation.
In addition to these parameters, the XDrawLine function needs the endpoints that
define the line to be drawn.
XDrawLine(display, drawable, gc, x1, y1, x2, y2)
The XDrawLine function uses the foreground of the GC to draw a line connecting the
two points specified.
To draw a polyline (multiple-line segments), X provides the graphic primitive
XDrawLines.
XDrawRectangle
The Xlib graphic primitive XDrawRectangle has as its unique parameters (parameters
beyond the Display pointer, Drawable, and GC) an x, y location specifying the upper-
left corner of the object and the width and height to apply to the rectangle being
drawn.
XDrawRectangle(Display, drawable, gc, x, y, width, height)
As with other graphic primitives, multiple rectangles can be drawn with a single
request by creating an array of XRectangle structures and employing the function
XDrawRectangles.
XDrawArc
The XDrawArc function works by specifying a rectangle to hold the arc and setting a
start and stop angle.
XDrawArc(display,drawable,gc, x, y, width, height, angle1, angle2)
The rectangle defined is not actually drawn by the request. Instead, it is used to
position the arc (x, y) and determine the arc’s aspect ratio (proportion of width to
height).
The width value is translated by the XDrawArc request as the x-axis diameter of the
arc and the height value is tsranslated as the y-axis diameter. This enables the draw-
ing of ellipses (width and height values are not equal) as well as circles (values are
equal).
The angles passed to XDrawArc instruct the function of the starting and ending points
of the arc extent. Because the unit of measure X expects angles expressed in is
(degrees/64), a full 360-degree arc is requested by setting the value of angle1 to 0×64
and angle2 to 360×64.
Note Because the value 360 is meaningful to programmers, the angle is generally
nested in the XDrawArc function as (360×64).
If you actually pull out a calculator and determine that 360×64 equals 23,040 and
use this value in the call to XDrawArc, those following behind you must reverse
the calculation to understand that the intent was for 360×64.
If an arc less than 360 degrees is desired, two decisions must be made: Where will
the arc begin and where will it end?
To express the starting point of an arc, angle1 determines (in degrees×64) the dis-
tance from the 3 o’clock position of the bounding rectangle XDrawArc should begin
drawing.
The extent of the arc (end point) is expressed by angle2 as (degrees×64) relative to
the starting angle.
Figure 7.3 illustrates the use of angle1 and angle2 to form a draw request using
XDrawArc.
As shown in Figure 7.3 angle1 determines the start point of the arc and angle2 the
distance from angle1.
Creating arcs and specifying start and end angles will be reviewed again in the con-
text of the Graphics Editor project with the introduction to the gxArc object.
Xlib Graphic Primitives Chapter 7 193
Figure 7.3
XDrawArc parameters.
The last thing to consider with the introduction of Xlib graphic primitives is the
manner in which draw requests are formed for filled objects.
Filled Graphics
For nearly every graphic XDraw function introduced in this chapter there is a corre-
sponding XFill, which accepts an identical parameter list but produces a filled
graphic object.
Table 7.3 shows the match of XDraw to XFill functions provided by Xlib.
The exception noted in Table 7.3 refers to the fact that the XFillPolygon parameter
list does not exactly mirror the XDrawLines function.
The prototype for XFillPolygon is
XFillPolygon(display, drawable, gc, points, npoints, shape, mode)
Next Steps
This chapter provided a whirlwind introduction to the Xlib graphic primitives that
will be used in later chapters to accomplish the drawing of objects supported by the
Graphics Editor application.
Specific examples and more detail are provided when the functions are introduced in
the context of authoring the application.
The next important step before employing the graphic primitives within the editor
application is to review bitmap versus vector graphics.
The X Window System is a bitmapped-based graphic system. The Graphics Editor,
however, is vector based. Reviewing both methods makes it clear how they cans co-
exist between the application and the windowing environment.
Part III
Back to School
In this chapter
• Vector Graphics
• Raster Graphics
The request shown in Figure 8.1 is to connect the points between (10,10) and
(10,20). The first step in the graphics pipeline is to determine all the source pixels
that should be set based on the request. This phase of the process incorporates ele-
ments of the GC, such as the stipple, dashes, line style, and line width. If the line
width were 4, for instance, four adjacent pixels would be affected by the request.
Similarly, if a dashed line were requested, not all of the consecutive pixels between
the points would be set, but only those representing the dash pattern specified.
The actual value of the pixels determined to be set depends on the foreground, back-
ground, and possibly tile values specified in the GC.
The next step of the pipeline is to apply the source pixels to the destination using the
logical function specified in the GC.
Finally, any necessary clipping is performed either to bring the graphics request to
the bounds of the Drawable destination or to honor any clip mask specified within
the GC.
The purpose of applying the graphics pipeline in response to a graphics request is to
determine the final state of the pixels in the destination. These pixel values are used
as indexes into a color look up table (CLUT) to determine the values needed to drive
the monitor (CRT).
The description of Figure 8.2 and the X Server’s response to the graphics request
leads to the two following concepts:
• Initial requests made of the X server using the Xlib Graphic Primitives covered
in Chapter 7, “Xlib Graphic Primitives,” are vector based.
• The graphics pipeline, however, converts the results of the Xlib primitive func-
tions into a raster (bitmapped) image that can then be mapped to the screen.
Vector Versus Raster Graphics Chapter 8 199
Vector Graphics
Vector-based graphics employ a process of creating images using functions based
on mathematical statements that place shapes precisely within a given space of the
window.
In physics, a vector is the representation of both a quantity and a direction.
speak
geek Functions that create an image in vector-based graphics employ a series of vector
specifications such as the Xlib primitive XDrawLines function, which specifies a num-
ber of vertices to be connected.
A vertex (singular of vertices) is a point that marks the junction of two line segments.
speak
geek Like popular graphic programs such as Adobe Illustrator, the Graphics Editor is
entirely vector based, meaning the graphic objects created and altered by the pro-
gram will consist of geometric descriptions.
These descriptions will be retained in terms of origins, vertices, angles, dimensions
(width and height), and bounds as is pertinent to a specific shape. In fact, geometric
descriptions specific to the shapes supported by the Graphics Editor form the
graphic object definitions.
The benefit of maintaining graphics as vectors is the flexibility of manipulating them
speak
Raster Graphics
Raster graphics, often called bitmapped graphics, are images described by pixel
values that can be directly mapped to the screen.
200 Part III Back to School
…
Raster Image
252
253
Output
254
255
Colormap
Modifying a raster image is difficult, slow, and generally results in loss of information
that affects the quality of the image. Modifications to raster images are applied to
every pixel value comprising the entire image.
Contrast this with modifying vector-based images, which affects only the pixels that
connect the geometric description of the objects.
Reversing the alteration of a vector-based image results in an image very near the
original, whereas raster images are degraded to some extent.
Note Clearly, processors and software packages exist that are dedicated to the task of
manipulating raster images and ensuring their integrity is retained.
The only thing more astounding than the computing power required for the task
is the cost associated with such platforms.
Vector Versus Raster Graphics Chapter 8 201
Next Steps
It is crucial to understand the relationship between the vector-based Xlib calls and
the bitmapped display nature of the X Window System before proceeding to the next
chapter.
Chapter 9, “Object Bounds Checking,” focuses on the best means for determining
the bounds of an object based on the graphic descriptions associated with different
geometric shapes.
Because an object’s bounds are used later in the Graphics Editor project to determine
whether an X ButtonPress event has selected the graphic (made it active for manipu-
lation), correctly calculating the occurrence of an event within an object’s bounds is
necessary for installing proper object control.
8
In this chapter
• Point-Array–Based Object Bounds
• Arc Object Bounds
Listing 9.1 defines the data type used in the Graphics Editor project for representing
the bounds of the object.
Note The data type Dimension used for the width and height fields of the Bounds
structure defined in Listing 9.1 is defined in the X Window environment as
typedef unsigned int Dimension;
Because the LatexLine, PolyLine, Box, Arrow, and Text objects are all represented
with similar internal structures, namely as a series of XPoints contained in an array,
the method of calculating the bounds of these objects will be the same. Taking into
account the Arc object, two methods are required for calculating object bounds. One
method serves point-array–based objects and one method is explicit to the Arc
object.
Listing 9.2 shows the definition of the GXLine structure used to represent the data
specific to the point-array–based objects used in the Graphics Editor.
The difference of the minimum and maximum points representing the line object
determines the dimensions of the object as illustrated in Figure 9.1.
Figure 9.1
A demonstration of Line
Object Bounds.
[0] 54, 97
The points defined for the line object shown in Figure 9.1 can be defined as
XPoint pts[6] = {{54, 97}, {113, 55}, {155, 120}, {101, 107}, {54, 97}};
There are five points in the array accounting for the last line that closes the object.
206 Part III Back to School
The maximum x point found is at element two (155) and the smallest is at the first
and last elements (54). The largest y point is at element three (107) and the smallest
is at element two (55) .
Applying the width and height calculations seen in Listing 9.2 reveals
width = 155 - 54 = 99;
height = 107 - 55 = 52;
At the return of the function getLineBounds, the bounds structure for this object is
bounds.x = 50;
bounds.y = 50;
bounds.width = 99;
bounds.height = 52;
After illustrating the calculation for determining the bounds of an arc object, I will
show you how to use the bounds of an object to accomplish object selection.
Figure 9.2
XArc defintion. x, y width
h
e
i angle1
g
h
angle2 t
height=100;
angle2=270*64
width=100;
The data defining the arc object directly transfers to define the bounds of the object.
bounds.x = 10;
bounds.y = 20;
9
bounds.width = 100;
bounds.height = 100;
Now that we have bounds definitions for sample objects of a line and an arc type, let
us see how to employ them.
By comparing the bounds calculated for each of the objects in the drawing window to
the ButtonPress event location, an object can be deemed intended for selection by
the user.
Consider the bounds calculated for the line object seen in Figure 9.1 and repeated in
Figure 9.4 as compared to the sample event using the gxObjSelect function found in
Listing 9.4.
Listing 9.4 Testing Object Selection
1: Boolean gxObjSelect( Bounds bnds, XEvent *xe )
2: {
3: if( xe->xbutton.x > bnds->x &&
4: xe->xbutton.y > bnds->y &&
5: xe->xbutton.x < (bnds->x + bnds.width) &&
6: xe->xbutton.y < (bnds->y + bnds.height)) {
7:
8: return True; // object selected
9: }
10: return False; // object not selected
11: }
The function shown in Listing 9.4 uses the bounds calculated from the line object
passed to the getLineBounds shown in Listing 9.2.
Comparisons are made to determine whether the event occurred at a point greater
than the origin of the object
3: if( xe->xbutton.x > bnds->x &&
4: xe->xbutton.y > bnds->y &&
Notice that the extents of the object are defined as where the horizontal edge and
vertical edge of the object are placed on the screen relative the object’s origin.
If any element of the if statement fails, the entire test will fail because of the use of
the Boolean AND (&&) function.
Object Bounds Checking Chapter 9 209
Applying the values of the illustration, the line object would appear selected by the
user using this method of determination.
Refer to Listing 9.4 for a demonstration of the application of the ButtonPress event
to the XArc example from Figure 9.3. It too appears to be selected.
The shortcoming highlighted in these examples is the confusion that occurs using
the method of selection shown in Listing 9.4.
Not only do both objects appear to be selected, but also the ButtonPress event did
not even touch the line object.
Consider the ButtonPress event illustrated in Figure 9.5.
Again the ButtonPress event is within the bounds of both the sample objects. This
time the event touches neither of the objects, which, when using the gxObjSelect
from Listing 9.4, makes both objects appear selected.
This is a limitation of the selection function that must be overcome to ensure correct
object control and manipulation.
9
Next Steps
To select objects properly using the Graphics Editor, a method for determining
whether an object is selected must return true only if the ButtonPress event
explicitly falls on the object.
To accomplish a selection function of the required sophistication, an understanding
of certain trigonometric functions must first be discussed for detecting whether a
point falls on a line or lies beyond the arc defined by the end and center points of an
arc object.
Chapter 10, “Trigonometric and Geometric Functions,” demonstrates a finer method
of determining object selection and reviews the mathematical skills necessary for the
task.
In this chapter
• Calculating Point and Line Intersections
• Calculating Slope
Note The function XDrawLines does not implicitly close the object represented by the
point array, meaning that the endpoints are not connected. To accomplish this,
the array would have to be expanded to contain five points and the starting point
repeated as the last element of the array.
The function near_segment shown in Listing 10.1 considers only a single line seg-
ment represented by endpoints
int x1, int y1, int x2, int y2
The final two parameters are the x and y points contained in the ButtonPress event. 10
The function begins by testing the distance of the event point xp and yp from each of
the endpoints defining the line segment.
9: if( abs(xp - x1) <= TOLERANCE && abs(yp - y1) <= TOLERANCE ) {
and
13: if( abs(xp - x2) <= TOLERANCE && abs(yp - y2) <= TOLERANCE ) {
214 Part III Back to School
Note The TOLERANCE variable used in various places throughout the Graphics Editor
project is defined in a header file not yet introduced.
Its use is to set the allowable margin of error for interactive functions. Specifically,
when the user attempts to click on the line object, he might be TOLERANCE dis-
tance from it and still successfully accomplish the selection.
This is necessary because the line object can only be a single pixel wide (line
width of 1). As the event is also a single pixel (x, y) point, the level of precision
required to have the event land exactly on the line object could be too great con-
sidering the steadiness of most of our hands.
EXCURSION
You Must Exercise Your abs
The function abs used in testing the difference between the event points and the line seg-
ment endpoints
abs(xp - x1)
and
abs(yp - y1)
If the event points are an acceptable distance from one of the line segment’s end-
points, the function returns true and the line object is considered selected.
If the event point is somewhere within the line segment, the function continues by
ordering the points defining the line segment.
17: if( x1 < x2 ) {
18: xmin = x1 - TOLERANCE;
19: xmax = x2 + TOLERANCE;
20: } else {
21: xmin = x2 - TOLERANCE;
Trigonometric and Geometric Functions Chapter 10 215
The ordering of the points is accomplished by testing to see whether x1 is less than
x2. If x1 is the lesser of the two points, its value is assigned as xmin, making the value
of x2 the maximum or xmax value.
Note By subtracting TOLERANCE from the xmin value and adding it to the xmax value,
you increase the bounding box defined by the line segment.
Note Notice that the bounds checking being performed here is not the same as the
bounds checking done in the previous chapter.
Here only a single line segment comprising the whole object is being tested,
making for a much finer determination. Further, an event point lying within the
bounds of the segment is used only to determine whether further calculations
and testing should be performed and not whether the object should be consid-
ered selected.
After determining that the event point is within the bounds of the line object, a test
is made to ensure that the x value of the endpoints are not equal, indicative of a verti-
cal line. If the line is vertical, the variables x and y are assigned the x value of the
endpoints (x1) and the y component of the event point (yp) respectively. 10
40: if( x2 == x1 ) {
41: x = x1;
42: y = yp;
If the line is not vertical (x components of the endpoints are not equal), the line is
tested for being horizontal. A horizontal line will have equal y values for the end-
points and the y value of the endpoints and event x component will be used for the
values of the variables x and y.
43: } else if( y1 == y2 ) {
44: x = xp;
45: y = y1;
216 Part III Back to School
If the line is neither vertical nor horizontal, the slope of the line is calculated and
used to determine the values of the x and y variables.
46: } else {
47: slope = ((float) (x2 - x1)) / ((float) (y2 - y1));
48: y = (slope * (xp - x1 + slope * y1) + yp) / (1 + slope * slope);
49: x = ((float) x1) + slope * (y - y1);
50: }
Figure 10.1 demonstrates the relationship of the variables slope; x1, y1; x2, y2; xp,
yp; and x, y.
(x, y)
(x1, y1)
Point Line Intersection. slope = 0 (x2, y2)
(x1, y1)
(x2, y2)
As shown in Figure 10.1, the variables x and y will be some point on the line as
determined by one of the three steps we’ve seen.
Calculating Slope
The slope of a line is defined as the steepness or tilt of the line. It is measured as a
ratio of vertical change (rise) over horizontal change (run). In mathematics, slope is
usually designated by the letter m.
Given a line passing through points (x1, y1), (x2, y2) the slope m of the line deter-
mined by
y2 - y1
m = _________
x2 - x1
as long as x2 ≠ x1.
Note The slope of a vertical line is undefined and the slope of a horizontal line is zero.
For this reason, near_segment special cases these conditions.
Trigonometric and Geometric Functions Chapter 10 217
Note The use of the keyword float in the calculation of slope is called a cast.
Casting variables is necessary to promote their data types either for compatibility
during assignments or precision in calculations.
The cast of the result of (x2 - x1) and (y2 - y1) to the data type of float
ensures that the division operation is done with floating point numbers. A great
deal of precision would be lost if slope were calculated using integers or whole
numbers.
Calculating the x, y point on the line as required in Case 3 of Figure 10.1 is not as
straightforward.
Case 3 requires that functions for determining x and y be derived from one or more
of the forms for writing linear equations and applied to what is known about the line
segment and event point.
48: y = (slope * (xp - x1 + slope * y1) + yp) / (1 + slope * slope);
49: x = ((float) x1) + slope * (y - y1);
EXCURSION
Common Equations for Representing a Line
Linear Equations are functions for representing lines based on the information known
about them.
Table 10.1 shows three of the more common forms for writing linear equations.
For instance, employing the Point-Slope form and solving independently for x and y identi-
fies the point needed by Case 3 of Figure 10.1.
218 Part III Back to School
When the point (x, y) is found, the distance of this point from the event point is cal-
culated:
52: dx = ((float) xp) - x;
53: dy = ((float) yp) - y;
Finally, the line is considered selected if twice the sum of the deltas squared (distance
between the x and y component of the event point and the point x, y found on the
line) is less than the square of the tolerance.
55: if ( (float)(dx * dx + dy * dy) < (float)(TOLERANCE + TOLERANCE) ) {
56: return True;
57: }
When the evaluation of the if condition results in False, the program will fall
through to
59: return False;
To facilitate this representation, X provides the XArc structure for holding the
required arc data.
typedef struct {
int x, y;
Dimension width, height;
int angle1, angle2;
} XArc;
The purpose of the arc_find function found in Listing 10.2 is to determine whether
an event point intersects the arc defined by an XArc structure and specified as the
first parameter.
continues
220 Part III Back to School
Following the declaration of the many variables used to determine whether the event
point intersects the arc is the calculation of half of the major and minor axes.
15: rx = (float)arc_data->width / 2;
16: ry = (float)arc_data->height / 2;
Note The major and minor axis refer to the diameter on the x-axis and y-axis. The
longer of the two is called the major axis and the shorter is the minor axis.
Half the distance of the major and minor axis determines the radius on the x-axis
and the radius on the y-axis.
Independent values are maintained for radius on the x-axis and radius on the y-
axis because the aspect ratio of the arc might not be constant.
Following the calculation of the radii for the x and y-axes, the center point of the arc
is found.
18: cx = arc_data->x + rx;
19: cy = arc_data->y + ry;
Next, we calculate the distance between the center of the ellipse and the foci as well
as the coordinates of the foci. This calculation is dependent upon the orientation of
arc (in the case of a non-constant aspect ratio). So a test is made on whether the
radius on the x-axis is greater than or equal to the radius on the y-axis.
EXCURSION
It Was Not Foci Bearing Gifts at the Nativity?
The foci are fixed points from which all points on the arc (circle or ellipse) are a constant
distance. Other than being a cool word to say, foci are crucial to the definition of an arc.
Foci is plural and in this context refers to two fixed points, one relative to the major axis
and one to the minor axis.
In the case of a circle, the two points are the same and correspond with the center of the
circle.
For an ellipse, the major axis containing the foci is always longer than the minor axis.
Thus, a test to determine the longer of the two axes is necessary to correctly calculate the
distance of the foci from the center and the foci coordinates.
22: if( rx >= ry ) {
This definition of foci prepares you for a definition of a circle and an ellipse to demonstrate
the use of foci.
A circle is defined as
PO + PO = 2a
where a is the radius of the circle, P is any point on the circle, and O is the foci.
(Remember that the major and minor axes are equal and therefore the foci are equal.)
Figure 10.2 illustrates the definition of a circle.
P
10
Figure 10.2
The definition of a
a
circle.
O
P
222 Part III Back to School
As demonstrated in Figure 10.2, PO = a is the radius of the circle and therefore 2a is the
diameter. In other words, distance PO from any point P on the circle to the (convergence)
of the foci O is the constant a.
In an ellipse, however, the foci are not equal and they do not correspond to the center
point of the object, as demonstrated in Figure 10.3.
Figure 10.3 P
Foci of an ellipse.
P f
P P
r
f r
Case 1 Case 2
Major axis = Major axis =
X-axis Y-axis
If we name one focus (singular for foci) f and the other r, we can form the definition of an
ellipse as
Pf + Pr = 2a
Consider the first case demonstrated in Figure 10.3 where the major axis coincides
with the x-axis
22: if( rx >= ry ) {
and
25: f2x = cx + d;
26: f2y = cy;
Consistent with the explanation, the major axis (hmaj) is assigned the value of radius
on the x-axis, and the minor axis (hmin) the value on the y-axis.
27: hmaj = rx;
28: hmin = ry;
Case 2 illustrated in Figure 10.3, however, sets the major and minor axis to
34: hmaj = ry;
35: hmin = rx;
Trigonometric and Geometric Functions Chapter 10 223
More information is required before actually determining whether the event point
intersects the angle.
First, determine the angle measured from the positive x-axis (3 o’clock position) of
the vector (ex, ey), (cx, cy) measured in degrees:
atan2( cy - ey, ex - cx ) * 180/M_PI
Its use in calculating the angle of the vector defined by the center point of the
ellipse and the event point is to convert the Radian value returned by the atan2
to Degrees as the function returns a value in the range –M_PI and M_PI.
41: angle = atan2( cy - ey, ex - cx ) * 180/M_PI;
Next, convert the angle1 and angle2 measurements used by X to degrees. As you
recall from the introduction to XDrawArc in Chapter 7, section “XDrawArc,” page
192, X maintains these angles as integers in the form “degrees × 64.”
45: angle1 = (double)arc_data->angle1 / 64.0;
46: angle2 = (double)arc_data->angle2 / 64.0;
Finally, apply the information gathered and see whether the arc has been selected. 10
The determination is whether the sum of the distances between the event point and
the foci is greater than the major axis by more than TOLERANCE. If it is greater, the arc
has not been selected.
Consider each focus separately. First, (f1x, f1y)
48: if( sqrt( sqr(f1x - ex) + sqr(f1y - ey) ) +
49: sqrt( sqr(f2x - ex) + sqr(f2y - ey) ) > 2 * (hmaj + TOLERANCE))
50: return( False );
224 Part III Back to School
If the sum of the differences is not greater, the function returns true to indicate that
the arc has successfully been selected by the event point.
54: return( True );
EXCURSION
Introducing a Limitation of the X Window System
A limitation of the X Window System is the inability to draw ellipses that have a major axis
and minor axis that are not parallel to the x-axis and y-axis. Figure 10.4 illustrates such an
ellipse.
minor
axis
y-axis
One solution is to convert the ellipse into a series of line segments using a Bézier
algorithm and then treat it like a point-array–based object.
With a superior method of selecting both point-array–based objects and arc objects,
you are ready to review the concepts required to perform geometric transformations.
Next Steps
Chapter 11, “Graphic Transformations,” introduces and satisfies the requirements
for performing actions such as moving, scaling, and rotating the objects of the
Graphics Editor. Understanding these actions (known as geometric transformations)
will bring you close to completing the foundation the last several chapters have so
meticulously worked to lay out.
In this chapter (M04)
• Moving
This is styled M05
• You
Scaling
will learn amazing things and be
Chapter 11 wowed
• You
by the knowledge of Que
• Rotating
• Nextwill learn amazing things
Steps
• You will learn amazing things and be
wowed by the knowledge of Que
• If this is longer please ask editorial to edit
to fit
• Box size does not get adjusted
Graphic Transformations
We have studied calculations for accurately determining when objects in the
Graphics Editor are selected from the canvas, and we now shift our focus to the
actions that can be applied to an object that has been successfully selected.
Graphic transformations, as implied by the phrase, are actions that alter the graphic
object. The Graphics Editor supports the move, scale, and rotate functions for alter-
ing the objects of the editor.
Each of these is discussed in the following sections. As was necessary with the analy-
sis of proper object selection, consideration is given separately to point-based objects
and arc objects because the transformation methods are unique to the data fields of
the varying objects.
Moving
Moving an object, regardless of type, requires the application to maintain the delta
or distance the cursor has traveled between iterations of the move request.
To move an object in the Graphics Editor, first select the object and then press and
hold the right mouse button over the object while moving the cursor. The distance
the cursor travels is applied to the location designator of the object.
For point-array–based objects, the object’s location is implicit to the values of the
points contained in the array.
Arcs, however, have an explicit x, y value that describes their placement in the canvas
window.
226 Part III Back to School
The following two sections show the functions that manage the updates required for
the point-based and arc objects to move, based on the coordinates of an X event
understood to be a PointerMotion event structure.
Moving a Line
Listing 11.1 demonstrates how the Graphics Editor tracks and applies the distance
the cursor has traveled. Although the GXObj has not been defined, you know from
Listing 9.2 in Chapter 9, “Object Bounds Checking,” the definition of the GXLine
structure. This structure provides the unique data field definition for GXObjs of type
line.
The GXLine structure is presented in Chapter 9, section “Point-Array–Based Object Bounds,”
page 204.
34: x = 0;
35: y = 0;
36: }
37: }
The line_move function in Listing 11.1 begins by declaring the local static variables:
3: static int x = 0, y = 0;
EXCURSION
The Keyword static Has Multiple Uses
Contrast the use of the keyword static as applied to local variables compared to its use
in the declaration of the line_move function
1: static void line_move
When applied to local variables, the static keyword ensures that their values are residual
(maintained) between calls to the function. This is critical to the line_move function
because it must retain the (x, y) values of the PointerMotion event driving the placement
of the object being moved.
Applied to the function declaration, however, static simply limits the scope (visibility) of
the function to this file.
The static variables x, y retain the coordinates of the X PointerMotion event, allow-
ing only the incremental delta of the distance the pointer has traveled to be applied
to the points defining the object.
In a moment, you will see that the value of 0 assigned to the x and y is important for
knowing when the move action begins.
The next executable line of the function
5: GXLinePtr line_data = (GXLinePtr)line->data;
declares a variable line_data and assigns it the unique data field from the
GXObjPtr line passed as the first parameter to the line_move function.
After declaring a simple integer for looping, the function tests to see if x and y have
non-zero values: 11
6: int i;
7:
8; if( x && y ) {
228 Part III Back to School
Non-zero values for the x and y indicate that this is not the first iteration of the
move action, an important thing to know because the rubber-banding object must be
erased from the screen.
9: XDrawLines( XtDisplay(GxDrawArea), XtWindow(GxDrawArea), rubberGC,
10: line_data->pts, line_data->num_pts, CoordModeOrigin );
Note The rubber-banding effect used during object creation and transformation in the
Graphics Editor project refers to drawing the object in an interactive mode.
Allowing the user to move an object around the screen, other objects existing on
the canvas are not erased by the drawing and erasing of the object in interactive
mode.
The rubbergc graphic context will draw the object the first time the X Graphic
Primitive is requested and erase it the second time without harming the under-
lying background or objects.
Caution (and careful management) must be applied to the number of calls made to a
speak
In the body of the else, calling the object’s erase method removes the object from
the canvas, and then the values of x and y are set to equal the coordinates of the
PointerMotion event if it exists.
Graphic Transformations Chapter 11 229
Note Although the specific GXObj definition is not introduced until Chapter 15,
“Common Object Definition,” suffice it to say that an object will have methods
(internal functions) for accomplishing all necessary self-management tasks perti-
nent to the type of object it is.
Object methods include functions for drawing, erasing, moving, scaling, rotating,
copying, saving, restoring, and deleting.
Perhaps Chapter 15 should have been titled “A Horse for the Cart.”
Note The object’s method for erasing is not compatible with the call to the X primitive
XDrawLine that is used to manage the rubber-banding effect.
The incompatibility results from the difference in GC function used to draw the
object on the screen.
All the GXObj methods for drawing employ the GXcopy function that cannot be
erased or undone with the GXxor function.
As Chapter 20, “Latex Line Object,” demonstrates, and Chapter 7, “X Lib
Graphic Primitives,” section “GCTile,” page 188 introduced, the method of
erasing objects placed onscreen with the GXcopy is to tile in the canvas
background where the object being erased currently resides.
EXCURSION
A New Form of the if then else Construct
The C language syntax
var = test ? value1 : value2;
is equivalent to
if( test )
var = value1;
else
var = value2;
Although not clear or readily readable, the notation is very common with C programmers.
The necessity of testing for the implicit non-null value of event is to account for the trans-
11
formation function line_move being interrupted or ended.
To abort or end the move action, a null event pointer is passed to the line_move function,
allowing it to reset the values of x and y to zero so the transformation can start over with
another object.
230 Part III Back to School
The implicit test for a non-null event follows the body of the else entered when the
values of x and y were zero.
As discussed earlier, sending a null event pointer causes the function to reset the val-
ues of x and y so the move action can be repeated.
19: if( event ) {
20: for( i = 0; i < line_data->num_pts; i++ ) {
21: line_data->pts[i].x += (event->xbutton.x - x);
22: line_data->pts[i].y += (event->xbutton.y - y);
23: }
If there is a valid event pointer, the difference between the previous and current
PointerMotion event location is applied to every point in the line_data->pts array.
After updating all the points, the object is implicitly placed at the new location and
the rubber-banding object is drawn to reflect this by using the global GC variable
rubberGC.
With the interactive object drawn to the screen, it is imperative that the current
event location be retained so that the next iteration of the function can erase it.
31: x = event->xbutton.x;
32: y = event->xbutton.y;
As the line_move function is invoked, the line object is continually updated to the
new cursor location by adding the difference that the cursor moved from the pre-
vious call.
Graphic Transformations Chapter 11 231
A similar move function must exist for the GXObj of type arc.
Moving an Arc
The method of moving an arc object is simpler than the point-array–based object
because its location is assigned explicitly by the x and y data fields in the XArc struc-
ture.
Listing 11.2 shows the function for performing the move transformation on an arc
object.
The arc_move function shown in Listing 11.2 should be very familiar because it
follows the form of the line_move function except that the data type XArc is being
managed instead of a GXLine.
Focusing only on the differences between the line_move and arc_move functions,
consider the assignments that explicitly place the arc object at the new location.
35: arc_data->x += (event->xbutton.x - x);
36: arc_data->y += (event->xbutton.y - y);
and the graphic primitive, which draws and erases the interactive object:
41: XDrawArc( XtDisplay(GxDrawArea), XtWindow(GxDrawArea), rubberGC,
42: arc_data->x, arc_data->y,
43: arc_data->width, arc_data->height,
44: arc_data->angle1, arc_data->angle2 );
Beyond these few lines, the arc_move function behaves exactly like the line_move
function.
Advancing an understanding of graphic transformations, the next action to consider
is scaling.
Scaling
The transformation of scaling an object refers to changing the object’s height and
width.
For many objects the width and height values refer only to the values defined by the
object’s bounds. Altering the width and height of the bounds of the object will affect
each of the segments comprising the object because they must be proportionally
altered to accomplish or obtain the new bounds of the object.
The challenge of scaling an object is that the x and y deltas (determined as they were
with the move transformation by calculating the difference of the current and
Graphic Transformations Chapter 11 233
previous event locations) are not applied linearly to the object. Rather, the direction of
the scale action must be considered and a greater portion of the delta applied to the
side corresponding to the direction of the scale than to the opposing side.
The Graphics Editor applied eight handles to an object made active by the selection
process.
Figure 11.1 and Figure 11.2 illustrate how the handles are placed on Line and Arc
objects respectively.
Figure 11.1
Illustrating the Line
object’s handles.
Figure 11.2
Illustrating the Arc
object’s handles.
The placement of object handles when the user selects an object corresponds to
every corner and every side of the object as shown in Figures 11.1 and 11.2.
A scale action is instigated when the user selects an object to reveal its handles, posi-
tions the mouse cursor over the desired handle, and presses and holds the left mouse
button to select the handle.
As long as the left mouse button is pressed, subsequent PointerMotion events will
alter the width or height of the object in a manner relative to the handle selected. 11
When the mouse button is released, the object is redrawn with the new dimensions.
Returning to the discussion of the scaling challenge, consider the object shown in
Figure 11.1. If the handle on the right side of the object is selected for the scale
action, the point within the object closest to the handle must change with a greater
proportion of the delta than the point furthest away.
234 Part III Back to School
speak If all points changed an equal amount, the result would be a move action instead of
the intended scale action.
geek
The final challenge when performing the scale transformation is the loss of data
integrity.
As calculations are made to determine the proportion of the delta to apply to the
object fields controlling the object’s dimensions, rounding errors will occur.
EXCURSION
Rounding Errors Are a Loss of Precision
Rounding errors occur when a floating-point number such as 1.5 is assigned to a variable
of type int (integer). The portion of the number following the decimal point is lost because
an integer cannot represent a decimal value.
This can occur when the delta being applied to a point is 3; however, due to the position
of the point within the object relative to the handle controlling the scale action, only 50 per-
cent of the delta is applied.
The result is (3 × 0.5) or 1.5 applied to the data point.
Further, it is necessary to use integer variables to represent the points or data fields within
the graphic objects because it is impossible to draw half a pixel; therefore, everything
must be rounded to the nearest integer number or whole pixel.
As rounding errors compound, the data representing the object is affected, and, for
instance, lines are not as straight as they should be.
The function implementing the scale transformation must account for rounding
errors. One method to minimize the effect of rounding errors is to ensure that all
transformations are done to the original points or data fields. In this way, the round-
ing errors will not compound. At worst only a fraction of a pixel is lost, as opposed
to a fraction of a pixel being lost for each iteration of the scale transformation.
The following sections present the functions for scaling point-array–based and arc
objects accounting for applying only a portion of the delta based on the handle
selected and ensuring data integrity by minimizing rounding errors.
Scaling a Line
The code in Listing 11.3 is the entry point for accomplishing scaling of point-
array–based objects. As discussion focuses on the body of the function, keep in mind
the tasks that must be accomplished by the scale transformation: namely, properly
proportioning the delta applied and minimizing the effects of rounding errors.
Graphic Transformations Chapter 11 235
In reading through Listing 11.3, you have probably identified that the basic structure
11
of the functions follows the management behavior of the move functions seen in pre-
vious sections.
It is still essential for the function to distinguish between the first iteration of a scale
action and subsequent iterations.
236 Part III Back to School
As with the move functions seen in Listing 11.1 and Listing 11.2, the actual graphic
object must be erased from the screen and the interactive rubber-banding object
drawn instead.
The scale action, however, takes on another responsibility as well. Specifically, it
maintains a pointer to a temporary GXLine structure
3: static GXLinePtr tmp_data = NULL;
for managing when to erase the rubber-banding object, and for ensuring that the
transformation is only applied once to the original set of points defining the object.
Consider the body of the else, which like the move function’s use of x and y indi-
cates that this is the first iteration for the current scale action, as no tmp_data has
been created (implicit null test).
9: } else {
10: /* our first time... */
11: (*line->erase)( line );
In addition to ensuring that the object’s method is invoked to erase the object, the
body of the else creates a new reference to a GXLine structure and assigns this value
to tmp_data.
13: tmp_data = (GXLinePtr)XtNew(GXLine);
EXCURSION
Introducing a New Function for Allocating Memory
The XtNew function is provided by the X Toolkit Intrinsics library. It is a function that could
have an equivalent in C that looks like
tmp_data = malloc( sizeof(GXLine) );
However, this is a good time to introduce it. It is important to remember that any memory
allocated by an application must at some point be freed.
The call to free the memory allocated with the XtNew function is XtFree as you’ll see
shortly.
The function then assigns to tmp_data the number of points it will manage and cre-
ates a valid array for storing the same number of points as the object being scaled:
14: tmp_data->num_pts = line_data->num_pts;
15: tmp_data->pts =
16: (XPoint *)XtMalloc( sizeof(XPoint) * tmp_data->num_pts );
The tmp_data structure stores the original points defining the object. This allows the
scale function to apply the transformation to the temporary copy of the points with-
out affecting the original object.
Graphic Transformations Chapter 11 237
Subsequent calls to the line_scale function result in the original points being copied
into the tmp_data structure and again having the transformation applied to the tem-
porary data structure, leaving the original data unaffected.
22: memcpy( (char *)tmp_data->pts, (char *)line_data->pts,
23: sizeof(XPoint) * tmp_data->num_pts );
24:
25: apply_delta( tmp_data->pts, tmp_data->num_pts,
26: FixedX - event->xbutton.x,
27: FixedY - event->xbutton.y );
The result is that only one transformation is ever applied to the data points in order
to minimize the effects of rounding described earlier.
EXCURSION
Adding Functions to Your Memory Management Repertoire
The function memcpy is provided by the standard C library, and, as the name implies,
serves to copy data from one area of memory to another.
In this context it is used to copy the original points contained in the line_data->pts array
to the tmp_data->pts array.
The syntax of the call is
memcpy( destination, source, total size );
Because the memcpy function is written to transfer data of any type from the source
address to the destination address, the structure references are cast to character points
(char *) to satisfy the function’s prototype found in the header
#include <stdlib.h>
Optionally, a for loop could be formed to explicitly copy all the points from one array to
another.
Return to the body of the else indicating the first iteration of the scale action to
examine the function call
17: get_bounds( line_data->pts, line_data->num_pts,
18: &OrigX, &OrigY, &ExntX, &ExntY );
The variables OrigX, OrigY, ExntX, and ExntY are global variables used to define the
upper and lower bounds of the object being scaled. Their use will become clear when
11
we examine the apply_delta function.
Before discussing apply_delta however, let’s look at the use of FixedX and FixedY
passed as parameters the apply_delta function.
238 Part III Back to School
Having reserved the introduction of the cursor management function that invokes
the line_scale, line_move, and arc_move functions evaluated in this chapter, accept
for the moment that the value of FixedX and FixedY are assigned the location of the
object handle at the start of the scale action.
Unlike the move function, which applied an incremental delta to the points of the
object, the scale functions apply the total delta with each iteration.
This is necessary because the original object data points are copied into the tmp_data
structure with each call to the line_scale function. The goal is to affect the points a
total of one time by the scale operation. So the total delta from the start of the oper-
ation is applied to the original data points, instead of an incremental delta being
applied to previously scaled points.
To calculate the overall delta from the start of the operation, the variables FixedX
and FixedY record the location of the ButtonDown event that started the scale action.
The values of FixedX and FixedY are then subtracted from the current event point to
determine the distance the mouse cursor has moved since the start of the operation.
Now that you understand how the parameters to apply_delta are formed and the
purpose they serve, consider Listing 11.4, which introduces the apply_delta func-
tion declaration.
26: case 7:
27: apply_delta_left_side( data, num, dx, dy );
28: break;
29: default:
30: setStatus( “LINE: The end is nigh!” );
31: }
32: }
continues
240 Part III Back to School
Only four directions require calculations when applying the scale delta to a line
object. They are apply_delta_right_side, apply_delta_left_side,
apply_delta_top_side, and apply_delta_bottom_side.
The functions shown in Listing 11.5 that combine directions are straightforward and
require no explanation.
Consider instead the function
2: apply_delta_bottom_side( XPoint *pts, int num_pts, int dx, int dy )
Graphic Transformations Chapter 11 241
the function tests the ExntY value to ensure that it is non-zero. This prevents a fatal
divide by zero, as you’ll see shortly.
5: if( ExntY == 0 ) return;
The function continues by looping through all the points in the points array pts.
Remember, this array was tmp_data->pts as passed by the calling function, meaning
the values being altered are a copy of the original points.
9: pts[i].y -= (int)( dy *
10: ((float)(pts[i].y - OrigY) / (float)ExntY));
Because the function’s purpose is to apply only the scale delta to the bottom of the
object, only the y values are affected.
The variables dx and dy were passed from the calling function as the result of the
evaluation of
FixedX - event->xbutton.x
and
FixedY - event->xbutton.y
To determine the proportion of the dy value to apply to each data point, the ratio of
the point’s distance from the origin of the object and the extent of the object is used.
Note The variable ExntY (globally set in the calling function line_scale) is the y value
of the lower-right corner of the object. Similarly, OrigY is the upper-left corner of
the object.
The effect of this calculation is that when the bottom handle (or either bottom cor-
ner handle) is used to control the scale action, the farther from the origin of the
object the data point lies the greater the portion of the delta applied to that data
point.
The same ratio is used in the other side functions differing only in which component
11
of the delta is applied as dictated by the direction of the scale action. In other words,
the left and right side actions only apply the x delta (dx) to the x component of the
data points and the top, like the bottom, scale direction only affects the y value by
some portion of the y delta (dy).
242 Part III Back to School
Note The reference to side functions for applying the scale delta is meant to generi-
cally refer to any of the apply_scale_<direction> functions, where direction is
top_side, bottom_side, left_side, and right_side.
A final difference is that the bottom scale direction deducts the calculated portion of
the delta, and the top scale direction adds the portion that is determined by the ratio
of the point’s distance from the extent relative to the origin. Effectively, the top scale
direction inverts the bottom scale operation as is necessary for scaling in the opposite
direction. For the same reason, the left and right scale operations are inverted.
Having successfully applied the delta to the sides dictated by the active scale handle,
the line_scale function draws the rubber-banding object to the canvas window, as
seen in Listing 11.3, lines 29–30:
29: XDrawLines( XtDisplay(GxDrawArea), XtWindow(GxDrawArea), rubberGC,
30: tmp_data->pts, tmp_data->num_pts, CoordModeOrigin );
At some point the line_scale function will end as indicated by the passing of a null
event point. When this happens, the second else statement is evaluated
31: } else {
the contents of the temporary storage structure’s XPoint array pts are transferred to
the line object’s data pts array
33: memcpy( (char *)line_data->pts, (char *)tmp_data->pts,
34: sizeof(XPoint) * line_data->num_pts );
Note Remember that the points contained in the temporary structure were only trans-
formed once. The points transferred to the line object are the points gained from
the last iteration of the line_scale function.
Note When freeing nested pointers such as the pts field contained within the allo-
cated tmp_data structure, you must free them in the reverse order that they were
allocated.
This is necessary because after a pointer is freed it can no longer be legally ref-
erenced.
In others words, it would be a dangerous (and potentially fatal) mistake to free
tmp_data and then reference it to free the pts field.
When freed, the value referenced by the tmp_data variable is no longer valid.
Therefore set its value back to NULL in preparation for a subsequent scale action
request.
39: tmp_data = NULL;
Look now at the arc equivalent to the line_scale function shown in Listing 11.3.
Scaling an Arc
The arc_scale function shown in Listing 11.6 is nearly identical in its structure to
the line_scale function seen in Listing 11.3.
continues
244 Part III Back to School
Comfortable with the structure of this function based on the study of the line_scale
function, note the changes in data references to account for the different object type.
Listing 11.7 shows the apply_delta function for applying the scale delta to the arc
object. It, too, is nearly identical in structure to the apply_delta function used by the
line_scale function.
EXCURSION
A Review of the Graphics Editor Project Structure
As demonstrated in the Graphics Editor program structure seen in Chapter 6,
“Components of an X Window Application,” there is total separation of source code for
line, arc, and text object routines.
This, as indicated in Appendix B, “Application Layout Code Listing,” is accomplished by
having multiple program files. In this way the code controlling line objects is placed in a
different file than the code for arcs.
Further, through use of the keyword static within the files containing the source code for
the various objects, the function names can be reused for routines of similar purpose.
This is the case with the apply_delta function. As is made clear in Chapter 13,
“Application Structure,” the resolution of the varying apply_delta functions is determined
by the version of the function co-located in the file containing the source code for the dif-
ferent objects.
Graphic Transformations Chapter 11 245
continues
246 Part III Back to School
As with line scaling, only the side functions actually perform calculations. The corner
functions simply invoke the correct combination of side routines to accomplish their
task.
Consider the function
1: static void apply_delta_top_side( XArc *data, int dx, int dy )
for applying the scale delta to the top side of the arc object. Evaluating it will shed
light on how all the arc side functions work because the relationships follow those
specified with the line side functions
5: y1 = data->y;
6: y2 = data->y + data->height;
7:
8: data->y = min( y1 - dy, y2 + dy );
9: data->height = max( y1 - dy, y2 + dy ) - data->y;
By storing the upper and lower corner y values, the apply_delta_top_side function
can determine the new values for the y and height field of the XArc structure.
The logic applied here is that the minimum point between the upper-right value less
the delta and the lower-right value plus the delta will be the new y value for the arc.
This minimum evaluation of the points is important for determining when the user
has, for instance, selected the handle in the lower-right corner and then dragged it to
a value less than the upper-right corner of the object.
In other words, if the scale action flips the arc object, the apply_delta_<direction>
routine must account for this, and not allow the y value to be less than the y + height
value.
A look at the remaining side functions shows that the logic is the same, changing only
the components affected and the direction in which the delta is applied. For instance,
left and right side functions change only x and width values, where the top and bot-
tom affect only the y and height values.
As you advance in the development of the Graphics Editor project and apply the
concepts introduced in this chapter, the ideas will become clearer.
The last transformation to consider before doing so, however, is rotation.
11
Rotating
As the name implies, the rotation transformation revolves objects around a center
point. For the purpose of the context editor, this center point will coincide with the
center of the object as is demonstrated in the next section.
248 Part III Back to School
Rotating a Line
Rotating a point-based object such as a line is most simply done by rotating each
individually.
Figure 11.3 shows the relationship of a point to a 90-degree angle. By representing a
point in this way, the dissection of the right angle allows a relationship to be derived
for performing the rotation.
The point (x, y) in Figure 11.3 is the point prior to rotation and point (x1, y1) is the
point after rotation. To move from (x, y) to (x1, y1), you must apply the definition of
the sine and cosine of an angle.
As you might recall from early trigonometry study, sine is defined as a function that
maps an angle to the y coordinate of the angle’s intersection with the unit circle.
Similarly, cosine is the function that maps an angle to the x coordinate of the inter-
section.
Applying these definitions, the points x and y can be represented in terms of the
angle > and radius r as
x = r cos >
y = r sin >
Therefore, to determine the values of x1 and y1 you only need to add the angle = to >
as seen below.
x1 = r cos (> + =)
y1 = r sin (> + =)
Solving for x1
r cos (> + =) = r cos >cos = + r sin > sin =
Substituting the value of x for (r cos >) and y for (r sin >) yields
Graphic Transformations Chapter 11 249
x1 = x cos = - y sin =
or
y1 = y cos = + x sin =
There are only a couple more issues to resolve before coding the solution for
rotating a point.
First, the proof shown for calculating x1 and y1 assumes that the angle’s origin is at (0,
0). Second, as demonstrated in Figure 11.3, the point (x, y) does not sit exactly on
the perimeter of the circle.
Both can be resolved by applying a principle introduced in Chapter 10,
“Trigonometric and Geometric Functions.” All points are a constant distance from
the foci of a circle. Therefore, the point before rotation must be transformed to an
origin of (0, 0) and the point after rotation must be transformed to a point equal to
the magnitude of the original point’s distance from the circle the point is perceived
to sit on.
See Chapter 10, page 218, for a discussion of point and arc intersection.
continues
250 Part III Back to School
The function itself expects that a pointer to the point being rotated be passed as the
first parameter, the degrees that the point should be rotated as the second, and lastly
the coordinates of the center point of the object containing the point
2: void rotate_point( XPoint *pt, int deg, double cx, double cy )
The variables employed by the function are of the data type double
4: double dx, dy, theta, cosa, sina, mag;
As you might recall from Chapter 2, “Programming Constructs,” section “Data Types,” page 70, the
data type double is significantly more precise than float.
Precision during rotation is a serious issue. Consider the quantity of floating point
operations required to accomplish the rotation of a point. Further, moving the float-
ing point results to an integer variable (whole pixel representation) inherently causes
further loss of precision. The use of the data type double aids in minimizing loss of
precision during the rotation calculations.
Next, the rotate_point function must calculate the foci in the same way it was done
in Chapter 10.
6: dx = (double)pt->x - cx;
7: dy = cy - (double)pt->y;
The function gx_compute_angle applies the definition of sine and cosine by working
backward to determine the angle based on the intersection points. The
gx_compute_angle is introduced later.
Bounds checking is performed on the angle theta to ensure that it is in the proper
range for the rotation operation.
19: if (theta < 0.0) {
20: theta += M_2PI;
21: } else if (theta >= M_2PI - 0.001) {
22: theta -= M_2PI;
23: }
The magnitude of the foci is calculated so that it can be applied to the new point
resulting from the rotation
25: mag = sqrt(dx * dx + dy * dy);
Finally, the value of the rotation point is assigned to the XPoint structure pointer,
replacing the previous values of the point with the rotated values.
30: pt->x = gxround(cx + cosa);
31: pt->y = gxround(cy - sina);
Notice that in conjunction with the assignment of the rotated point to the XPoint
structure, the point is transformed relative to the center coordinate of the object
containing the data point. Also, the gxround macro is employed to ensure that the
values are rounded up to nearest whole pixel representation.
I show the management function that invokes the rotate_point for point-
array–based objects shortly, but first consider the concept of rotating arcs.
Rotating an Arc
Because rotating a circle of 360 degrees has no meaning, the issue of rotating an arc
is limited to ellipses and arcs of less than 360 degrees.
Rotating an Ellipse
11
As a caveat at the end of Chapter 10, the X Window System is incapable of repre-
senting an ellipse with a major and minor axis that is not parallel with the x and y
axes.
Therefore, the rotation of an ellipse is not supported by the Graphics Editor
projects.
252 Part III Back to School
Next Steps
The next chapter will introduce coordinate systems and illustrate the need to under-
stand the system in which graphic transformations are performed. This is not impor-
tant to the success of the Graphics Editor project, but will provide clarity to the issue
of graphic transformations.
Demonstrated with the use of the X Window System primitives and X Window cre-
ation in Chapters 7 and 8, the origin is consistently in the upper-left corner of the
screen. However, when the mathematics for rotating a point is discussed, the origin
relative the operation was silently made the lower-left corner (refer to Figure 11.3).
Determining the origin for an operation or calculation is a factor of the coordinate
system being employed.
Following the brief discussion on coordinate systems, the foundation will be com-
plete, and full attention will focus on furthering the Graphics Editor.
In this chapter
• Rectangular Coordinates
• Polar Coordinate System
Coordinate Systems
The concept of coordinate systems must be addressed to make a book on the
X Window System and graphic transformations complete.
This chapter introduces coordinate systems and programmatic considerations for
each of them.
As the last building block in the foundation preparing us to focus solely on program-
ming the Graphics Editor, this chapter marks the end of Section One.
A coordinate system determines the positive and negative directions of the x and y axes
and their relationship to horizontal and vertical placement. Further, a coordinate
system dictates the location of the origin or (0,0) position within the representation.
In computer graphics, a primary concern is in locating points on the surface of the
canvas or drawing area. To specify a location requires knowledge of the coordinate
system in use.
In a windowing environment, coordinates within the system are discrete, meaning
that they represent the pixels used to position the elements bearing the coordinates.
Every Drawable (Window or Pixmap) in the X Window environment maintains its own
coordinate system or management of element placement within its bounds.
The coordinate system employed by the Drawables in the X Window environment
has its origin in the upper-left corner of the Drawable with the x-axis increasing to
the right and the y-axis increasing downward, as depicted in Figure 12.1.
254 Part III Back To School
Figure 12.1 0, 0
increasing x
The coordinate system
increasing y
used in the X Window
environment.
Within the X Window System’s coordinate system, negative numbers can be used to
represent coordinates; however, negative coordinates affect visibility.
The following sections introduce two common coordinate systems employed in
computer graphics and windowing environments.
Rectangular Coordinates
A rectangular coordinate system has horizontal and vertical axes that are right angles to
each other. The coordinates within the rectangular coordinate system are evenly
spaced in both the x and y directions. The origin of the rectangular coordinate sys-
tem is at the junction of the two axes, as shown in Figure 12.2.
Figure 12.2
The rectangular coordi-
incr y
(+y)
nate system.
decr x
(–x)
0, 0 incr x
(+x)
decr y
(–y)
The rectangular coordinate system is also known as the Cartesian coordinate system. It
is commonly used in digital graphics systems because of its easy adaptation to raster
displays.
Review Chapter 8, section “Raster Graphics,” page 199, for a review of vector versus raster
graphics.
A polar coordinate system defines angles and distances rather than scalar values.
A scalar value in this context refers to an integer data type that is scaled in a linear
speak
manner.
geek
The coordinate points in a rectangular coordinate system are known as scalar values,
whereas the polar coordinate system uses a combination of angles and distances to
indicate placement in system.
In the polar coordinate system, the position of a point (coordinate) is defined as the
angular deflection of a line.
The endpoints of the line are formed from the origin and the specified location of
the point.
To represent a location in the polar coordinate system, points are represented in
terms of an angle called theta (θ) and a radius (the length of the line) referred to as
rho (ρ).
Figure 12.3 illustrates the relationship between theta and rho within the polar coor-
dinate system
Figure 12.3 90
point
rho
The polar coordinate
system. x theta
180 0
270
Applying what you now understand about coordinate systems, it should be obvious
why both coordinate systems are useful to software engineers.
The placement of windows and the drawing of primitives in the X Window System
environment uses a coordinate system very similar to and obeying many of the same
properties as the rectangular coordinate system.
However, accomplishing graphic transformation such as rotation is most easily done
using the polar coordinate system.
Related to the employment of a coordinate system, software engineers also must be
aware of measuring conventions used within the system. 12
256 Part III Back To School
This issue will present itself again when adding a print driver to the Graphics Editor
application because the representation of coordinates on a screen or monitor is
affected by units of measure and scale factors when transferring the coordinates to
paper using the PostScript language.
Next Steps
This chapter concludes Section One, “Starting Points,” and the foundation I
thought imperative before approaching the Graphics Editor Project.
As you begin the Section Two, “Graphics Editor Application,” you should be ade-
quately prepared, either directly by skipping over the Starting Points or indirectly by
spending time with the introduction the initial chapters provide.
13
14
Graphics Editor Application 15
This section focuses strictly on developing the Graphics Editor project.
With a solid understanding of the concepts necessary for accomplishing C and X
Window System programming provided in Section One, I pay less attention to the 16
details of the language and environment and more attention to the structure and
execution of the Graphics Editor project.
17
Where to Begin 18
Unlike Section One, this section does not afford the flexibility to start anywhere
except at the beginning. Each chapter in this section builds on the previous one.
As the project advances, code samples reflect only the changes to functions or files 19
previously introduced, when possible to do so without sacrificing clarity.
20
What’s at the End
At the completion of Section Two, you will have a functional, two-dimensional
graphical editor capable of drawing, moving, scaling, rotating, and altering the 21
attributes of a variety of graphic objects.
22
23
24
25
26
27
28
29
Part IV
Application Structure
This chapter launches the Graphics Editor project by structuring the main program
file and creating the graphical user interface.
Before proceeding, it is important that, independent of the knowledge and experi-
ence you bring to the project, you have reviewed the necessary sections in Part I to
prepare adequately for the information presented here.
Project structure and organizations are critical elements of software design. As the
Graphics Editor project progresses, it will grow in complexity very quickly.
The goal of this chapter is to lay out the application structure, including graphical
user interface, parsing the command line, and finally, configuring the Canvas for
receiving and processing the X events that will control user actions.
Because much of the source code for structuring the application is introduced in
Chapter 6, focus here is given mainly to new ideas.
Note As new features are added to code listings already introduced, I will show incre-
mental listings. An incremental listing generally shows only the line before and
after the lines being inserted.
When starting a new application, you should first consider how the code will be
structured based on elements present in the project. Second, you should consider
how the application will be structured.
262 Part IV Laying Out the Parts
Project Structure
Structuring the code addresses the issue of where source and header files will reside
relative to each other. Addressed as well is the placement of the object and executable
files.
Note The directory structure for the Graphics Editor was introduced in Figure 1.11.
If you haven’t reviewed make file syntax (discussed in Chapter 1, “UNIX for
Developers,” in the section “Makefile”) and how to structure the project correctly,
as discussed in the “make.defines” section of Chapter 1, you should do so now.
The layout and project management presented in Chapter 1 is intended for
multiple-platform support. In other words, through use of the GNU make utility
and directory layout introduced in Chapter 1, the project can be built successfully
using any version of the UNIX operating system.
Table 13.1 shows the organization of the application source code by describing the
contents of each of the files contained in the project and showing a relative path for
their placement.
The descriptions provided in Table 13.1 will serve as a guide for how the source code
of the Graphics Editor project is structured.
Note Because the files introduced in Table 13.1 are relative, it is expected that a pro-
ject root directory (value of GxHome variable in the make.defines) be decided
before implementing the structure to support the introduction of the files in the
project.
Let us focus now on the structure of the application by deciding how best to lay out
the graphical user interface.
Making the decision of what goes where on a graphical user interface requires under-
speak
Management Functions
Copy
Cut
continues
264 Part IV Laying Out the Parts
Assigning Attributes
Foreground color
Background color
Fill Style
Line Width
Degrees of Rotation
Miscellaneous
Exit
Great care must be given to the production of any graphical user interface to ensure
that elements of aesthetics and intuitiveness are well balanced.
Intuitiveness
If features are hidden from the users or not logical in their placement within the user
interface, users will probably not use them much. This has an obvious and direct
impact on the impression of quality assigned to the application.
Aesthetics
The aesthetics of the interface is as important as creating an interface that is intuitive
in its use. The attractiveness of the design has equal impact on the impression of
quality.
Addressing the issues of creating an intuitive and aesthetically pleasing interface
based on the features that must be supported by the application, you can begin to
sketch either mentally or literally what the interface should look like.
Figure 13.1 shows my mental image of what the interface should look like.
Figure 13.1
The Graphics Editor
Object
interface. Canvas
Management 13
Functions
Miscellaneous
The only feature listed in Table 13.2 not addressed in the proposed interface shown
in Figure 13.1 is a mechanism for changing the attributes of objects.
To address this requirement, a menu listing the possible attributes will be presented
to the user when she right-clicks over an active object.
This approach does not adhere to the requirement of being initially intuitive because
the user will not know that the menu exists until instructed of its presence. The solu-
tion does offer two things, however. First is the chance to demonstrate programmati-
cally how to create pop-up and cascading menus and second is a tidy workspace that
is not littered with icons that are not general to the entire application.
The following section shows how to create the interface presented in Figure 13.1.
continues
266 Part IV Laying Out the Parts
Listing 13.1 begins, following some comments, by including header files from
various sources.
The first two come from the C environment and the delimiters < and > surrounding
the filename provide a hint for where the C preprocessor should look for them:
6: #include <stdlib.h>
7: #include <stdio.h>
These header files provide most of the commonly used built-in functions provided
by the C language and are generally included at the beginning of all C source files.
The next few included header files are supplied by the X Window System develop-
ment environment:
Application Structure Chapter 13 267
EXCURSION
Including a Path Component with Header Files
Header files often contain path components in addition to the actual filename as seen with
the three header files included from the X environment.
The convention is to specify only to the compiler the home directory of header locations
when using the compiler flag -I. From the home or root component the compiler is
instructed to search, subdirectories are nested with the header filename.
For example, the compiler flag -I used to compile the file shown in Listing 13.1 would
include the path component
-I /usr/include
Notice that the header files provided by X, like those provided by C, are delimited
with < and > symbols. The following two header files, however, use double quotes
(“ “) to contain the filenames, indicating to the preprocessor that the files are local.
13: #include “gxGraphics.h”
14: #include “gxProtos.h”
Although the C preprocessor will find the files regardless of how the filenames are
delimited, the search order is determined by these delimiters and therefore affects
the speed with which the files are located.
268 Part IV Laying Out the Parts
Another advantage of correctly delimiting the filenames is to ensure that anyone who
follows behind you will know where to begin looking as well.
The header file gxProtos.h, however delimited, is included to ensure that the func-
tions invoked in this file but defined elsewhere in the project are correctly proto-
typed.
As you scan further through Listing 13.1 you will identify create_canvas,
create_status, and create_buttons as functions unique to the Graphics Editor pro-
ject. The forward declaration for these is contained in gxProtos.h and required
before the compiler can successfully check whether the parameter types and return
values are consistent with the usage. This level of error checking done by the com-
piler prevents many semantic and syntactical errors introduced by the programmer.
Because many of the functions prototyped in gxProtos.h require references to struc-
tures defined by the project, the inclusion of gxGraphics.h provides the necessary
type definitions to satisfy the compiler. This implies that the order the files are
included is important as well, as the compiler must have the type definitions before
they are valid for use.
Listing 13.2 shows the contents of the gxGraphics.h file.
In its current state, gxGraphics.h is a very simple file. As the Graphics Editor project
grows in complexity, so will the gxGraphics.h header file.
After ensuring that the Intrinsic.h header file has been included to resolve the 13
Widget data type used later in the file, gxGraphics.h checks for the existence of an
environment directive _GX_GRAPHICS_H_INC_. If the variable has not been defined,
gxGraphics.h defines it to ensure that a portion of the file is included only once:
8: #ifndef _GX_GRAPHICS_H_INC_
9: #define _GX_GRAPHICS_H_INC_
This mechanism for preventing multiple inclusions of the same file is common in
C syntax when new data types or variables are created because multiple inclusions
would redefine the types or variables defined the first time the file was included.
Although gxGraphics.h is in its fledgling state, the syntax is included for future
expansion. Line 11 of Listing 13.2 closes the implicit body of the ifndef directive:
11: #endif /* _GX_GRAPHICS_H_INC_ */
Not meant to prevent multiple inclusions, the test of the presence of GLOBAL indi-
cates whether this is the first inclusion of the file. If so, it declares the variables pref-
aced with the GLOBAL variable as in the first iteration. GLOBAL is defined to be nothing:
20: GLOBAL Widget GxStatusBar;
21: GLOBAL Widget GxDrawArea;
Subsequent inclusions of the header file and the test for the presence of GLOBAL result
in GLOBAL being redefined to the keyword extern.
This is a powerful mechanism that enables a variable to first be declared and subse-
quently externed. Effectively, the first file to include this header is responsible for
declaring all the global variables used by the project. Further, inclusions simply
inform the compiler that the variable has already been declared for use.
Listing 13.3 shows the contents of the gxProtos.h file in its current state. This file
also grows in complexity as more and more functions are shared throughout the
project.
continues
270 Part IV Laying Out the Parts
Notice that the use of EXTERN follows the same form and serves the same purpose as
the environment directive GLOBAL used in gxGraphics.h. However, a different name
is used here to avoid conflicts in the compiler environment.
13
Many functions here have not been introduced yet. Focus for the moment on only
the lines
19: EXTERN Widget create_canvas ( Widget );
20: EXTERN void create_status ( Widget, Widget );
21: EXTERN void create_buttons( Widget );
To the toplevel widget returned from the initialization of the X environment, func-
tion main in gxMain.c adds a form widget as its sole child:
30: form = XtVaCreateWidget( “topForm”,
31: formWidgetClass, toplevel,
32: NULL );
To the form widget are added a canvas area, a status bar, and the object management
and control buttons:
34: canvas = create_canvas( form );
35: create_status( form, canvas );
36: create_buttons( form );
Prototypes for these functions are found in gxProtos.h; however, the actual function
definitions are in the file gxGraphics.c as seen in Listing 13.4.
272 Part IV Laying Out the Parts
continues
274 Part IV Laying Out the Parts
Lines 8–12 of Listing 13.4 issue the include compiler directive for inclusion of the
header files required to resolve variable, structure, and prototype definitions external
to the gxGraphics.c file.
The first function defined in gxGraphics.c shown in Listing 13.4 is the
create_canvas routine, and at lines 17–40.
The create_canvas function accepts one parameter for specifying the parent of the
form widget it creates to act as the canvas area. Notice that the variable storing the
value returned by the creation function is the global variable GxDrawArea, which is
found in gxGraphics.h.
The placement of the canvas area created by create_canvas is specified as having its
edges chained to the corresponding edges of the parent. Clearly, the parent is
expected to be of the class formWidgetClass because relative positioning has no
meaning with manager widgets of other classes.
In Chapter 5, the section “The Form Widget,” page 136, demonstrates the use of composite
resources for specifying relative placement of the children it manages.
276 Part IV Laying Out the Parts
Finally, the create_canvas function registers for the receipt of X events that would
not normally be sent to a form widget.
By use of the X Toolkit Intrinsic call XtAddEventHandler, the create_canvas function
can request that the function drawAreaEventProc is called when PointerMotion
events are received. The prototype for drawAreaEventProc is found in gxProtos.h,
and its definition will be introduced shortly.
Although the variable GxDrawArea is global to the project, its value is returned for use
by the calling function main found in gxMain.c. Even though main could refer to the
value by the global reference, the return is included to demonstrate its use.
Continuing in the review of gxGraphics.c, consider lines 42–57, which define the
function create_status.
The create_status function creates a label widget for use as place to display
context-sensitive help messages. Assignment of the help message is seen with the
definition of the object drawing and management buttons. Optionally, messages can
be explicitly displayed by invoking the setStatus function that will be introduced
shortly.
Notice in the placement of the label widget (lines 49–54) that the resource
XtNfromVert used to indicate the vertical placement of the widget is relative to the
value of the second parameter passed to the function. Looking back at Listing 13.1 at
the invocation of the create_status function reveals that the second parameter is the
GxDrawArea widget.
Lines 59–68 of the file gxGraphics.c shown in Listing 13.4 define the function
statusProc that is used to process the event handler assigned to buttons created by
the function create_icons.
The function statusProc is responsible for updating the GxStatusBar label widget to
reflect the context-sensitive help message for any button that gains focus.
The create_icons function is located at lines 70–97 in Listing 13.4 and is respon-
sible for parsing the GxIconData reference passed as the second parameter from the
function create_buttons function.
The definition of the GxIconData structure pointer dictates everything about the but-
tons being added to the interface.
Listing 13.5 introduces the gxIcon.h header file containing the GxIconData refer-
ences, bitmap icons, callback functions, and context-sensitive help strings used to
define the icons created for the application.
Application Structure Chapter 13 277
101: 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
102: 0x00,0x00,0x00,0x00,0x00,0x00};
103: icon_static( box_icon, box_bits, 36, 32 );
104:
105: static unsigned char arrow_bits[] = {
13
106: 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
107: 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x00,
108: 0x00,0x00,0x80,0x02,0x00,0x00,0x00,0x40,0x04,0x00,0x00,0x00,
109: 0x20,0x08,0x00,0x00,0x00,0x10,0x10,0x00,0x00,0x00,0x08,0x20,
110: 0x00,0x00,0x00,0x04,0x40,0x00,0x00,0x00,0x02,0x80,0x00,0x00,
111: 0x00,0x01,0x00,0x01,0x00,0x80,0x00,0x00,0x02,0x00,0x40,0x00,
112: 0x00,0x04,0x00,0x20,0x00,0x00,0x08,0x00,0xe0,0x07,0xc0,0x0f,
113: 0x00,0x00,0x04,0x40,0x00,0x00,0x00,0x04,0x40,0x00,0x00,0x00,
114: 0x04,0x40,0x00,0x00,0x00,0x04,0x40,0x00,0x00,0x00,0x04,0x40,
115: 0x00,0x00,0x00,0x04,0x40,0x00,0x00,0x00,0x04,0x40,0x00,0x00,
116: 0x00,0x02,0x80,0x00,0x00,0xc0,0x01,0x00,0x07,0x00,0x00,0x00,
117: 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
118: 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
119: 0x00,0x00,0x00,0x00};
120: icon_static( arr_icon, arrow_bits, 36, 32 );
121:
123: static unsigned char text_bits[] = {
124: 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
125: 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
126: 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x80,0xff,0xff,
127: 0x1f,0x00,0x80,0x83,0x1f,0x1c,0x00,0x80,0x01,0x0f,0x18,
128: 0x00,0x80,0x00,0x0f,0x10,0x00,0x00,0x00,0x0f,0x00,0x00,
129: 0x00,0x00,0x0f,0x00,0x00,0x00,0x00,0x0f,0x00,0x00,0x00,
130: 0x00,0x0f,0x00,0x00,0x00,0x00,0x0f,0x00,0x00,0x00,0x00,
131: 0x0f,0x00,0x00,0x00,0x00,0x0f,0x00,0x00,0x00,0x00,0x0f,
132: 0x00,0x00,0x00,0x00,0x0f,0x00,0x00,0x00,0x00,0x0f,0x00,
133: 0x00,0x00,0x00,0x0f,0x00,0x00,0x00,0x00,0x0f,0x00,0x00,
134; 0x00,0x00,0x0f,0x00,0x00,0x00,0x00,0x0f,0x00,0x00,0x00,
135: 0x80,0x1f,0x00,0x00,0x00,0xe0,0x7f,0x00,0x00,0x00,0x00,
136: 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
137: 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
138: 0x00,0x00,0x00,0x00,0x00,0x00};
139: icon_static( text_icon, text_bits, 36, 32 );
140:
141: /*
142: * control icons
143: */
144: static unsigned char copy_bits[] = {
145: 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
146: 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
147: 0x00,0x00,0x7c,0x00,0x00,0x00,0x00,0x82,0x00,0x00,0x00,0x00,
148: 0x01,0x01,0x00,0x00,0x80,0x00,0x00,0x00,0x00,0x80,0x80,0x0f,
149: 0x00,0x00,0x80,0x40,0x10,0x00,0x00,0x80,0x20,0x20,0x00,0x00,
150: 0x80,0x10,0x40,0x00,0x00,0x00,0x11,0x40,0x00,0x00,0x00,0x12,
151: 0x40,0x00,0x00,0x00,0x14,0x40,0x00,0x00,0x00,0x10,0x40,0x00,
152: 0x00,0x00,0x20,0x20,0x00,0x00,0x00,0x40,0x10,0x00,0x00,0x00,
153: 0x80,0x0f,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xc0,0x03,0x00,
continues
280 Part IV Laying Out the Parts
204: 0x00,0x00,0x0e,0xf1,0xc7,0x00,0x00,0x06,0x15,0xd4,0x00,0x00,
205: 0x00,0xd5,0xd5,0x00,0x00,0x00,0x11,0xc4,0x00,0x00,0x00,0xfe,
206: 0xff,0x00,0x00,0x00,0xfc,0x7f,0x00,0x00,0x00,0x00,0x00,0x00,
207:
208:
0x00,0x38,0x0c,0x07,0x00,0xe0,0x31,0x00,0x06,0x00,0x30,0x31,
0x8e,0xe7,0x00,0xf0,0x30,0xcc,0xb6,0x01,0xc0,0x31,0xcc,0xf6,
13
209: 0x01,0x90,0x31,0xcc,0x36,0x00,0xf0,0xfc,0xbf,0xef,0x01,0x00,
210: 0x00,0x00,0x00,0x00};
211: icon_static( save_icon, save_bits, 36, 32 );
212:
213: static unsigned char load_bits[] = {
214: 0x00,0x00,0x00,0x38,0x00,0xc0,0x03,0x00,0x30,0x00,0x80,0xc1,
215: 0x71,0x3c,0x00,0x80,0x61,0xdb,0x36,0x00,0x80,0x61,0xf3,0x36,
216: 0x00,0x80,0x6d,0xdb,0x36,0x00,0xc0,0xcf,0xf9,0x7d,0x00,0x00,
217: 0x00,0x00,0x00,0x00,0xc0,0xff,0x01,0x00,0x00,0x60,0x20,0x03,
218: 0x00,0x00,0x50,0x50,0x05,0x00,0x00,0x50,0x20,0x0d,0x00,0x00,
219: 0x90,0xff,0x0c,0x03,0x00,0x10,0x00,0x0c,0x07,0x00,0x10,0x00,
220: 0xcc,0x0f,0x00,0x10,0x00,0xcc,0x1f,0x00,0x10,0x00,0xcc,0x0f,
221: 0x00,0x10,0x7f,0x0c,0x07,0x00,0x50,0x41,0x0d,0x03,0x00,0x50,
222: 0x5d,0x0d,0x00,0x00,0x10,0x41,0x0c,0x00,0x00,0xe0,0xff,0x0f,
223: 0x00,0x00,0xc0,0xff,0x07,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
224: 0x00,0x38,0x0c,0x07,0x00,0xe0,0x31,0x00,0x06,0x00,0x30,0x31,
225: 0x8e,0xe7,0x00,0xf0,0x30,0xcc,0xb6,0x01,0xc0,0x31,0xcc,0xf6,
226: 0x01,0x90,0x31,0xcc,0x36,0x00,0xf0,0xfc,0xbf,0xef,0x01,0x00,
227: 0x00,0x00,0x00,0x00};
228: icon_static( load_icon, load_bits, 36, 32 );
229:
230: static unsigned char export_bits[] = {
231: 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x06,0x30,0x00,0x7c,0x00,
232: 0x00,0x30,0x00,0xd8,0x76,0xb7,0xf9,0x00,0xd8,0x5c,0x66,0x33,
233: 0x00,0x78,0x0c,0x66,0x33,0x00,0x18,0x0c,0x66,0xb3,0x01,0x3c,
234: 0x9e,0x6f,0xe3,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xe0,0x03,
235: 0x00,0x00,0x00,0x10,0x06,0x00,0x00,0x00,0x10,0x0a,0x00,0x00,
236: 0x00,0x10,0x1e,0x00,0x00,0x00,0x10,0x10,0x00,0x00,0x00,0x10,
237: 0x10,0x00,0x00,0xfc,0x17,0xd0,0xff,0x00,0x00,0x10,0x10,0x00,
238: 0x00,0x00,0x10,0x10,0x00,0x00,0x00,0x10,0x10,0x00,0x00,0x00,
239: 0x10,0x10,0x00,0x00,0x00,0x10,0x10,0x00,0x00,0x00,0xe0,0x0f,
240: 0x00,0x00,0x00,0x00,0x00,0x80,0x01,0x1f,0x00,0x00,0x80,0x01,
241: 0xd3,0xfd,0x38,0xfb,0x07,0x8f,0xb7,0x6d,0xae,0x01,0x03,0xb3,
242: 0x6d,0x86,0x01,0x9b,0xb7,0x6d,0x86,0x0d,0xdf,0xf6,0x38,0x0f,
243: 0x07,0x00,0x30,0x00,0x00,0x00,0x00,0x78,0x00,0x00,0x00,0x00,
244: 0x00,0x00,0x00,0x00};
245: icon_static(export_icon, export_bits, 36, 32 );
246:
247: static GxIconData gxDrawIcons[] = {
248: { &line_icon, gx_line, “Draw an elastic line...” },
249: { &pen_icon, gx_pencil, “Draw a freestyle line...” },
250: { &arc_icon, gx_arc, “Draw a circle...” },
251: { &box_icon, gx_box, “Draw a square or rectangle...” },
252: { &arr_icon, gx_arrow, “Draw an arrow...” },
253: { &text_icon, gx_text, “Draw dynamic text...” },
254: /*-----------------------------------*/
255: /* this list MUST be NULL terminated */
continues
282 Part IV Laying Out the Parts
The bitmap data is divided into two groups. Lines 32–139 define the object drawing
icons and lines 141–245 define the object management icons. The bitmap data used
Application Structure Chapter 13 283
to define the different icons, as mentioned, is created using the X Client bitmap (or
equivalent). After the desired icon is created using a utility capable of saving data in
XBM format, the contents of the saved file is inserted into the gxIcons.h header for
visibility in the project.
13
Chapter 6, in the section “Creating Buttons,” on page 159, discusses in greater detail the genera-
tion of bitmaps for use by an application.
After the many icons defined for use in the Graphics Editor user interface, lines
147–258 show the GxIconData array definition for gxDrawIcons. This array defines
the icons, callbacks, and context-sensitive help messages for the object-drawing
buttons. Lines 260–272 show a similar definition for the gxCntrlIcons used to define
the object management buttons.
These arrays are passed independently by create_buttons in Listing 13.4 to the
create_icons function for parsing and conversion into command widgets that will
act as holders for the different icons:
128: create_icons( butnPanel, gxDrawIcons, draw_manager );
and
146: create_icons( butnPanel, gxCntrlIcons, ctrl_manager );
The create_icons function defines a loop and is executed as long as there is a valid
element of the GxIconData pointer iconData.
Notice in Listing 13.5 that the GxIconData arrays are NULL terminated. The
create_icons function loops until it finds the end of the array as indicated by the
presence of the NULL:
80: while( iconData->info != NULL ) {
As long as there is a valid array element, a Pixmap is created from the XbmData field of
the GxIconData structure called info:
82: pix = create_pixmap( parent, iconData->info );
This Pixmap is then used as the background Pixmap of the command widget whose
parent is the button panel created by create_buttons and passed as the first para-
meter of the create_icons function.
Event handlers are added to this button to manage the context-sensitive help
string—displayed when the button gains focus (mouse cursor enters button’s win-
dow) and to clear the string when the focus is lost.
Added to the button, as well, is a callback function dictated by the type of button
that is being created. The buttons from the gxDrawIcons array are assigned the
draw_manager callback function and the gxCntrlIcons are given the cntrl_manager
function.
284 Part IV Laying Out the Parts
The func field of the GxIconData corresponds to the specific action that will be
invoked within the draw_manager or cntrl_manager functions.
Look again at the declaration of the gxDrawIcons and gxCntrlIcons arrays gxIcons.h
shown in Listing 13.4. The second field of the elements being initialized determines
the specific action assigned to the icon.
Functions such as gx_line, gx_pencil, gx_arc, and so forth are assigned to the
gxDrawIcons array elements. The gxCntrlIcons elements have functions such as
gx_copy, gx_delete, and gx_select assigned to them.
All the functions that are found in the GxIconData array elements declared in
gxIcons.h are prototyped in gxProtos.h. Their declarations will be introduced
shortly as will the draw_manager and cntrl_manager functions.
First, however, we end the analysis of Listing 13.4 by noting the creation of the exit
button on lines 148–156:
148: exitB = XtVaCreateManagedWidget( “ Exit “,
This is the first button that has been created to use a label type of string. All the pre-
vious command widgets had a background pixmap assigned, which excludes the use
of text in the button.
The label assigned to a command button is its instance name by default. Notice that
the instance name has spaces nested in it to extend the size of the button to approxi-
mately equal the menu button panes created in the create_buttons function.
Although valid syntax, a danger exists in using spaces in widget instance names: the
difficulty in including the value in an external resource file such as .Xdefaults.
Returning to Listing 13.1, a few final requirements must be met in the function main
defined in gxMain.c.
Specifically, the form and canvas widget created unmanaged are explicitly managed:
38: XtManageChild( canvas );
39: XtManageChild( form );
All X applications require the realization of the toplevel widget: recursively map-
ping the windows of all its descendents to the screen with a call to XtRealizeWidget:
Application Structure Chapter 13 285
The last step of the function main is to enter an infinite loop in which the application
will continually monitor its event queue: 13
43: XtAppMainLoop( appContext );
As the X Server sends events to the application, the Graphics Editor will remove
them from the queue and dispatch them to the widgets that have registered a request
to receive them.
The presence of the call to exit is only to satisfy the GNU C Compiler because
without it the compiler would issue the warning
Control reaches end of non-void function
Because the XtAppMainLoop function contains an infinite loop, this line of the func-
speak
continues
286 Part IV Laying Out the Parts
Most of the functions defined in the file gxGx.c shown in Listing 13.6 are only stubs
at this point, doing little more than printing out a statement that the function has
been reached.
The bodies of these functions will evolve over time; however, their presence even in
this simple form is absolutely necessary to satisfy the link phase when building this
phase of the project.
288 Part IV Laying Out the Parts
The functions used in the gxDrawIcon array elements defined gxIcons.h are not con-
tained in a single file. Instead, a file specific to the graphic object type created by the
icon is defined in Listings 13.7 through 13.9.
The file gxLine.c shown in Listing 13.7 controls the creation of all point-based
objects.
The gxArc.c file shown in Listing 13.8 contains only support for the arc graphic
object.
The gxText.c file shown in Listing 13.9 contains only source code for supporting
the vector text or Hershey font graphic object.
The functions shown to satisfy the references assigned to the drawing icons in the
gxDrawIcons array are, like most of the functions in gxGx.c shown in Listing 13.6,
only stubs at this point in the project. These files will grow significantly in com-
plexity during the next several chapters as the actual objects are defined, controlled,
saved, and restored.
The next section is a diversion from advancing the project in that there are currently
no command-line parameters supported by the application. However, I would be
negligent to not demonstrate ways to add flags and parameters to the Graphics
Editor program.
The getopt function returns the next option letter found in argv that matches a
letter in the optstring value.
The definition of optstring made by the programmer is done to specify the option
letters used by the application and therefore enable the getopt function to recognize
them.
Note The letters placed in the definition of the optstring value are synonymous with
what has been referred to as the flags passed to an application.
In forming the value for optstring, if a colon follows a letter, the option is expected
to have an associated argument.
The external variables used by getopt must be declared to the file actually employing
the getopt function. These variables include the following:
extern char *optarg;
extern int optind, opterr, optopt;
The getopt function places the index of the next argument to be processed in the
optind variable. The optind is external and initialized to 1 before the first call to
getopt.
When all options have been processed, meaning nothing remains on the command
line, getopt returns the constant EOF.
If flags (letters) are specified on the command line that are not contained in the
optstring value, getopt prints an error message to the standard error (stderr) and
Application Structure Chapter 13 291
returns a question mark. However, if the external variable opterr is set to 0 before
invoking the getopt function, error messages are disabled and getopt will not report
unidentified options found on the command line.
13
Listing 13.10 shows a code fragment for how the getopt function can be used to
process command-line arguments.
The flags identified by the sample code shown in Listing 13.10 are v, i, and o.
Because the options i and o are followed by a colon (:) in the optstring value, it is
292 Part IV Laying Out the Parts
expected that these flags will have an associated argument, which the getopt func-
tions assigns to optarg:
20: case ‘i’:
21: infile = optarg;
A caveat in the use of the getopt function is that getopt does not enforce the inclu-
speak
The first parameter specifies a widget that is used to identify the resource database to
search for the values of the resources specified. The widget that is specified is gen-
erally the application shell returned from the call to XtVaAppInitialize, as the class
name of the application is used to determine the resource database maintained for
this application.
The second parameter, base, is an address to the structure that will be used to store
the values obtained by the call to XtVaGetApplicationResources. The relationship to
the fields of the structure referenced by the second parameter and the resources will
be clear shortly.
The next parameter, XtResourceList resources, is an array of XtResource elements.
The XtResource structure is defined as follows in the Intrinsic.h header file:
Application Structure Chapter 13 293
The field resource_name is the name assigned by the programmer to the resource
being created for inclusion on the command line or in a resource file.
By convention, the first letter of a resource name is lowercase, and subsequent words
speak
A resource_type identifies the data type of the resource value. Table 13.3 shows
many of the common resource types understood by Intrinsics and the related C type.
XtRColor XColor
XtRCursor XCursor
XtRDimension Dimension
XtRFloat Float
XtRFont Font
XtRInt int
XtRShort short
XtRString String
The resource_size specifies the number of bytes that the field of the structure refer-
enced by base has reserved for this resource’s value. The sizeof operator is used to
determine this value.
The field resource_offset specifies the offset of the field in the structure referenced
by base for storing the value associated with this variable. The XtOffsetOf function
is used to determine this value.
294 Part IV Laying Out the Parts
The last two fields of the XtResource structure determine the default type and
default value for this resource if no assignment is made either on the command line
or in a resource file.
Listing 13.11 shows a code sample to properly define a resource array to be passed to
the XtVaGetApplicationResource function.
43:
44: XtVaGetApplicationResources( toplevel,
45: (XtPointer)&cmdArguments,
46;
47:
resources, XtNumber(resources),
NULL);
13
48:
49:
50: if( cmdArguments.inFile )
51: printf( “infile = %s\n”, cmdArguments.inFile );
52:
53: if( cmdArguments.outFile )
54: printf( “outfile = %s\n”, cmdArguments.outFile );
55: }
Following the definition of a structure to contain all the values supported on the
command line, the array resources is defined to inform Intrinsics of all the details
about the resources understood by the application.
The resource names, classes, sizes, placement in the _cmdArgs structure, and even
default values are provided in the XtResource array.
Next, a second array is created for inclusion in the call to XtVaAppInitialize in
order to inform of the command-line arguments:
26: static XrmOptionDescRec optionList[] = {
When the call to XtVaAppIniatalize is made, all the resources known by the applica-
tion are loaded into the X Resource Database. The second field to the optionList
informed the database manager of pertinent resource file entries that would satisfy
these resources and the first field instructed the valid command-line parameters.
Note Resource names do not have to be the same as the field names of the structure
which houses them. I’ve simply done this for clarity in determining the relation-
ships of the resources, structure fields, and command-line options.
Setting Up a Canvas
Focusing attention on the routine create_canvas responsible for creating the
drawing area GxDrawArea in Listing 13.4, we must be clear in the understanding
of the relationship to the event handlers assigned to the widget
34: XtAddEventHandler( GxDrawArea, PointerMotionMask, False,
35: (XtEventHandler)drawAreaEventProc, (XtPointer)NULL);
36: XtAddEventHandler( GxDrawArea, ButtonPressMask|ButtonReleaseMask, False,
37: (XtEventHandler)drawAreaEventProc, (XtPointer)NULL);
and the actual cursor management routines found in Listing 13.6 at lines 82–88
82: void draw_manager( Widget w, XtPointer cd, XtPointer cbs )
83: {
84: void (*draw_func)( XEvent * ) = (void (*)(XEvent *))cd;
85:
86: if( draw_func != NULL ) (*draw_func)( NULL );
87: draw_mgr_func = draw_func;
88: }
As you might recall, the draw_manager routine was assigned as the command widget
callback function for all the gxDrawIcons parsed and created by the create_icons
function.
Review the client data passed as the last parameter to the XtAddCallback function in
create_icons:
Earlier I pointed out that the iconData reference is an element of the gxDrawIcons
array. Looking again at the gxDrawIcons array definition, you see the object creation
routines are being passed to the draw_manager callback function as client data when
the user selects one of the object’s drawing icons.
When the draw_manager function is invoked because a user selects one of these icons,
it casts the client data to a function of type void, which expects an XEvent structure
pointer as its only parameter:
Application Structure Chapter 13 297
If the function pointer is not NULL, the function is invoked with a NULL XEvent
pointer reference to ensure that it has been reset from any previous creation: 13
86: if( draw_func != NULL ) (*draw_func)( NULL );
Finally, the draw_manager function assigns the function pointer value to a global vari-
able called draw_mgr_func:
87: draw_mgr_func = draw_func;
All this occurs as a result of a graphic object draw icon being selected and it finishes
before the user has even had a chance to move the cursor to the GxDrawArea canvas.
How does the event handler drawAreaEventProc assigned to the GxDrawArea relate to
the draw_manager function?
Every time a ButtonPress, ButtonRelease, or PointerMotion event occurs in the
window of the GxDrawArea widget, the drawAreaEventProc is invoked.
If the value of the global variable draw_mgr_func is not NULL, the drawAreaEventProc
continually invokes the routine passing to it the current event:
104: if( draw_mgr_func != NULL ) (*draw_mgr_func)( event );
The object-specific creation and management routines are introduced in Chapter 15,
“Common Object Definition.” These functions are responsible for performing the
correct action based on current event type being sent.
Note If the examples for building the project introduced in Chapter 1 are used as is,
the proposed project structure must be honored or the vpath values will not be
correct.
See Chapter 1, section “Makefile,” page 31, for a complete review of the make
utility and its configuration syntax.
298 Part IV Laying Out the Parts
Next Steps
After you have successfully compiled and linked the files introduced in this chapter
(and ensured that the elements are functional), you are ready to advance to the next
chapter.
Chapter 14, “Program Flow,” provides a brief discussion of the overall flow and pro-
gram execution to clarify how functions such as event handlers and callbacks are
invoked.
In this chapter
• Processing Events
• X Event Hooks
Program Flow
Chapter 13, “Application Structure,” focused on structuring the application and cre-
ating the graphical user interface. This chapter explains in more depth the nature of
event-driven programming and the interaction between the application and the X
Window System.
When the Graphics Editor is built and executed from the source code presented in
Chapter 13, control is quickly relinquished to the XtAppMainLoop function.
Beyond the creation of the graphical user interface and initialization of global vari-
ables for use during program execution, the program does not follow the conven-
tional program flow that you might have experienced in the past.
After the XtAppMainLoop is entered, execution lies somewhere in the Intrinsics
library well out of sight, only returning control to the Graphics Editor through one
of several entry points.
It is not possible to predict absolutely where in the code execution will be at any
given moment. Nor is it possible to predict the order in which functions will be
entered because program flow at this point depends on several factors, primary of
which are the habits of the user.
The user’s navigation of the application leads to the generation of events. These
events can be in the form of ButtonPress, ButtonRelease, or PointerMotion in the
drawing area window or callbacks invoked from the selection of a command widget.
Other events of interest to the Graphics Editor are the EnterNotify and
LeaveNotify events for updating the context-sensitive help message in the status
window.
300 Part IV Laying Out the Parts
speak All the events registered with the widgets that form the user interface account for the
multiple entry points into the editor application.
geek
The event-driven behavior of an X-based application directly affects the heuristic
method applied to the development of the application. Specifically, the programmer
must account for and be prepared for entry into the application from any one of the
many points registered with X for responding to user input.
Processing Events
The X events sent by the server to an application in response to user actions are, in
fact, data structures. Approximately 33 different event structures are defined by the X
Window System, and all of these events are relative to a window. Behaviors such as a
mouse pointer motion, entering and leaving windows, and even requests to change
the width or height of a window are communicated to the application by an appro-
priate event structure being filled and passed by the X Server to the client.
Of the 33 events understood by X, most are not communicated to the application
unless explicitly requested. As was demonstrated in Chapter 13, section “Setting up a
Canvas,” page 296, specifying the appropriate event mask for events of interest
requests of the X Server that these events are communicated to the application.
The most complex aspect of X Window System programming is the task of creating
a function to account for all possible events that can occur in the windows and subse-
quently invoking the functions registered by the user for the event. This task is com-
monly known as the event-loop.
Fortunately, the X Toolkit Intrinsics, through the library functions XtAppNextEvent
and XtDispatchEvent, simplifies the task. An understanding of how the presence of
events within a window of the application translates to a function being called by the
X Server is important to this discussion.
Several hooks are available for the X Window System to communicate events to an
application. These mechanisms included callbacks, event handlers, and translations.
EXCURSION
Application Program Hooks
In computer programming, a hook is a place (usually accessed through an interface) pro-
vided in packaged code that enables a programmer to attach customized functions. This
enables a programmer to insert additional capability into the package or library. In the
context of the X Window System, these hooks enable a programmer to specify re-entry
points to the application based on the occurrence of specific events.
Typically, hooks are provided for a stated purpose and are documented for the program-
mer. Some writers use hook to also refer to the function that is inserted.
Program Flow Chapter 14 301
• Requests in which the client asks the server to perform a task or provide infor-
mation.
• Replies from the server that can be immediate if the request by the client is a
roundtrip request such as XQueryGeometry.
• Events that the X Server can surprise the client with in response to user action.
• Errors reported by the server to reflect an unrecoverable condition.
Asynchronous implies that the X Server sends events whether or not the client is ready to
speak
receive them.
geek
Many Xlib functions (requests) made by the application cause the X Server to generate
events. Additionally, the user’s typing or moving the pointer will generate events. Both
requests and events are buffered and the X Server addresses them asynchronously to
maximize efficient use of the network.
When debugging an X Window application, it is often very convenient to request the
X Server behave synchronously so that errors are reported at the moment they occur.
The following function lets you disable or enable synchronous behavior:
XSynchronous( Display *, Boolean );
The second parameter is expected to be either True or False indicating whether the X
Server should start (True) or stop (False) its synchronous behavior.
On POSIX-conformant systems a global variable _Xdebug exists that, if set to a non-zero
value before starting the program, will force synchronous behavior.
Note that graphics can update significantly slower (30 times or more) when synchroniza-
tion is enabled, and it is therefore only used for debugging.
In normal asynchronous operations, X Window clients should be constantly prepared for
any event that could be sent to them. For instance, windows within an application can be
obscured by other applications and then exposed, requiring the applications to redraw.
302 Part IV Laying Out the Parts
X Event Hooks
The following sections address the mechanisms by which X invokes the functions
registered as the event hooks for an application.
Widget Callbacks
The X Server generates events as it monitors the display hardware. The manner
in which the user manipulates widgets by pushing a button, selecting a list item,
choosing a menu item, or even moving the mouse pointer results in the X Server
creating a corresponding event.
Graphical, event-driven programming is different from traditional programming in
speak
Event Handlers
When a widget is created, it will know how to respond to certain events, such as a
window manager’s request to change size, color, background, or border size. This
knowledge enables a command widget to appear highlighted when the mouse is
clicked within its window or a menu to cascade when selected by the user.
However, hooks do not exist for all events understood by a widget. The command
widget will not inherently enable a programmer to associate a new procedure for
Program Flow Chapter 14 303
BSelect Press corresponds to a mouse press and the translation table associates the
action Arm to this translation. The Arm action maps to a function internal to the wid-
get that causes, for instance, the command widget to highlight for appearing pressed.
If the mouse is clicked (pressed and released), Activate and Disarm functions are
called. The Activate function internal to the widget looks for functions registered
with the widget resource XtNcallback and invokes those it finds. The Disarm func-
tion returns the widget to normal coloring by turning off the widget highlighting.
Keyboard events can also be listed in the table to provide facilities such as hot keys,
function or numeric select keys, and help facilities.
Examples of keyboard translations include KActivate, which typically refers to the
Enter key, or KHelp for the HELP or F1 key. Pertinent actions are associated with
these translations.
An advanced feature of the X Window System is the capability to add actions to
existing translations or add new translations to a widget’s translation table.
304 Part IV Laying Out the Parts
This feature, though not exercised in the Graphics Editor application, is employed
with the functions XtParseTranslationTable and XtAugmentTranslations.
The function XtParseTranslationTable, as the name implies, parses a translation
table established by the application developer, the result of which can be installed
with the function XtAugmentTranslations.
The introduction of translation tables provides a complete coverage of the hooks
available for an application developer to expand and advance the X Window
environment.
Next Steps
The discussion of the application’s flow in this chapter should bridge the understand-
ing of the functions you author and their relationship to the processing of X
Window events.
With a better understanding of the program flow of the Graphics Editor and its rela-
tionship to the X libraries and Server, you are ready to structure the graphic objects
that are used to represent entities of the application.
The next chapter will advance the structure of the Graphics Editor project by build-
ing the common object component from which all objects in the editor will be based.
In this chapter
• Line Object Data Structure
• Text Object Data Structure
Note All the data structures introduced in this chapter are targeted for inclusion in the
gxGraphics.h header file.
Chapter 9, “Object Bounds Checking,” page 203, introduced the idea that the objects supported by
the Graphics Editor are divided into two families: point-array–based objects and arc objects.
PolyLine LatexLine
Figure 15.1
LatexLine, PolyLine,
Box, and Arrow objects
are all point-array–
based objects.
Arrow
Box
Note Notice in Figure 15.1 that the PolyLine object is selected as indicated by the
scale handles surrounding the object.
The section “Common Object Data Structure,” later in this chapter, explains fully
the necessity of selecting the objects created in the Graphics Editor application
and the data fields that manage an object’s activity.
Listing 15.1 provides a structure for representing all the objects shown in
Figure 15.1.
The GXLine structure is very simple, requiring only the array of points
2: XPoint *pts;
The XPoint array pts stores the vertices of the line object in the elements of the
array. Clearly, the number of vertices corresponds directly to the number of array
elements as reflected by num_pts.
This structure definition declares two new data types for use in the Graphics Editor
application:
4: } GXLine, *GXLinePtr;
The GxLine data type will refer to an actual occurrence of the structure and
GxLinePtr, as the name implies, will point to an occurrence of the structure.
15
Not nearly as simple is the structure required for representing a vector text object in
the Graphics Editor application.
Figure 15.2
The point-array–based
Text object.
The data structure used to represent the Graphics Editor Text is shown in
Listing 15.2.
The GXText data type introduced in Listing 15.2 contains elements for tracking the
position in the drawing area of the text string managed by the object
2: int x, y; /* upper-left */
are used to manage the scale feature of the text object and will be discussed in detail
in Chapter 24, “Vector Text Object.”
Common Object Definition Chapter 15 309
are required to manage specifics of the character definitions for the vector font set
that is employed for this graphic text object.
The font and fontp fields
7: GXFont font;
8: GXFontP fontp; 15
are effectively read-only and refer to the font assigned to this object.
A significant complication related to the vector text object involves the font or char-
acter definitions that govern the appearance of characters drawn by the text object.
The following section introduces vector font concepts and the complicated data
management employed to support them.
The character definition for the letter A shown in Listing 15.3 consists of three line
segments. Figure 15.3 illustrates the orientation of the vectors comprising each of
the segments.
Figure 15.3
0, –12
Representation of the line
segments defining the letter
A for a simple vector font. seg0
seg2
–5, 2 5, 2
–15 15
seg1
–8, 9 –8, 9
15
As shown in Figure 15.3, the origin of the graph defining a character in this vector
font set is located in the center of the cell. As this origin differs from the origin used
by the X graphic primitives, the definition of the text object data structure must
account for correctly placing the text by tracking the character string’s location.
A review of what you now know about vector fonts is important.
A single character consists of multiple line segments that are composed of multiple
points. In other words, an array of points forms a line segment and an array of line
segments (array of points) defines a character.
Putting this all together, we can say that an array of arrays of points forms a charac-
ter. Therefore, an entire font set (that is all character definitions) is an array of char-
acters, which is an array of arrays of points. An entire font set is represented as
XPoint **plain_simplex[] = {
char32, char33, char34, char35, char36, char37,
char38, char39, char40, char41, char42, char43,
char44, char45, char46, char47, char48, char49,
char50, char51, char52, char53, char54, char55,
char56, char57, char58, char59, char60, char61,
char62, char63, char64, char65, char66, char67,
char68, char69, char70, char71, char72, char73,
char74, char75, char76, char77, char78, char79,
char80, char81, char82, char83, char84, char85
char86, char87, char88, char89, char90, char91,
char92, char93, char94, char95, char96, char97,
Common Object Definition Chapter 15 311
The elements of the plain_simplex font set are character definitions. The arrays of
line segments defining each of the characters are presented in Chapter 24, “Vector
Text Object.” Important to our present discussion is the understanding that the font
set consists of an array in which each element is itself an array and each of these
arrays further contain arrays of points.
15
The GXFont data type has been defined in the Graphics Editor project to represent a
font set.
typedef XPoint ***GXFont;
The length of the arrays is important to every array nested in the GXFont data type
defining a vector font set. To track this important length information, a second array
is defined:
typedef int ** GXFontP;
The GXFontP array, however, contains elements of type int where each element cor-
responds to a length value for the corresponding element in the GXFont data type.
Use of these two arrays is closely linked and follows the convention reflected in the
following code snippet:
1: static void GXDrawText( GXTextPtr text, GC gc )
2: {
3: char *txt = text->text;
4: int c, chr, nsegs, num_pts;
5:
6: for( c = 0; c < text->len; c++, txt++ ) {
7: chr = *txt - ‘ ‘;
8: nsegs = 0;
9:
10: while( text->font[chr][nsegs] != NULL ) {
11: num_pts = text->fontp[chr][nsegs];
12:
13: if( num_pts > 0 ) {
14: XDrawLines( XtDisplay(GxDrawArea),
15: XtWindow(GxDrawArea), gc,
16: text->vpts[c][nsegs], num_pts,
17: CoordModeOrigin );
18: }
19: nsegs++;
20: }
21: }
22: }
312 Part IV Laying Out the Parts
This snippet is the actual code used to draw the text string contained in a GXText ref-
erence. It is officially introduced in Chapter 24, but is included here to show how a
vector font set is employed.
The routine begins by extracting the text string from the GXText structure referenced
by text:
3: char *txt = text->text;
an index into the font set is calculated by subtracting the decimal value of a space
(‘ ’) from the character to be drawn:
7: chr = *txt - ‘ ‘;
This works because the characters in the font set are ordered identically to the print-
able characters found in the ASCII table as shown in Figure 15.4.
Notice in Figure 15.4 that the decimal value of a space is 32. As the first character
defined in the vector font set is the space (char32), if a space is to be rendered by the
GXDrawText function, it subtracts a space (decimal 32) from a space resulting in the
value of chr being 0. An index of zero corresponds to the first element of the array,
which is in fact the definition of the space character.
Note Although a space can’t be drawn, its definition in the vector font set is important
for separating words an appropriate distance when it is used in a text string man-
aged by the graphic text object. 15
Applying this to the letter A (char65) you see that ‘A’ – ‘ ‘ or 65 – 32 is 33, which
corresponds to the 34th element of the array, which is the character definition for
char65.
After calculating a value for chr (index of the character definition in the font set), the
GXDrawText function can use chr to index into the array defining the number of
points for the segments of which this character is composed:
11: num_pts = text->fontp[chr][nsegs];
With this data, the routine has all the information it needs to draw a single segment
of the character.
As we saw with the definition of a single character
static XPoint *char65[] = {
seg0_A, seg1_A, seg2_A,
NULL,
};
the character definitions are NULL terminated. This enables us to loop until all seg-
ments of the character have been drawn:
10: while( text->font[chr][nsegs] != NULL ) {
are the actual points used to draw the object to the canvas. The value of vpts is
derived by transposing the character definitions in the font set for each character of
the text string to the location referenced by the x and y fields of the GXText structure.
Understanding the GXText data structure and the complex GXFont data type will take
some time. These definitions will be reviewed and explained in greater detail in
Chapter 24.
The complexity of the GXText data structure is inversely proportional to the GXArc
data structure introduced in the next section.
Figure 15.5
Examples of the graphic
Arc object.
The data types for the structure used to manage the graphic arc objects shown in
Figure 15.5 are found in Listing 15.4
Common Object Definition Chapter 15 315
Fortunately, the X Window System provides a structure for representing the Arc
objects, and this structure is sufficient for use in the Graphics Editor.
With the specific data requirements for the various graphic objects defined and
explained in the preceding sections, we are ready to introduce the common object
data structure and demonstrate its relationship to the specific graphic objects.
15
Common Object Data Structure
All the graphic objects supported by the Graphics Editor application require many
pieces of information.
These data fields are not repeated for every object type but are contained in a com-
mon object data structure from which individual objects are derived.
Listing 15.5 introduces the common object definition used by all graphic objects in
the Graphics Editor.
continues
316 Part IV Laying Out the Parts
The first several fields maintain the values of the attributes assigned to an individual
object:
5: Pixel fg; /* foreground */
6: Pixel bg; /* background */
7:
8: Pixmap fs; /* fill style */
9: int ls; /* line style */
10: int lw; /* line width */
These fields will be used to create the Graphic Context (GC) used to draw the object.
Following the attributes are fields that manage whether the object is selected
12: Boolean selected;
The selected field is set to True when the object is selected and False otherwise.
When an object is selected (or active), eight handles are drawn to reflect this status
and indicate the scale directions available for changing the size of the object. Only
when an object is active can it be scaled, moved, copied, or cut from the screen.
The next field in the common object data structure is used to store the object-
specific data structures for the various graphic object types understood by the editor:
17: void *data;
The data field can point to any one of the structures introduced early for repre-
senting the specific data requirements of the editor objects.
Common Object Definition Chapter 15 317
These methods are explained by their names. The draw and erase methods require a
single parameter referencing the object to be drawn or erased.
The bounds method also requires a reference to the object and returns a reference to
an XRectangle reflecting the bounds of the object specified.
The find method requires, in addition to an object reference, a reference to an
XEvent structure defining the x and y location of the mouse cursor when the object
selection was attempted.
The select and deselect methods toggle the selected field to reflect the current
status and create or destroy the handles reference as required by the change in
status.
The copy action is accessed by the icon on the button panel to the right of the
graphical user interface. When an object is active (selected) and the copy button is
clicked, the active object is duplicated, with the copy placed next to the original.
Finally, the move and scale methods are implied by the user’s navigation of the
mouse cursor. Because these actions are modal, meaning they are dependent on the
user’s wishes, the action field
32: void (*action) ( struct _gx_obj *, XEvent * );
EXCURSION
What Is a Linked List?
A linked list is a programming construct that enables nodes (data structures) to be linked
together by a reference to the next node:
node (head) -> node -> node -> node -> NULL
The usefulness of a linked list lies in the fact that it can contain an unknown number of
nodes. Contrast this to an array that has a finite number of elements determined when the
array is either defined or allocated.
It is important when using the linked list construct that the head, or beginning of the list,
be meticulously maintained. If for any reason a reference to the beginning of the list is lost,
the entire list is also lost. As demonstrated with this previous linked list, a NULL pointer
marks the last node in the list.
Chapter 16, “Object Manipulation,” section “Deleting an Object,” page 321, and Chapter
17, “Utilities and Tools,” section “Linked List Management,” page 346, discuss functions
used by the Graphics Editor for adding and removing nodes to the linked list supported by
the common object data structure.
The linked list provided for in the common object data structure is used to maintain
the objects created in the editor.
All objects created by the Graphics Editor will be appended to the next field of the
previously created object with the first object defining the head of the list.
This completes the definition of the vital data structures required by the Graphics
Editor application.
Next Steps
An understanding of the data fields and methods defined in the common object data
structure will allow us to introduce functions that employ them.
Chapter 16 will demonstrate how the graphic objects created by the editor are
treated identically despite the specific type represented by the object. This generic
treatment of the graphic objects is accomplished by using the methods and data ele-
ments of the common object data structure.
In this chapter (M04)
• Copying
This is styled
an Object
M05
• You
Deleting
will learn
an Object
amazing things and be
Chapter 16 •
•
•
wowed by
Parsing
You
the knowledge
• Refreshing Objects
• You will for
learn
will learn
Managing
of Que
an amazing
Object things
amazing
Object things and be
Handles
wowed by the knowledge of Que
• Mangaging the Status of an Object
• If this is longer please ask editorial to edit
• Processing User Navigation of Objects
to fit
• Next Steps
• Box size does not get adjusted
Object Manipulation
Chapter 15, “Common Object Definition,” led you through the definition of the
data structures used to create the common graphic object as well as the unique
structures for representing the supported graphic object types in the Graphics
Editor.
Using the methods and data fields provided by the common object data structure,
the Graphics Editor can build functions capable of managing objects without regard
to their type.
This chapter introduces the functions that manipulate objects generically.
Note Many of the functions presented in this chapter complete the stub (empty) func-
tions used to satisfy necessary function resolutions in Chapter 13, “Application
Structure.”
All the functions introduced in this chapter are intended for inclusion in the
gxGx.c source file of the Graphics Editor project.
Copying an Object
The source code in Listing 16.1 satisfies the function invoked when the Copy icon
from the button panel of the Graphics Editor interface is pressed.
continues
320 Part IV Laying Out the Parts
The gx_copy function defined in Listing 16.1 checks for the presence of the global
variable gxObjCurrent
3: if( gxObjCurrent ) {
which refers to the currently active object in the Graphics Editor application.
If there is a currently active object, the gx_copy function invokes the copy method
contained in the common object structure definition, passing the current object to
satisfy the function’s parameter requirements:
4: (*gxObjCurrent->copy)( gxObjCurrent );
This method, as explained in Chapter 15, “Common Object Definition,” in the sec-
tion “Common Object Data Structure,” page 315, is assigned based on the specific
graphic object type contained in the data field of the common object data structure.
EXCURSION
C Is Not C++
If you are familiar with the C++ programming language, you know that a feature of the lan-
guage is the capability of all objects to make an implicit self-reference with the keyword
this.
Unfortunately, this mechanism is not available in the C programming language. For this
reason, a consistent parameter requirement for the methods contained in the common
object data structure is a reference to the graphic object whose method is being invoked.
Following the invocation of the copy method, all the objects are redrawn with a call
to the gx_refresh function.
If there is not an object currently selected, the else clause of the initial test is
entered
6: } else
7: setStatus( “Select an object to copy!” );
and an information message is placed in the status label, indicating the intended
order of actions for copying an object.
Located near the Copy icon in the Graphics Editor button panel is the Cut icon.
The function to satisfy this icon is discussed in the following section.
Object Manipulation Chapter 16 321
Deleting an Object
Previously introduced as a stub function in Chapter 13, the code in Listing 16.2 pro-
vides the functionality to remove the currently selected object from the canvas and
the linked list of created objects.
continues
322 Part IV Laying Out the Parts
As with the gx_copy function, the gx_delete routine shown in Listing 16.2 begins by
testing for the presence of a currently selected object. In its absence, an information
message is placed in the status label, providing a hint to the user of the intended
sequence of events for deleting an object.
If, however, an object is currently selected, the body of the if statement is entered
where the object’s deselect method is invoked to remove the object’s handles
8: (*gxObjCurrent->deselect)( gxObjCurrent );
and then the object is erased from the screen by a call to its erase method:
9: (*gxObjCurrent->erase)( gxObjCurrent );
It is then necessary to parse the entire linked list of objects in search of the one being
deleted so that it can be removed from the list.
This process is started by assigning a variable to point to the beginning of the list:
17: gx_objs = gxObjHeader;
It is important that the variable gxObjHeader is not used directly because the refer-
ence will be advanced through the list and it is crucial that the beginning of the list is
maintained; otherwise the entire list will be lost.
The construct of the loop
22: while( gx_objs->next && (!found)) {
Object Manipulation Chapter 16 323
reads, “So long as there is another object in the list (gx_objs->next) and the one
being deleted has not been found (!found), continue looping.”
The variable found is assigned directly the Boolean result of the comparison of the
next object in the list and the object being deleted:
25: found = (gx_objs->next == gxObjCurrent );
As mentioned previously, when the objects match, found will interrupt the loop and
the next field of the current object will point to the one being deleted.
When a match is found, it is removed from the list by reassigning what the current
node’s next field points to:
30: if( found ) {
31: gx_objs->next = gxObjCurrent->next; 16
32: gxObjCurrent->next = NULL;
As long as the object has not been found, progress continues through the list by
moving to the next node:
33: } else {
34: gx_objs = gx_objs->next;
35: }
EXCURSION
Managing Pointers in a Linked List
It is important that you understand how the fragment
31: gx_objs->next = gxObjCurrent->next;
removes gxObjCurrent from the list of graphic objects known to the Graphics Editor appli-
cation.
A linked list, introduced in Chapter 16, “Object Manipulation,” in the Excursion “An
Introduction to Linked Lists,” page xx, is a construct that allows a dynamic number of
items to be managed by an application.
The next field of the common object data structure enables us to link the graphic objects
created in the Graphics Editor together.
Conceptually, we depict this list as
gxObjHeader->next->next->next->next->(NULL)
meaning the gxObjHeader, whose value is a reference to the first object created by the
application, has as the value of its next field a reference to the second object created,
which has as the value of its next field a reference to the third and so forth. The most
recently created object will not have a valid reference assigned to its next field, thus
marking the end of the list.
When the list is traversed in search of the object being deleted, the value of gxObjCurrent
is somewhere in the list.
324 Part IV Laying Out the Parts
gxObjCurrent
On the first iteration of the traversal, the variable gxObjs refers to the beginning of the list.
gxObjs
With subsequent iterations, the gxObjs value is advanced to equal the next node.
gxObjs
This continues until the current object is found, at which point the next field referring to the
current object is assigned the reference pointed to by the current object.
gxObjs
Linked lists are covered again in Chapter 17, “Utilities and Tools,” in the section “Linked
List Management,” page 346.
Notice again the test for the object being deleted being found:
25: found = (gx_objs->next == gxObjCurrent );
The test is against the next field of the gx_objs variable. This means that the
gxObjHeader value (first object created) is not considered in the body of the loop.
This is important because the head of a linked list is always a special case.
If the loop ends without the object being found
41: if( found == False ) {
If the object being sought is indeed the beginning of the list, its removal is accom-
plished with the line
43: gxObjHeader = gxObjCurrent->next;
and the objects still maintained in the editor are refreshed to redraw any portion of
them that might have been obscured by the object that was deleted.
53: if( gxObjHeader ) gx_refresh();
The following section introduces the gx_refresh function used to redraw the entire
list of objects controlled by the Graphics Editor application.
Refreshing Objects
To account for the immediate graphic nature of the X Window System as introduced 16
in Chapter 4, “Windowing Concepts,” in the section “Expose,” page 122, it is often
necessary to refresh the graphic objects displayed on the drawing area canvas. The
gx_refresh function introduced in Listing 16.3 accomplishes this task.
and loops until it finds the end of the list marked by NULL:
5: while( obj ) {
For every object in the list, the object’s draw method is invoked
6: (*obj->draw)( obj );
Perhaps one of the simplest functions acting against the common object definition,
gx_refresh, is one of the most vital. Without it the objects would never get redrawn
to the drawing area window.
Another critical function is the one introduced in the next section that enables us to
find an object selected by a ButtonPress event.
This enables the return of the object found by the parse function to the calling
function.
Object Manipulation Chapter 16 327
This function is recursive, meaning that it calls itself. Therefore, the initial test of
parse_all_objects is to ensure that neither the object selected by the event nor the
end of the list has been found:
5: if( obj && (*gx_obj == NULL) ) {
If there is an object to test and no object has been found, the return value of the
object’s find method is used to determine whether the event successfully selected the
current object:
6: if( (*obj->find)( obj, event ) ) {
A True returned from the find method indicates that the object should be saved in
the return field of the parameter list:
7: *gx_obj = obj; 16
A status is reported and the recursion unfolds, returning the selected object.
The find method returning False leads to another level of recursion being invoked
12: parse_all_objects( obj->next, event, gx_obj );
with the next element of the list passed for testing against the event reference.
At some point, either an object will be deemed selected or the obj->next reference
passed to the subsequent level of recursion will be NULL and the test
5: if( obj && (*gx_obj == NULL) ) {
continues
328 Part IV Laying Out the Parts
The gx_draw_handles function found in Listing 16.5 begins with the definition of an
array of bitmapped character data:
3: static unsigned char mask_bits[8][HNDL_SIZE] = {
4: {0x7f, 0x3f, 0x1f, 0x1f, 0x3f, 0x73, 0xe1, 0xc0},
5: {0x18, 0x3c, 0x7e, 0xff, 0x18, 0x18, 0x18, 0x00},
6: {0xfe, 0xfc, 0xf8, 0xf8, 0xfc, 0xce, 0x87, 0x03},
7: {0x10, 0x30, 0x70, 0xff, 0xff, 0x70, 0x30, 0x10},
8: {0x03, 0x87, 0xce, 0xfc, 0xf8, 0xf8, 0xfc, 0xfe},
9: {0x00, 0x18, 0x18, 0x18, 0xff, 0x7e, 0x3c, 0x18},
10: {0xc0, 0xe1, 0x73, 0x3f, 0x1f, 0x1f, 0x3f, 0x7f},
11: {0x08, 0x0c, 0x0e, 0xff, 0xff, 0x0e, 0x0c, 0x08} };
Each element corresponds to an arrow indicating the direction that the handle will
scale the object if this handle is selected. Because all the handles are based on a
square, the HNDL_SIZE directive indicates the length of the handle bit data array. 16
It is defined in the gxGraphics.h header file as
#define HNDL_SIZE 8
A Pixmap created from each of the elements of bit data defined by the array
mask_bits need only be created once. To manage this a static flag
is used to ensure that only with the first call to this function the Pixmaps are created:
21: masks[i] =
22: XCreatePixmapFromBitmapData(XtDisplay(GxDrawArea),
23: XtWindow(GxDrawArea),
24: (char *)mask_bits[i],
25: HNDL_SIZE, HNDL_SIZE,
26: 1, 0, 1 );
Because the creation of the Graphics Context is based on the attributes of the object
specified as the first parameter of the creation utility, the FillStyle must explicitly
be set to FillSolid for these handles to appear correctly:
33: XSetFillStyle( XtDisplay(GxDrawArea), gc, FillSolid );
Next, a test is performed to ensure the correct number of handles has been specified
for the object:
35: if( obj->num_handles != 8 ) {
The test of the num_handles is necessary to prevent a potentially fatal error occurring
when less than eight handles are created for the object and the subsequent for loop
expects to index eight elements of the handles array:
40: for( i = 0; i < 8; i++ ) {
One further change to the Graphics Context employed for drawing the handles and
the routine is complete. Specifically, the assignment of a ClipMask corresponding to
the shape of the arrow Pixmap created from the bitmap data defined by the
mask_bits array must be assigned to the gc. Always when using a ClipMask a
ClipOrigin must be specified to align the mask with the item being drawn:
The origin specified for the arrow Pixmap used as the ClipMask is the same as the
location for the position of the handle being drawn.
Finally, the individual handle is drawn using the XFillRectangle graphic primitive:
46: XFillRectangle( XtDisplay(GxDrawArea),
47: XtWindow(GxDrawArea), gc,
48: obj->handles[i].x,
49: obj->handles[i].y,
50: obj->handles[i].width,
51: obj->handles[i].height );
As a result of to the assignment of the ClipMask to the Graphics Context, the rectan-
gle is drawn only where the bits of the arrow Pixmap are set to 1. In other words, the
item being drawn is clipped (not drawn) where there is not a bit set in the ClipMask.
Finally, a call to return the Graphics Context to the pool cached by the X Toolkit is
called to complete the function:
54: XtReleaseGC(GxDrawArea, gc);
To erase the handles associated with an object, the gx_erase_handles function shown
in Listing 16.6 is used.
Object Manipulation Chapter 16 331
The second parameter for this function indicates whether the gc should be created
with a background tile assigned. A False as passed from the gx_draw_handles func-
tion is used to draw objects to the canvas and a True is used to erase them.
This function is presented in Chapter 17, “Utilities and Tools,” in the section “Graphics Context
Tiling,” page 348, where the use of a background tile for erasing objects is discussed in detail.
Having looked at the first level of management required for objects activated by the
user, namely managing the handles associated with the object, the discussion contin-
ues in the next section to managing the object’s status.
The source code that is found in Listing 16.7 for the find_graphic function begins
the process of applying an event to the objects managed by the editor.
The function ensures that the event under consideration is a ButtonPress event:
5: if( event->type == ButtonPress ) {
The source code for the activate_obj function is found in Listing 16.9.
Otherwise, if the event did not successfully return from any known object’s find
method, all objects are deactivated:
14: deactivate_objs();s
Listing 16.8 shows the source code for the deactivate_objs function.
Object Manipulation Chapter 16 333
The deactivate_objs function in Listing 16.8 is absolute. For any object currently
active in the application, the deselect method is invoked. When the
deactivate_objs function completes, there will be no active objects in the Graphics 16
Editor application and the value of gxObjCurrent will be NULL.
The activate_objs function begins by considering that another object can be cur-
rently active:
3: if( gxObjCurrent ) deactivate_objs();
If this is the case, the deactivate_objs function is invoked to toggle the object’s
status to inactive.
After ensuring that a valid object reference was passed to the activate_objs function
6: if( obj ) {
the gxObjCurrent value is set to equal the newly found object. The object’s select
method is invoked to create the object’s handles and set its status flag selected to
True.
The user’s selection of a graphic object from the Graphics Editor’s canvas begins
with the user’s navigation of the interface. The following section demonstrates how
the user’s navigation actions are processed by the Graphics Editor application.
334 Part IV Laying Out the Parts
The function is quite simple in that it only has to determine which branch of the
application the event is targeted for.
If one of the drawing functions gx_line, gx_pencil, gx_box, gx_arrow, gx_arc, or
gx_text is currently active, its value will be assigned to the function pointer
draw_mgr_func as described in Chapter 13, in the section “Laying Out the User
Interface,” page 265.
An active drawing function gets preferential treatment
4: if( draw_mgr_func != NULL ) {
and the event received by the drawAreaEventProc is sent to this active function:
5: (*draw_mgr_func)( event );
7: } else {
8: /* update the object */
9: update_obj( gxObjCurrent, xe );
10: }
11: }
The process_event function has two responsibilities. First, in the absence of a cur-
rently selected object, is to direct the event to the find_graphic function introduced
in Listing 16.6:
5: find_graphic( xe );
However, if an object is already selected, the event is targeted for updating the active
object by a call to update_obj:
9: update_obj( gxObjCurrent, xe ); 16
The update_obj function must screen based on event type to determine the nature
of object update required. Listing 16.12 shows the source code for the update_obj
function.
The update_obj function only has to switch on the event type to determine which
update function to call. Listing 16.13 shows the source code for the first case
buttonpress_update.
continues
336 Part IV Laying Out the Parts
If a current action is assigned, the body of the if statement is skipped and immedi-
ately the action is invoked for the current event:
31: if( obj->action ) {
32: (*obj->deselect)( obj );
33: (*obj->action)( obj, event );
34: }
Object Manipulation Chapter 16 337
is to ensure that the handles are not displayed while the object is either scaled or
moved.
If an object’s action field does not have a current function assigned, the
buttonpress_update function determines what the value should be.
are updated with the x, y coordinates of the current event. This will be important
during the scale action covered in Chapter 20 in the section “Scaling a Line Object,” 16
page 398.
To determine the proper value of the action field for the current object, the
buttonpress_update determines whether one of the object’s handles was successfully
selected by the event being processed:
12: find_handle( obj, event, &found, &indx );
The source code for the find_handle routine is provided in Listing 16.14.
The find_handle function returns two values in the fields passed as the second and
third parameters: a status flag indicating whether a handle was found and, optionally
if found reflects that a handle was found, the index of the handle.
The successful selection of the object’s handle indicates that the user intended to
scale the object. In this case, the object’s scale method is used as the value for the
action field:
Two chores are addressed in preparation for the scale action. The first changes the
cursor to reflect the new state of the application:
17: set_cursor( SCALE_MODE );
This function is provided in Chapter 17, “Utilities and Tools,” page 343.
The second chore required in preparation for the scale action is to set the value of
indx (reflecting the specific handle selected by the user) to the global variable
GxActiveHandle.
This information is necessary to ensure that the object is scaled in a direction appro-
priate to the direction of the arrow of the selected object’s handle.
338 Part IV Laying Out the Parts
and the buttonpress_update function parses all known objects to see whether the
event landed on an object:
21: parse_all_objects( obj, event, &e_obj );
An event directed to the update branch of the Graphics Editor application that is
located directly on the graphic object implies that the user intended for the object to
be moved.
If the parse_all_objects function as shown in Listing 16.4 determines that an
object is selected by the specified event, the object is returned in the last parameter
field of the function call.
If the event selected the same object as the currently active object
22: if( e_obj == obj ) {
the user’s intent to move the object is confirmed and the action field is updated
accordingly:
23: obj->action = obj->move;
The utility to update the applications cursor to reflect the new state of the applica-
tion is called:
24: set_cursor( MOVE_MODE );
If the object selected by the event does not match the active object, the
buttonpress_update function deactivates the current object and ends the update
process:
25: } else {
26: deactivate_objs();
Another update function used by the update_obj routine manages the ButtonRelease
event type. The source code for the buttonrelease_update function is found in
Listing 16.14.
7: if( obj->action ) {
8: (*obj->action)( obj, event );
9: (*obj->action)( obj, NULL );
10:
11: obj->action = NULL;
12: (*obj->select)( obj );
13: gx_refresh();
14: }
15:
16: set_cursor( NORMAL_MODE );
17: }
By design, the event type is intended to end the iterative actions that affect an object.
Therefore, after the ButtonRelease event is passed to the current action function,
the function is invoked a second time with a NULL event to allow the object to per-
form any necessary cleanup
9: (*obj->action)( obj, NULL );
Finally, the object’s select method is invoked to redisplay the handles and the screen
is refreshed:
12: (*obj->select)( obj );
13: gx_refresh();
The find_handle function starts by ensuring that handles for this object exist and
that a handle has yet to be found:
8: if( gx_obj && gx_obj->handles && !(*found)) {
Then for each handle known to the object, a series of greater-than/less-than tests are
performed to see whether the coordinates of the XEvent lie within bounds of the
object’s handle.
Object Manipulation Chapter 16 341
If the event’s x component is greater than the starting point of the handle
10: if( ( xe->xbutton.x > gx_obj->handles[i].x ) &&
and the event’s y component is greater than the start and less than the end of the
handle
12: ( xe->xbutton.y > gx_obj->handles[i].y ) &&
13: ( xe->xbutton.y < gx_obj->handles[i].y + HNDL_SIZE)) {
the handle is considered selected. The found flag is updated to inform the calling
function that a handle was found and the index of the satisfying handle is stored in
the indx field: 16
14: *indx = i;
15: *found = True;
If none of the object’s handles pass the test, the found flag is returned to the calling
function as False, indicating no handle was found and the value of the indx field is
immaterial.
This completes the discussion of routines in the Graphics Editor that manage objects
based on common object data structure without regard to the specifics of the object’s
type.
Next Steps
This chapter covered in detail the management routines that act on objects generi-
cally, that is, despite their actual type.
In Chapter 17, more utilities and tools are introduced which address areas of the
Graphics Editor application necessary for object creation, management, and house-
keeping.
In this chapter
• Common Object Creation
• Creating a Graphics Context
Listing 17.1 Source Code for the gx_create_obj Function for Inclusion in gxGx.c
1: GXObjPtr gx_create_obj( void )
2: {
3: GXObjPtr gx_obj = XtNew( GXObj );
4:
5: gx_obj->fs = None;
6: gx_obj->ls = LineSolid;
7: gx_obj->lw = 1;
8:
9: gx_obj->bg = WhitePixelOfScreen(XtScreen(GxDrawArea));
10: gx_obj->fg = BlackPixelOfScreen(XtScreen(GxDrawArea));
continues
344 Part IV Laying Out the Parts
The gx_create_obj routine begins by allocating the memory required for the new
object:
3: GXObjPtr gx_obj = XtNew( GXObj );
With memory for the object created, the default attributes are assigned:
5: gx_obj->fs = None;
6: gx_obj->ls = LineSolid;
7: gx_obj->lw = 1;
8:
9: gx_obj->bg = WhitePixelOfScreen(XtScreen(GxDrawArea));
10: gx_obj->fg = BlackPixelOfScreen(XtScreen(GxDrawArea));
The next part of the gx_create_obj function initializes fields of the common object
data structure to a safe state:
12: gx_obj->handles = NULL;
13: gx_obj->num_handles = 0;
14:
15: gx_obj->data = NULL;
Then a temporary function is assigned to the method fields of the object to prevent
an erroneous invocation of an invalid function:
Utilities and Tools Chapter 17 345
Listing 17.1a Source Code for the null_func Routine for Inclusion in gxGx.c
1a: static void null_func( void )
2a: {
3a: printf( “Warning: null function called!\n” );
4a: }
Because the object is newly created, there is not a current action to assign to it.
Therefore, set the action field to reflect this:
17
26: gx_obj->action = NULL;
It is necessary to initialize the next field of the object structure that links to other
objects. This is critical to the proper management of the linked list in which this
object will be placed:
28: gx_obj->next = NULL;
The global variable draw_mgr_func is reset to NULL to ensure that subsequent events
generated in the drawing area canvas are sent to the object that is created and set as
active:
33: draw_mgr_func = NULL;
Though the creation routines for the object-specific data structures are not intro-
duced until Chapter 20, “Latex Line Object,” you should understand (extrapolate)
that the graphic object’s specific creation routines employ the gx_create_obj routine.
Further, upon receiving the object created by the gx_create_obj function, the
object-specific data is assigned to the data field of the object and the appropriate
object’s methods are updated to properly manage that data field.
For the editor to retain and continually manage the object, it must be added to the
linked list of objects under the editor’s control. The following section describes the
linked list management function that adds nodes to the editor’s linked list.
346 Part IV Laying Out the Parts
Designating a head node as the first element in the list and continually assigning
consecutive node references to the next field of the last node in the list accomplishes
this.
The gx_add_obj routine is responsible for finding the end of the list (last node in the
list) and assigning the reference to the node being added to the next element of that
object structure.
Listing 17.2 shows the source code for gx_add_obj.
Listing 17.2 Source Code for the gx_add_obj Function for Inclusion in gxGx.c
1: void gx_add_obj( GXObjPtr obj )
2: {
3: GXObjPtr gx_obj;
4:
5: if( gxObjHeader == NULL ) {
6: gxObjHeader = obj;
7: } else {
8: gx_obj = gxObjHeader;
9:
10: /* find the end of the object list */
11: while( gx_obj->next != NULL ) {
12: gx_obj = gx_obj->next;
13: }
14:
15: /*
16: * add the new object to the end of our list
17: */
18: gx_obj->next = obj;
19: }
20: }
If the gxObjHeader variable is NULL, this is the first object created by the Graphics
Editor, and it therefore assumes the responsibility of being the head of the list:
6: gxObjHeader = obj;
If a valid head already exists, however, the list must be traversed in search of its end.
Utilities and Tools Chapter 17 347
To do this, a variable is assigned the value of the list head and a while loop is entered
until the next element’s value is NULL, as NULL marks the end of a linked list:
8: gx_obj = gxObjHeader;
9:
10: /* find the end of the object list */
11: while( gx_obj->next != NULL ) {
As long as there is a valid element assigned to the next field of the current node,
traversal continues
12: gx_obj = gx_obj->next;
When the end of the linked list is found, the element being added assumes a position
as the new end of the list:
18: gx_obj->next = obj;
The capability to create and retain graphic objects is made more meaningful when
the Graphics Editor can draw those objects to the canvas. 17
In the next section I introduce a utility function used by the editor to create a
Graphics Context based on the current attribute settings of an object.
Listing 17.3 Source Code for the gx_allocate_gc Function for Inclusion in gxGx.c
1: GC gx_allocate_gc( GXObjPtr obj, Boolean tile )
2: {
3: GC gc;
4:
5: XGCValues values;
6: XtGCMask mask = 0L;
7:
8: values.foreground = obj->fg;
9: mask |= GCForeground;
10:
11: values.background = obj->bg;
12: mask |= GCBackground;
13:
14: values.line_width = obj->lw;
15: mask |= GCLineWidth;
continues
348 Part IV Laying Out the Parts
5: DefaultRootWindow(XtDisplay(parent)),
6: grid_bits, grid_width, grid_height,
7: BlackPixelOfScreen(XtScreen(parent)),
8: WhitePixelOfScreen(XtScreen(parent)),
9: DefaultDepthOfScreen(XtScreen(parent)));
This code fragment creates a Pixmap using the character bitmap data grid_bits
shown in Listing 17.5.
To be syntactically correct and prevent compiler errors associated with the changes
shown in Listing 17.4, you must create the grid.xbm file from Listing 17.5 and place
this file in the src/include directory. 17
Further, an include directive must be added to the beginning of gxGraphics.c for
the grid.xbm file as follows:
#include “gxGraphics.h”
#include “grid.xbm”
Finally, the following line must be added to the GLOBAL section of the gxGraphic.h
header file:
GLOBAL Pixmap GxDrawAreaBG;
The next section introduces how to alter the appearance of the applications cursor to
reflect the status or state of the application.
Listing 17.6 Source Code for the set_cursor Function to Be Added to gxGraphics.c
1: void set_cursor( CursorMode mode )
2: {
3: Cursor new_cursor;
4:
5: switch( mode ) {
6: case LINE_MODE:
7: new_cursor = gxCrosshair;
8: break;
9:
10: case PENCIL_MODE:
11: new_cursor = gxPencil;
12: break;
13:
14: case EDIT_MODE:
15: new_cursor = gxEdit;
16: break;
17:
18: case TEXT_MODE:
19: new_cursor = gxTextI;
20: break;
21:
22: case SCALE_MODE:
23: new_cursor = gxScale;
24: break;
25:
26: case MOVE_MODE:
27: new_cursor = gxMove;
28: break;
29:
30: case NORMAL_MODE:
31: default:
32: new_cursor = gxNormal;
33: break;
34: }
35: XDefineCursor( XtDisplay(GxDrawArea),
36: XtWindow(GxDrawArea), new_cursor );
37: XFlush( XtDisplay(GxDrawArea) );
38: }
The required parameter for the set_cursor function is of the data type CursorMode.
This is a data type unique to the Graphics Editor and is defined as the enumeration
shown in Listing 17.7.
Utilities and Tools Chapter 17 351
Listing 17.7 The CursorMode Enumeration for Inclusion in the gxGraphics.h Header File
1: typedef enum _cursor_mode {
2: NORMAL_MODE = 0,
3: PENCIL_MODE,
4: EDIT_MODE,
5: TEXT_MODE,
6: MOVE_MODE,
7: SCALE_MODE,
8: LINE_MODE
9: } CursorMode;
The set_cursor function switches on the value of the CursorMode specified to the
function and sets the new_cursor value to one of the global variables shown in
Listing 17.8.
Listing 17.8 Global Cursor References for Inclusion in the gxGraphics.c Source File
1: Cursor gxCrosshair;
2: Cursor gxPencil; 17
3: Cursor gxEdit;
4: Cursor gxNormal;
5: Cursor gxTextI;
6: Cursor gxMove;
7: Cursor gxScale;
After the new_cursor value is determined, the cursor’s corresponding cursor value is
defined for the Window of the GxDrawArea widget
35: XDefineCursor( XtDisplay(GxDrawArea),
36: XtWindow(GxDrawArea), new_cursor );
and the expose event generated by the XDefineCursor function is flushed from the
application’s event queue to ensure that the new cursor appears immediately in the
drawing area window:
37: XFlush( XtDisplay(GxDrawArea) );
The Cursor variables shown in Listing 17.8 require values well in advance of the
application employing them, so a new function called initializeGX has been added
to the gxGraphics.c source file.
The contents of the initializeGX function are found in Listing 17.9. The function’s
purpose is to initialize all global variables used by the application in addition to the
cursor variables in Listing 17.8.
352 Part IV Laying Out the Parts
Listing 17.9 Source Code for the initializeGX Function for the gxGraphics.c File
1: void initializeGX( void )
2: {
3: Pixel color;
4: Display *dsp = XtDisplay(GxDrawArea);
5:
6: XtVaGetValues( GxStatusBar, XtNbackground, &color, NULL );
7:
8: gxObjHeader = NULL;
9: gxObjCurrent = NULL;
10:
11: gxCrosshair = XCreateFontCursor(dsp, XC_crosshair);
12: gxPencil = XCreateFontCursor(dsp, XC_pencil);
13: gxEdit = XCreateFontCursor(dsp, XC_cross);
14: gxTextI = XCreateFontCursor(dsp, XC_xterm);
15: gxScale = XCreateFontCursor(dsp, XC_dotbox);
16: gxMove = XCreateFontCursor(dsp, XC_hand1);
17: gxNormal = XCreateFontCursor(dsp, XC_top_left_arrow);
18:
19: rubberGC = XCreateGC(XtDisplay(GxDrawArea),
20: DefaultRootWindow(XtDisplay(GxDrawArea)), 0, NULL );
21:
22: XSetForeground( XtDisplay(GxDrawArea), rubberGC, color );
23: XSetFunction( XtDisplay(GxDrawArea), rubberGC, GXxor );
24:
25: XSetWindowBackgroundPixmap(XtDisplay(GxDrawArea),
26: XtWindow(GxDrawArea), GxDrawAreaBG);
27:
28: FixedX = FixedY = OrigX = OrigY = ExntX = ExntY = 0;
29: }
The function starts by extracting the background color of the status label for use
later in the function when the global rubberGC is defined:
6: XtVaGetValues( GxStatusBar, XtNbackground, &color, NULL );
The widget specified for determining the background color of the application is
arbitrary.
The function continues by ensuring that the head of the linked list used to manage
the objects known to the editor and the pointer to the currently active object are set
to NULL:
8: gxObjHeader = NULL;
9: gxObjCurrent = NULL;
Following this, the cursor values used to reflect the state of the application are
created:
11: gxCrosshair = XCreateFontCursor(dsp, XC_crosshair);
12: gxPencil = XCreateFontCursor(dsp, XC_pencil);
Utilities and Tools Chapter 17 353
To provide the compiler with the definition of the cursors created in lines 11–17, the
header file cursorfonts.h must be added to the beginning of gxGraphics.c source file:
#include <X11/Xaw/Box.h>
#include <X11/cursorfont.h>
Following the creation of these cursors, they are available for use during execution of
the application.
The initializeGX function next creates the global rubberGC used by the object cre-
ation routines to provide a rubber-banding effect during object creation and update
routines:
19:
20:
rubberGC = XCreateGC(XtDisplay(GxDrawArea),
DefaultRootWindow(XtDisplay(GxDrawArea)), 0, NULL ); 17
The color extracted from the status label when the function began is assigned as the
foreground color of the rubberGC Graphics Context
22: XSetForeground( XtDisplay(GxDrawArea), rubberGC, color );
and the function of the Graphics Context is set to the GXxor value, which enables the
rubber-banding behavior and ensures that items drawn using this GC do not affect the
drawing area permanently:
23: XSetFunction( XtDisplay(GxDrawArea), rubberGC, GXxor );
Finally, several global variables used to manage the object’s scale, move, and rotate
functions are initialized:
28: FixedX = FixedY = OrigX = OrigY = ExntX = ExntY = 0;
Although use of these variables is not introduced until Chapter 20, their definitions
can be added to the GLOBAL section of gxGraphics.h
GLOBAL Widget GxStatusBar;
GLOBAL Widget GxDrawArea;
GLOBAL Pixmap GxDrawAreaBG;
GLOBAL GC rubberGC;
GLOBAL int GxActiveHandle;
and they will be ready for use when the functions, which require them, are intro-
duced.
Before completing the introduction of the initializeGX function, it is important
that a call to it is added at the appropriate phase of the application startup. Listing
17.10 shows the proper placement for a call to initializeGX function.
Listing 17.10 Changes Necessary to the Function Main in gxMain.c for Invoking the
Function initalizeGX
1: .
2: .
3: .
4: XtRealizeWidget( toplevel );
5: initializeGX();
6:
7: XtAppMainLoop( appContext );
This completes a discussion of tools and utilities used by the Graphics Editor appli-
cation. These routines work to create the common portion of the editor objects, to
add objects to the management list, to create GCs to reflect the attributes of an object
(and optionally prepare that GC for erasing), as well as using the program’s cursor to
reflect the state of the editor.
Next Steps
In the next chapter, I will introduce the concept of file formats in preparation for
constructing the Save and Restore functions used by the Graphics Editor.
In this chapter
• Understanding Files
• Binary File Formatting
File Formats
As the Graphics Editor project advances in capability, it is increasingly more impor-
tant for users to be able to save and subsequently restore the graphic objects they
work so hard to create, place, and modify during a session of the editor application.
This chapter introduces the concepts and principles governing file formatting as a
segue to adding the Save and Load functions used by the Graphics Editor.
Note The most important concept to draw from this chapter is that the programmer
who authors the application that will employ a file decides the format of the data
placed in that file.
This can seem a hasty absolute as I point out that many applications understand
many file formats. However, as I introduce the concepts relating to file formats, con-
sider first that the decision of how to format a file starts with a programmer.
Several considerations guide the decision of how a file is formatted, or how the data
targeted for a file is arranged.
For example, one consideration is size, dictating whether compression should be used
in the file format. If a large amount of information is intended for the file contents
and these files are to be transmitted across a network, you should make the file as
small as possible.
Another consideration is speed. Files of considerable size that are formatted in a plain
text or ASCII format take longer to parse and load than files formatted using a binary
method.
356 Part IV Laying Out the Parts
These considerations direct the decision of the programmer in determining how the
data in a file will be organized.
Note As the save and load features of the Graphics Editor are implemented, be cog-
nizant that only the Graphics Editor application will be able to read the files for-
matted for a task.
By read I mean, of course, create graphic objects from the data contained in the
file that can be moved, scaled, rotated, and so forth.
However, as its popularity grows to worldwide renown, other graphic editors
might want to adopt the file format. This probably won’t occur for our application,
but I need a humorous way to demonstrate that file formats are generally unique
to the application programmer who conceives them.
Clearly, the more commonplace and popular the application becomes, the more
likely that other applications will adopt some level of support for their file format.
But it is not automatic.
In other words, not all graphic programs understand multiple graphic file formats,
just as not all word processors understand documents formatted by other word
processing applications.
For this reason, a unique suffix or file extension distinguishes the file type and
the application meant to interpret it.
When all the decisions are made and the file format decided, it cannot be arbitrarily
changed. Any changes to the placement of the data within the file or the characteris-
tics of the data will have a direct and significant impact on the application that reads
and writes it.
In other words, if a new data field is added to the file, the application’s functions that
write and read the file must be altered to support the new field.
Furthermore, a decision must be made as to whether the previous file format (before
the addition of the data field) will still be supported.
If support for the original format is continued, the application’s author must account
programmatically for determining which format a file passed to the application is in
and the action required to read it.
Emphasizing the impact that even a small change to the format of a file has on the
application leads me back to the statement made earlier: The programmer who
authors the application that will employ a file decides the format of the data placed
in that file.
Acknowledging the impact on the application for which the file is targeted, consider
for a moment the significance of a format change if that file format is understood by
multiple applications.
File Formats Chapter 18 357
The following sections will address creating files using both ASCII and binary for-
matting techniques. First, however, let us be sure of what a file is.
Understanding Files
In data processing, a file is a collection of records. For example, all the information
one might have on his customers would be ideal for storing in a file.
Each customer record would consist of fields for individual data elements. These data
elements could include items such as the customer’s name, identification number,
address, and so forth.
By placing all information within a record in exactly the same location for all fields
for each of the customer records, the organization of the file will be uniform and
easily manipulated by a computer program.
This example can be instantly paralleled to the data requirements of the Graphics
Editor project. Each object created in the editor will have common data fields, which
must be included in the data set saved to the file created for the purpose. However,
the task is complicated in that there are non-uniform data elements that also must be
included in the file. These non-uniform elements are the data-specific fields of the 18
various unique objects supported by the editor.
The last point to be made is the capability to distinguish file formats. Some applica-
tions describe files with given formats by assigning them a particular filename suffix.
(The filename suffix is also known as a filename extension.)
For example, a program or executable file is sometimes given or required to have an
.exe suffix. In general, the suffixes tend to be as descriptive of the formats as they
can be within the limits of the number of characters allowed for suffixes by the oper-
ating system.
What is most interesting about Listing 18.1 is the SIZE column reflecting the size of
the data field written for the data entities.
The first three fields written to a .pcx file are the Manufacturer, Encoding, and Bits
per pixel fields necessary to interpret the image data contained in the file. Each of
these data fields is only 1 bit in size. The fourth field placed in the file is the
Window element and is 8 bits in size.
Therefore, if a .pcx file were loaded into a text editor, the first 3 bits (corresponding
to the first three data elements) and the first 5 bits of the fourth data element would
be interpreted by the editor as a character.
Depending on the values of these elements, the bits can map to a printable character.
If not, the text editor will attempt to display a non-printable encoding of these 8 bits.
A discussion of the attempt to encode binary data into character data does not intro-
duce a file format consideration, but rather draws a contrast in data organization
between ASCII and binary formats.
What does serve as a consideration when choosing a binary file format is the com-
plexity required to parse the file because of its cryptic format: not that it will neces-
sarily be cryptic to the developer, but rather to those who adopt support of it later.
File Formats Chapter 18 359
An advantage of the binary file format is that the size of the file is minimized as the
absolute required data elements (down to the single bit value) are written to the file.
Generally, there is very little padding, as shown in the last field of the .pcx file
header.
As a caution when choosing the binary file format, the mechanism used to create the
file can be governed by the same limitations as binary object files introduced in
Chapter 1, “UNIX for Developers,” in the section “Object Files,” page 19.
For example, if the common object data structure introduced in Chapter 15,
“Common Object Definition,” in the section “Common Object Data Structure,”
page 315, were saved to a file using a dump as illustrated here
fwrite( gxObj, sizeof( GXObj), 1, fp );
the data would depend on the byte ordering and word groupings of the machine
architecture that performed the fwrite.
The data could only be successfully read on a machine of the same architecture.
The binary file formatting method is (generally) more efficient both in the size of
the files it generates and in the speed at which the files are parsed.
18
Files saved using ASCII file formatting (as introduced in the next section) are not as
efficient but are easily maintained.
Within a tagged file format, keywords are grouped closely to a value. Keyword value
pairs can be one per line or separated using a token known not to exist in either any
of the keyword specifications or possible values that will satisfy the keywords.
An example of a tagged file format is shown in Listing 18.2.
Optionally, all tag value pairs could appear separated by a token on the same line, as
shown in Listing 18.3.
The flexibility afforded by tagged file formats is the capability to add and remove
keys with minimal impact on the owning application.
In the absence of a keyword value pair, the application can assign a default value to
the destination field.
Further, if an unrecognized keyword value pair is found in the file, it could simply be
ignored, enabling new data elements to be added to the file with no impact on older
versions of the application loading the new file format.
The tagged ASCII file format is clearly the slowest to parse and load because of the
level of processing that must occur for the keyword value pairs found in the data file.
Not as robust as the tagged ASCII file but offering some level of convenience are the
position-specific ASCII file formats introduced in the next section.
This same format string is subsequently used to read the data with the C fscanf
function, as shown in Listing 18.5.
The position-specific mechanism, though simple to implement, does not offer the
resiliency or robustness of the tagged file method. A change to the file format intro-
duces a complication that is not trivial to overcome. However, by placing a control
tag or header element known as a magic number at the start of the file, the application
will know the expected data and the file characteristics.
Magic Numbers
The general method of determining the data elements written to an application’s
data file is through use of a magic number, which serves as a control or version num-
ber for the application’s restore function.
18
The best illustration of a magic number is its use in the graphic interchange file
(GIF) format. This graphic image file uses two slightly different formats.
A string found at the very beginning of the file distinguishes the file format as either
GIF87a or GIF89a.
Based on the value of the magic number, an application’s load function can account
for version differences corresponding to the format of the file being loaded.
The magic number, as illustrated with the strings GIF87a and GIF89, need not be an
actual number. Literally, the term refers to a position-specific entry in the data file
that can subsequently be used by the program to predict the elements contained in
the file. As described with the Tagged Line format that uses a tag line to determine
the data fields to follow, a magic number applies to the expectations of the contents
of the entire file.
Next Steps
With a clearer understanding of the decisions and options you face when determining
the best way to format a data file to meet the needs of the save-and-restore
feature of an application, you are ready to face the challenge posed in the next chapter.
Chapter 19, “Save and Restore,” satisfies the requirement of adding a save-and-
restore capability to the Graphics Editor..
In this chapter
• File Format Strategy
• Save and Restore Program Hooks
GXFont vpts;
GXFont font; /* segment definitions */
GXFontP fontp; /* num segs per char */
} GXText, *GXTextPtr;
and the standard point-array–based objects LatexLine, PolyLine, Box, and Arrow use
the GXLine data structure:
typedef struct _gxline {
XPoint *pts;
int num_pts;
} GXLine, *GXLinePtr;
Because the data requirements for the objects of the Graphics Editor are not identi-
cal, the file format strategy must account for the nesting of data for objects of differ-
ent types.
To accomplish this, a combination of the Position-Specific and Tagged-File
Formatting methods is used. The merging of the two techniques enables the restore
method of the editor to distinguish between object data for the various object types
as well as enabling the editor objects to expand the fields they save in future versions
without affecting existing data files.
Listing 19.1 shows the data file generated by procedures introduced later in this
chapter when the objects shown in Figure 19.1 are saved. The data file includes an
example of the point-array–based Line object, an Arc object, and the Text object.
Figure 19.1
A sample of the
objects saved by the
editor.
Save and Restore Chapter 19 365
The pattern of the file is to first save a tagged line indicating the format of the data
that follows on the next line. For instance,
1: OBJ - fg bg ls lw
19
shows that the data to follow will be specific to the common object portion of an
object and include the field’s foreground, background, line style, and line width.
2: 0 65535 0 1
The second line is a position-specific formatted line containing the data fields
promised by the previous tagged line.
Consider the tagged line produced by the Line object:
3: LINE [numpts x y x y ...]
This line indicates the form of the Line data that follows the tagged line. Specifically,
saving first the number of points contained in the object
4: 9
9: 52 28
10: 79 59
11: 65 59
12: 65 84
13: 74 91
Notice that the fields stored for the Text object are considerably less than those con-
tained in the GXText structure. Only the fields required to reproduce the object when
loaded into the editor are placed in the data file. For instance, because the editor
supports only one vector text font, the font data need not be placed in the data file.
If, however, in the future you expand the editor to enable the user to select from
many vector fonts, the font assigned to the saved object must be included in the data
file. Fortunately, the data file’s format strategy accounts for content expansion.
The strength of combining position-specific and tagged-file format strategies is that
speak
the application can parse the tagged line to know how to interpret the position-
geek
specific line.
In the future, when the application is advanced and functionality is added, the tagged
line is updated to reflect the additional data field(s) saved to the file. The newer ver-
sions of the application will include the evaluation of the tagged line to determine
whether an older file that doesn’t contain the additional data or a newer file that does
is being loaded.
The following sections show the necessary modifications to the Graphics Editor pro-
ject to support the save and restore functionality using a combined position-specific
and tagged-file format.
The functions gx_save and gx_load, however, have heretofore been empty (stub)
functions. The next section introduces their contents, but first, we must modify the
Common Object Structure to include an object method for saving the object-specific
data contained in each graphics object.
Add the following line to the GXObj data structure located in gxGraphics.h header
file:
void (*move) ( struct _gx_obj *, XEvent * );
void (*scale) ( struct _gx_obj *, XEvent * );
With the proper object method defined and initialized, we can now look at the con-
tents of the gx_save and gx_load functions.
The gx_save function begins by invoking a function to prompt the user for a file-
name:
5: char *filename = gxGetFileName();
When the filename is returned, a file of the specified name is opened for writing:
9: fp = fopen( filename, “w+” );
After ensuring that the file opened successfully in lines 11–13, the function
gxSaveObjs is called
specifying the beginning of the list of objects and a pointer for the opened data file.
Listing 19.3 shows the gxGetFileName function and Listing 19.4 shows the
gxSaveObjs function.
EXCURSION
Error Reporting in the C Programming Language
Notice the perror command, used for the first time in this text in Listing 19.2.
12: perror( “Failed to open file: “ );
The C programming language provides the perror function to print to stderr the pro-
grammer’s specified message (“Failed to open file: “) followed by the error reported
by the system when the offending call failed.
Save and Restore Chapter 19 369
System errors such as failing to open a file are made available to routines in C by merit of
a global variable named errno.
The value of errno is set when an error occurs, and its value corresponds to an array of
error strings maintained by C. For instance, if you wanted the Graphics Editor to report the
error to the status bar, replace the perror call with the following:
{
char msg[128];
sprintf( msg, “Failed to open file %s : %s\n”,
filename, strerror( errno ) );
setStatus( msg );
}
The strerror function, also provided by C, will find the error string corresponding to the
value of errno. In order for these functions to be prototyped for use and errno to be
visible in the source file, you must include the error.h header file and extern the
errno variable. This is accomplished with the following lines:
#include <error.h>
extern in errno;
continues
370 Part IV Laying Out the Parts
The gxGetFileName function that is found in Listing 19.3 creates a dialog widget
with the single OK button (lines 9–18). An illustration of dialogWidget created by
this function is found in Figure 19.2.
Figure 19.2
Prompting the user for a
filename.
The function then extracts the XtApplicationContext and uses it to intercept events
from the XtAppMainLoop by extracting and dispatching events until the user closes the
dialog prompting for input:
20: app = XtWidgetToApplicationContext( GxDrawArea );
21:
22: while( XtIsManaged(dialog)) {
23: XtAppNextEvent( app, &event );
24: XtDispatchEvent( &event );
25: }
A widget is considered managed as long as it is visible on the screen. When the user
presses the OK button, the dialog is removed from the screen by the close_dialog
function registered as the OK button’s callback action
18: XawDialogAddButton( dialog, “ Ok “, close_dialog, dialog );
19
and the test XtIsManaged returns False, ending the while loop.
The close_dialog function simply “unmanages” the dialogWidget when the button
is pressed:
61: if( dialog ) XtUnmanageChild( dialog );
The value entered into the text field inherent to the dialogWidget is consulted for
the user’s input
27: str = XawDialogGetValueString( dialog );
The remainder of the gxGetFileName function ensures the integrity of the string for
use as a filename. Specifically, a filename cannot contain newlines, spaces, or, solely
for the purpose of illustration, lowercase z characters.
When the function is sure of the contents of the string, a copy is returned to the
user:
51: if( str && *str )
52: return XtNewString(str);
372 Part IV Laying Out the Parts
Otherwise, if the removal of illegal characters has consumed the string, NULL is
returned:
53: else
54: return NULL;
The gxSaveObjs routine introduced in Listing 19.4 satisfies the final function intro-
duced in Listing 19.2
14: gxSaveObjs( fp, gxObjHeader );
and
14: gxSaveObjs( fp, obj->next );
are both important to the recursive nature of the function. Line 10 ensures that a
valid object reference was provided as the end of the list of objects is marked with
NULL, whereas line 14 passes the next possible object.
For each object contained in the list, two functions are invoked. The first is to save
the common object data fields
11: gxSaveCommon( fp, obj );
The gxSaveCommon found simply writes the tagged line and the position-specific data
line to the file referenced by the file pointer:
3: fprintf( fp, “OBJ - fg bg ls lw\n” );
4: fprintf( fp, “ %ld %ld %d %d\n”,
5: obj->fg, obj->bg, obj->ls, obj->lw );
The graphic objects’ save methods are introduced in Chapters 20–24 with the defini-
tion of the object internals.
The following section demonstrates how the data file generated by the save action of
the Graphics Editor is restored in the editor.
After prompting for and validating the filename, the file is opened read-only:
8: if( filename ) {
9: fp = fopen( filename, “r” );
374 Part IV Laying Out the Parts
If the file is opened successfully, the function gxLoadObjs is called with the file
reference:
14: gxLoadObjs( fp );
Listing 19.6 introduces the gxLoadObjs function, which traverses the file referenced
by fp and invokes the necessary create functions to restore the objects to the editor.
The gxLoadObjs function retrieves a line from the file reference by the file pointer fp
12: while( fgets( objForm, 128, fp ) != NULL ) {
using the C fgets function, which fills the buffer objForm up to the occurrence of a
newline character in the file.
Save and Restore Chapter 19 375
Note Knowing the format of the file, you are aware that this line contains the tagged
field line specifying the data elements provided on the following line.
Because this is version 1.0, there is no need to parse the tagged line to deter-
mine the fields specified. If, however, in future versions of the application, data
fields are added to the save format, this is when the line would be parsed.
Following the extraction of the tagged line, the data for the object-specific values is
read:
14: gxLoadCommon( fp, obj );
Notice that in the gxLoadCommon function, the syntax for reading the data written by
gxSaveCommon is very similar. Specifically, the fwritef has been renamed fscanf and
addresses of the structure elements are provided so that the values read from the file
can be assigned to them:
3: fscanf( fp, “ %ld %ld %d %d\n”,
4: &obj->fg, &obj->bg, &obj->ls, &obj->lw );
With the common object elements restored, the next line of the file is the tagged
line of the object-specific data. By reading this line into the objForm array
16: fgets( objForm, 128, fp ); 19
you can determine which load routine to call because the first character is sufficient
for determining the object type:
17: switch( *objForm ) {
Based on the value of the first character, one of the methods gxArcLoad, gxLineLoad,
or gxTextLoad is invoked to read the data specific to an object of that type.
These functions are introduced in the following chapters when (finally you must be
saying) the internals of the various editor objects are introduced.
376 Part IV Laying Out the Parts
Next Steps
The addition of functionality to support the save and restore capability of the
editor is necessary to produce a mature product. Relative to the flow of this text,
this chapter completes the evolution of the graphics editor objects and enables us to
address the important task of defining each object in full.
The following five chapters in Part V, “Adding Objects to the Editor,” provide the
definition of internal object methods and functions required to create a functional
graphics editor.
Part V
Adding Objects
to the Editor
In this chapter
• Creating a Latex Line Object
• Drawing and Erasing a Line Object
Figure 20.1
Choose the Latex Line
creation icon to begin the
Latex Line object
creation process.
During the creation of a Latex Line object, points are selected from the canvas to
define the vertices of the object. There is no limit to the number of points or the
direction of the segments created during this process.
When all the points are defined for the new Latex Line object, the user double-
clicks the mouse cursor to indicate the end of the creation. Upon completing the
interactive mode of the Latex Line object creation, a plethora of events occur.
This chapter presents the internal methods and supporting functions defined for the
Latex Line object. These routines control all aspects of managing the different fea-
tures of the object.
Note The code listings introduced in this chapter are targeted for placement in the
gxLine.c source file. Additionally, functions presented in the listings that are not
defined static should have a corresponding prototype placed in the gxProtos.h
header file.
380 Part V Adding Objects to the Editor
assigned the Latex Line icon the gx_line function for invocation when selected by
the user. This function is defined in Listing 20.1, and it serves as the entry point to
the creation process of the Latex Line object.
In reviewing the definition of the gx_line function, you instantly see that the func-
tion is entirely event driven—meaning, based on the event specified, the gx_line 20
function takes an appropriate action.
The actions for each of the events anticipated can be generalized as follows:
• Upon receiving a ButtonPress event, begin the creation process.
• Subsequent ButtonPress events cause the addition of the event point to an
array of points contained in the rubber_line structure.
• A ButtonPress event point that equals the previous point inserted into the
rubber_line structure indicates a double-click and ends the creation.
Passing a NULL event pointer to the gx_line function enables the local variable
rubber_line to be reset, thereby aborting the current creation mode. This is neces-
sary to eliminate the possibility of the user failing to end one creation process before
starting another. When a new creation process is started for any type of object, a
NULL event is passed first to ensure the initial state.
When, however, a valid event pointer is specified, the rubber_line variable is passed
to the GXRubberLine function, which is responsible for updating the line using the
interactive rubber-banding GC as follows:
9: GXRubberLine( rubber_line );
Then the event type is checked to determine which action body the function should
execute:
11: switch( event->type ) {
If the current event is a ButtonPress event, many things can happen because the
action body associated with this event is the busiest. If the rubber_line variable is
still NULL, the action body knows that this is the first iteration and sets the application
cursor to reflect the new mode:
13: if( rubber_line == NULL ) set_cursor( LINE_MODE );
Note As you might recall, the cursor control functions were introduced in Chapter 17,
“Utilities and Tools,” in “Using the Cursor as a State Indicator,” page 349.
When a ButtonPress event occurs, the gx_line action for it must determine whether
this event’s location coincides with the pervious ButtonPress event point passed to
the function:
18: if( point_equal_event( rubber_line, event ) == True ) {
If the point_equal_event function indicates that the points are equal, the creation
process ends by erasing the interactive rubber-banding line and calling the
create_line function (lines 21–25).
The ButtonPress case then returns the cursor to normal and clears the rubber_line
variable:
28: set_cursor( NORMAL_MODE );
29: rubber_line = NULL;
Latex Line Object Chapter 20 383
However, if the point_equal_ function returned False, indicating that the current
event point does not equal the previously added point (no double-click), the else
body is entered, the interactive line is updated
34: GXRubberLine( rubber_line );
If a valid rubber_line structure reference exists, replace the last point in the array
with the current event point:
52: rubber_line->pts[rubber_line->num_pts-1].x =
53: event->xbutton.x;
54: rubber_line->pts[rubber_line->num_pts-1].y =
55: event->xbutton.y;
If the rubber_line->pts array contains only one point, the array must be expanded
to accommodate the point addition:
58: (void)update_line( event, rubber_line);
Note that the last point added to the pts array by the ButtonRelease or
MotionNotify actions is not committed to the list of vertices that are specified by the
user. Instead, this last point simply makes it easier for the GXRubberLine to do its job
because it only must update the last segment defined by the array. This segment con-
sists of one point selected by the user (second to the last point of the array) and one
20
point stored temporarily by ButtonRelease or MotionNotify (last point). In other
words, one more point is maintained in the rubber_line->pts array than requested
by the user. This last point stored by the ButtonRelease and MotionNotify is
replaced with the next ButtonPress event.
The last thing the gx_line function is responsible for is ensuring that the interactive
rubber_line is again updated to the screen:
63: GXRubberLine( rubber_line );
The GXRubberLine function defined in Listing 20.2 updates segments of the line
being created, providing a visual interactive creation for the user by enabling him to
see where the next segment endpoint will be placed when the mouse button is
pressed.
384 Part V Adding Objects to the Editor
The GXRubberLine function begins by ensuring enough points are defined to form a
line segment
4: if( line && line->pts && (line->num_pts > 1)) {
and then it assigns the variable indx the value of one less than the number of points,
which is the last point of the array:
5: indx = line->num_pts - 1;
Then the last segment of the line defined by the pts array is drawn using indx (the
last point) and indx - 1 (the second to the last point):
6: XDrawLine( XtDisplay(GxDrawArea),
7: XtWindow(GxDrawArea), rubberGC,
8: line->pts[indx ].x,
9: line->pts[indx ].y,
10: line->pts[indx-1].x,
11: line->pts[indx-1].y );
5:
6: xe_x = event->xbutton.x;
7: xe_y = event->xbutton.y;
8:
9: /*
10: * the last point will always be the current motion event
11: * so check the one before for redundancy (equates to a
12: * double click to end the action )
13: */
14: if( line && (line->num_pts > 2) ) {
15: int num = line->num_pts - 2;
16:
17: if( (abs(line->pts[num].x - xe_x) <= TOLERANCE ) &&
18: (abs(line->pts[num].y - xe_y) <= TOLERANCE )) {
19:
20: pts_equal = True;
21: }
22: }
23: return pts_equal;
24: }
The point_equal_point function must ensure that there are at least three points in
the pts array before determining whether a double-click occurred:
14: if( line && (line->num_pts > 2) ) {
This is necessary because the ButtonRelease or MotionNotify and not the user
placed the last point of the array. In order for the line creation to be valid, at least
two user-specified points must be in the array.
When the correct number of points exists in the array, the last user-defined point is
compared to the event point. If the absolute value of their differences is less than the 20
TOLERANCE (3) defined in the gxGraphics.h header file, they are considered equal.
Note The margin of error allowed by TOLERANCE accounts for the user having Monday
morning shakes.
Furthermore, even on a good day, it is difficult to click the exact same point on
the screen consecutive times. It is expected that the mouse can move ever so
slightly from the subtle movement of hand muscles required to double-click the
mouse.
If the points are deemed equal (or close enough), the interactive portion of the cre-
ation process ends and an actual line object is created with a call to the create_line
function found in Listing 20.4.
386 Part V Adding Objects to the Editor
enables an invocation of the procedure with the caller having already created the
common object to contain the line data. This supports the restoring of the object
from a saved file as is seen later in the chapter.
During interactive creation, however, the function create_line is called with a NULL
value as the first parameter forcing the function to create the common object portion
through a call to gx_create_obj introduced in Chapter 17, “Utilities and Tools,” sec-
tion “Common Object Creation,” page 343.
7: obj = gx_create_obj();
and the contents of the second argument to the function is copied into the new
structure:
Latex Line Object Chapter 20 387
Finally, the new data structure is assigned as the unique data value for the new
object:
13: obj->data = line_data;
Consistent with objects of type Line, the methods for the new object are assigned
the line manipulation functions for controlling the object-specific data structure in
lines 15–24.
Finally, the create_line function must ensure the newly created object is retained by
the application by adding the object to the linked list used to manage editor objects:
26: gx_add_obj( obj );
Note To review the gx_add_obj function, refer to Chapter 17, section “Linked List
Management,” page 346.
The final function for review is the update_line function found in Listing 20.5,
which is employed by gx_line to commit user selected points to the
rubber_line->pts array.
The update_line function must manage the memory associated with the points
array, holding the vertices added to the Line object by the user. If the line pointer is
NULL, an XtMalloc is called to assign the initial storage space for the pts array
388 Part V Adding Objects to the Editor
After sufficient room is obtained to contain the new point, it is added to the array
14: xline.pts[xline.num_pts].x = event->xbutton.x;
15: xline.pts[xline.num_pts].y = event->xbutton.y;
As the user interactively creates the Latex Line, points are added to the
rubber_line->pts array and the number of points tracked in the num_pts field. At
some point the user will double-click the mouse cursor, which is caught by the
point_equal_point function, and the Line object will be created by the call to
create_line. A created object is no longer drawn using the interactive rubber-
banding GC, but by invoking the draw method of the object.
The following section introduces the methods that draw and erase the Line object
from the drawing area canvas.
8: XDrawLines(XtDisplay(GxDrawArea),
9: XtWindow(GxDrawArea), gc,
10: line_data->pts, line_data->num_pts,
11: CoordModeOrigin );
12:
13: XtReleaseGC( GxDrawArea, gc );
14: }
15:
16: static void line_draw( GXObjPtr line )
17: {
18: draw_erase( line, False );
19: }
20:
21: static void line_erase( GXObjPtr line )
22: {
23: draw_erase( line, True );
24: }
The draw_erase function begins by extracting the GXLine data structure from the
common object’s data field
4: GXLinePtr line_data = (GXLinePtr)line->data;
Important to the creation of the Graphic Context is the specification of the tile flag
to the gx_allocate_gc function introduced in Chapter 17, section “Creating a
Graphics Context,” page 347.
Note
20
If you recall, this flag indicated whether the background Pixmap of the
GxDrawArea was assigned as the value to the tile field of the GC created.
An appropriately created GC for the current draw or erase action, the draw_erase
function can request the object be updated in the canvas window:
8: XDrawLines(XtDisplay(GxDrawArea),
9: XtWindow(GxDrawArea), gc,
10: line_data->pts, line_data->num_pts,
11: CoordModeOrigin );
Because the X Server will attempt to cache the GC for future requests, it is important
to specify the complete use of it with a call to XtReleaseGC:
13: XtReleaseGC( GxDrawArea, gc );
390 Part V Adding Objects to the Editor
With the object visible on the screen, it is now eligible for manipulation by the user.
However, before it can be moved, scaled, or deleted, it must be selected by the user.
The next section introduces the Line object’s find method used to determine
whether an event has successfully located the object on the drawing area.
The line_find function extracts the event point from the XEvent structure
6: p.x = event->xbutton.x;
7: p.y = event->xbutton.y;
and then the intersection of the point with the segments forming the object:
12: found = segment_selected((GXLinePtr)line->data,&p);
The segment_selected function, found at the beginning of Listing 20.8, breaks the
Line object into point pairs, defining each of the line segments that comprise the
object. Each of these segments is passed to the near_segment function introduced in
Chapter 10, “Trigonometric and Geometric Functions,” in the section “Calculating
Point and Line Intersection,” page 211.
If for any segment the near_segment function returns True, the for loop ends and
the value is returned to indicate that the object has been found.
The point_selected function, found in Listing 20.8, parses all the points defining
the vertices (segment endpoints) of the object looking for a point within TOLERANCE
of the event point.
If an appropriate point is found, True is returned to the line_find method, indi-
cating that the current Line object has been successfully located by the event.
392 Part V Adding Objects to the Editor
The line_find method serves two purposes in the management of the Line object.
One purpose of the line_find method is to enable the user to select the object for
manipulation. A second purpose determines whether the manipulation requested by
the user is the move action.
The follow section introduces the step stemming from the line_find method
resulting in the selection (or implicit deselection) of the Line object.
The process of creating the handles for the Line object begins with a call to
line_bounding_handles found in Listing 20.10.
14: return;
15: }
16:
17: for( i = 0; i < 8; i++ ) {
18: gx_line->handles[i].width = HNDL_SIZE;
19: gx_line->handles[i].height = HNDL_SIZE;
20: }
21:
22: get_bounds( line_data->pts, line_data->num_pts,
23: &x1, &y1, &x2, &y2 );
24: width = x2 - x1;
25: height = y2 - y1;
26:
27: gx_line->handles[0].x = x1 - HNDL_OFFSET - HNDL_SIZE;
28: gx_line->handles[0].y = y1 - HNDL_OFFSET - HNDL_SIZE;
29:
30: gx_line->handles[1].x = x1 + (width/2) - HNDL_OFFSET;
31: gx_line->handles[1].y = y1 - HNDL_SIZE - HNDL_OFFSET;
32:
33: gx_line->handles[2].x = x2 + HNDL_OFFSET;
34: gx_line->handles[2].y = y1 - HNDL_SIZE - HNDL_OFFSET;
35:
36: gx_line->handles[3].x = x2 + HNDL_OFFSET;
37: gx_line->handles[3].y = y1+(height/2)-HNDL_OFFSET;
38: gx_line->handles[4].x = x2 + HNDL_OFFSET;
39: gx_line->handles[4].y = y2 + HNDL_OFFSET;
40:
41: gx_line->handles[5].x = x1 + (width/2) - HNDL_OFFSET;
42: gx_line->handles[5].y = y2 + HNDL_OFFSET;
43:
44: gx_line->handles[6].x = x1 - HNDL_OFFSET - HNDL_SIZE;
45: gx_line->handles[6].y = y2 + HNDL_OFFSET;
46: 20
47: gx_line->handles[7].x = x1 - HNDL_OFFSET - HNDL_SIZE;
48: gx_line->handles[7].y = y1+(height/2)-HNDL_OFFSET;
49: }
After testing for a failure of the allocation routine, the widths of all of the handles for
the Line object are assigned:
17: for( i = 0; i < 8; i++ ) {
18: gx_line->handles[i].width = HNDL_SIZE;
19: gx_line->handles[i].height = HNDL_SIZE;
20: }
394 Part V Adding Objects to the Editor
Then, in keeping with the name of the function, the bounds of the Line object are
obtained in order to determine the placement of the handles:
22: get_bounds( line_data->pts, line_data->num_pts,
23: &x1, &y1, &x2, &y2 );
Note The line_bounding_handles assigns handles at each corner and on every side
of the object to mark the bounding box that is required to contain the object.
For a review of object handles see Chapter 16, section “Managing Object
Handles,” page 327.
Note The get_bounds function is borrowed from the Text object and is introduced with
the Text object internal methods and functions in Chapter 24, “Vector Text
Object.”
The get_bounds function finds the minimum and maximum points of the segment
endpoints contained in the pts array.
Using these extents, the line_bounding_handles determines the width and the height
of the object:
24: width = x2 - x1;
25: height = y2 - y1;
Last comes the tedious task of placing each of the eight handles at the appropriate
location around the object in lines 27–48.
The position of the handles runs clock-wise, starting with handles[0] located in the
upper-right corner of the object’s bounds.
This discussion addressed the for-instance of the line_find method resulting in the
location of an object leading to its selection. If, however, the find methods of the
objects drawn on the editor’s canvas are invoked with no resulting object found, any
previously selected object must be deselected.
The Line object’s deselect method introduced in Listing 20.11 shows the steps nec-
essary to remove the handles indicating the active state of the Line object.
4: gx_erase_handles( line );
5:
6: XtFree((char *)line->handles );
7:
8: line->handles = NULL;
9: line->num_handles = 0;
10: }
11: }
As you can see when reviewing Listing 20.11, it is easier to deselect a Line object
than it is to select one.
The deselect method ensure that handles exist for the Line object and erases them
from the screen
4: gx_erase_handles( line );
and returns the handles and num_handles fields to their initial values:
8: line->handles = NULL;
9: line->num_handles = 0;
continues
396 Part V Adding Objects to the Editor
Note As you might recall, the move and scale actions are assigned and invoked from
the process_event function introduced in Chapter 16, section “Processing User
Navigation of Objects,” page 334.
The line_move function uses the static variables x and y to determine whether this
invocation is the first time the function has been called:
8: if( x && y ) {
Latex Line Object Chapter 20 397
If it is not the first time, lines 9–12 erase the rubber-banding copy of the Line object
drawn to reflect the object’s new location.
However, if x and y are still 0, the else is entered and the Line object’s erase
method is called to remove it from the screen so that it can be replaced with the
interactive version:
15: (*line->erase)( line );
It is important also for the first iteration to save the points associated with the cur-
rent event:
17: x = event ? event->xbutton.x : 0;
18: y = event ? event->xbutton.y : 0;
Note Notice that the if-then-else syntax of lines 17–18 enables the absence of a valid
event reference for resetting x and y values to 0 to cancel the move action.
If there is a valid event reference, all points that compose the Line object are moved
the difference of the static x and y values and the current event x and y compo-
nents:
22: for( i = 0; i < line_data->num_pts; i++ ) {
23: line_data->pts[i].x += (event->xbutton.x - x);
24: line_data->pts[i].y += (event->xbutton.y - y);
25: }
Assuming that every MotionNotify event successfully reaches the line_move function,
the object is relocated one pixel at a time. 20
The interactive depiction of the object is redrawn to the screen for the new point
values and the current event point is saved to the static x and y variables in lines
30–36.
Notice again that lines 22–25 directly apply the distance the cursor has moved dur-
ing the action to the points contained in the line_data->pts array. This is possible
because the move transformation does not risk any rounding error.
Whole pixel values are represented by the x and y components of the event structure
and their difference from the static x, y are applied directly to the whole pixel val-
ues contained in the object. In other words, it is an entirely integer-based calculation.
As demonstrated in the following section, the scale method of the Line object has
the potential of suffering data loss because of rounding errors and requires a slightly
more intense management of the action.
398 Part V Adding Objects to the Editor
41:
42: XtFree((char *)tmp_data->pts);
43: XtFree((char *)tmp_data);
44:
45: tmp_data = NULL;
46: }
47: }
48: }
The line_scale method, concerned with compounding rounding errors prone to the
floating point calculations of scaling the points contained in the line_data->pts
array, makes a copy of the points each time the function is invoked. Then the delta
of a FixedX and FixedY and current event point components are applied to the origi-
nal line_data->pts. This management of data points ensures that the margin of
error introduced by the floating-point calculations is absolutely minimized.
Note For a review of the calculations performed by the apply_delta function as well
as the declaration and assignment of the FixedX and FixedY variables, see
Chapter 11, section “Scaling a Line,” page 234.
Similar in structure to the line_move function, the line_scale function uses the
static tmp_data to determine whether the function is called for the first time
6: if( tmp_data ) {
as a means of updating the interactive Line object reflecting the user’s scale actions.
Otherwise, the Line object is erased 20
13: (*line->erase)( line );
These global variables are used by the scale support function called by the
apply_delta function introduced in Listing 11.4.
400 Part V Adding Objects to the Editor
Then, so long as a valid event reference is passed to line_scale, the original object
points are copied to the temporary structure
25: memcpy( (char *)tmp_data->pts, (char *)line_data->pts,
26: sizeof(XPoint) * tmp_data->num_pts );
and the delta created by the movement of the mouse cursor is applied to the tempo-
rary points:
28: apply_delta( tmp_data->pts, tmp_data->num_pts,
29: FixedX - event->xbutton.x,
30: FixedY - event->xbutton.y );
At some point, the user will release the mouse cursor, ceasing the move action, and
the temporary points replace the Line object’s original points:
38: memcpy( (char *)line_data->pts,
39: (char *)tmp_data->pts,
40: sizeof(XPoint) * line_data->num_pts );
With no further need for the temporary structure, it is removed from memory
42: XtFree((char *)tmp_data->pts);
43: XtFree((char *)tmp_data);
The points now contained in the line_data->pts array are a scaled version of the
original points with minimal loss of line integrity because of floating-point rounding
errors.
Only two more Line object methods await discovery before completing the addition
of this object to the Graphics Editor.
The next section introduces the copy method invoked by selecting the copy control
icon from the menu panel.
3: int i;
4: GXLinePtr temp_data;
5: GXLinePtr line_data = (GXLinePtr)line->data;
6:
7: (*line->deselect)( line );
8:
9: temp_data = (GXLine *)XtNew(GXLine);
10: temp_data->num_pts = line_data->num_pts;
11:
12: temp_data->pts = (XPoint *)
13: XtMalloc(sizeof(XPoint) * temp_data->num_pts );
14: for( i = 0; i < temp_data->num_pts; i++ ) {
15: temp_data->pts[i].x = line_data->pts[i].x + OFFSET;
16: temp_data->pts[i].y = line_data->pts[i].y + OFFSET;
17: }
18:
19: create_line( NULL, temp_data );
20: XtFree((char *)temp_data );
21: }
a temporary GXLine structure is created in lines 9–13 and the original object’s points
are copied to the new structure, but incremented by OFFSET so the new object does
not exactly overlay the original: 20
15: temp_data->pts[i].x = line_data->pts[i].x + OFFSET;
16: temp_data->pts[i].y = line_data->pts[i].y + OFFSET;
This temporary GXLine structure is used to create a brand new Line object con-
taining the offset copy of the original object’s points:
19: create_line( NULL, temp_data );
The following section presents the last area of functionality required by the Line
object, Saving and Restoring the object-specific data.
The line_save function extracts the GXLine data structure from the common portion
of the object
4: GXLinePtr line = (GXLinePtr)obj->data;
and prepares for saving the data it contains by writing a tagged line reflecting the
format of the data to follow
6: fprintf( fp, “LINE [numpts x y x y ...]\n” );
Finally, it writes point pairs one per line to the destination file referenced by fp:
9: for( i = 0; i < line->num_pts; i++ ) {
10: fprintf( fp, “%d %d\n”,
11: line->pts[i].x, line->pts[i].y );
12: }
The gxLineLoad function found in Listing 20.16 shows how the data written by the
line_save method is restored to the editor.
The gxLineLoad function reverses the steps of the line_save method by retrieving
first the number of points saved to the file
6: fscanf( fp, “%d\n”, &line.num_pts );
The introduction of the method and function required to save and restore the Line
object completes the introduction of this object into the Graphics Editor.
Next Steps
The Latex Line object introduced in this chapter is only one of four point-
array–based objects that share the GXLine object-specific data structure.
In the next chapter I will introduce the Pencil object, which enables a user to create 20
a free-style line using an unlimited number of points.
In this chapter
• Creating a Pencil Object
• Pencil Object Management
Figure 21.1
Selecting the Pencil cre-
ation icon starts the
process for a freestyle
line.
The creation of the Pencil object begins with the first ButtonPress and ends with
the next.
During the interim MotionNotify events separating the ButtonPress events starting
and ending the creation, points are added to a points array defining the object under
construction.
However, not every point is put into the array. This approach would require larger
point array storage with no added value because only points that alter the slope of
the implied line segment for the Pencil object being created are important enough
for storage.
This as well as other aspects of managing the Pencil object creation are introduced
in the sections that follow.
Note The code listings introduced in this chapter are targeted for placement in the
gxLine.c source file. Additionally, functions presented in the listings that are not
defined static should have a corresponding prototype placed in the gxProtos.h
header file.
406 Part V Adding Objects to the Editor
Because the initial x and y values were set to –1, any value returned from
gx_new_vertex indicates the need to process them.
The update_pencil function then determines, based on the value of the GXLine ref-
erence pen, whether this is the first time through the function
16: if( !pencil ) {
to determine which allocation routine to invoke to create space for the point being
added. On the first pass through this function, the pen value is NULL and the XtMalloc
function is used to gain the initial memory assignment:
17: pen.pts = (XPoint *)XtMalloc( sizeof(XPoint) );
18: pen.num_pts = 0;
Look again at the gx_new_vertex function introduced in Listing 21.3 and used by
update_pencil to determine whether the point contained in the event structure is
worthy of being added to the points array for the Pencil object under creation.
Pencil Line Object Chapter 21 409
First ensuring that there is a valid point for comparison contained in the upd struc-
ture passed as the second parameter to the function
6: if( upd &&
7: (upd->num_pts > 0) && (xe->xbutton.x > 0) ) {
the gx_new_vertex also ensures that the value of the x component of the event point
is not 0 because this would result in a fatal application error when used as the
denominator of a divide calculation later in the function.
After the validation step of lines 6–7, two slope values are calculated. The first
21
12: a = (float)upd->pts[upd->num_pts - 1].y /
13: (float)upd->pts[upd->num_pts - 1].x;
is for the previous point added to the update structure. The second
15: b = (float)xe->xbutton.y / (float)xe->xbutton.x;
Note To review slope calculations for a line, return to Chapter 10, “Trigonometric and
Geometric Functions,” in the section “Calculating Slope,” page 216.
410 Part V Adding Objects to the Editor
The function then compares the two slope calculations to determine whether they
are equal:
17: if( a != b ) {
Only if they are not equal are they returned to the calling function for addition into
the pts array of the object being created:
18: *x = xe->xbutton.x;
19: *y = xe->xbutton.y;
No value is gained by adding points with like slopes to the array, because the
XDrawLines function will ensure that points connecting endpoints are drawn to the
screen.
This method of evaluating points before adding them to the object minimizes the
space required to represent them by not including needless information.
When the ButtonPress event marking the end of the creation process for the Pencil
object is received, the create_line function introduced in the previous chapter is
invoked to create a Line object:
27: create_line( NULL, rubber_pencil );
As described in the next section, no other functions are unique to the management of
the Pencil object.
Next Steps
Two other point-array–based objects exist that employ the GXLine data structure to
represent their object-specific data. These, too, will end up as point-array–based
objects using the same methods for management as the Latex Line and Pencil
objects. However, like the Pencil object, they require a unique creation process.
The following chapter introduces the creation process for two other point-
array–based objects supported by the Graphics Editor: the Box object and the Arrow
object.
In this chapter
• The Box Object
• The Arrow Object
Object Templates
The Box and Arrow objects supported by the editor are considered template objects
because the interactive process of creating these objects enforces the shape appro-
priate to the object.
The following sections introduce the Box and Arrow objects. You will notice many
similarities between these objects and the Latex Line and Pencil objects introduced
in previous chapters.
Note The code listings introduced in this chapter are targeted for placement in the
gxLine.c source file. Additionally, functions presented in the listings that are not
defined static should have a corresponding prototype placed in the gxProtos.h
header file.
Figure 22.1
The Box object
creation icon.
During the creation of the Box object, the user moves the mouse cursor to the canvas
window and presses and holds the mouse button while dragging to form the box
dimensions desired.
412 Part V Adding Objects to the Editor
Releasing the mouse button ends the creation process, and a Box object consistent
with the size specified during the interactive phase of the creation is added to the list
of objects known to the editor.
The gx_box function introduced in Listing 22.1 satisfies the assignment made to the
gxDrawIcons array in Chapter 13, “Application Structure,” for the function to invoke
when the gx_box icon is selected:
{ &box_icon, (void (*)(void))gx_box,
“Draw a square or rectangle...” },
The gx_box function differs from the creation routine gx_line and gx_pencil only in
the form of the temporary XRectangle rubber_box data structure employed, the
update applied to it
36: (void)update_box( event, rubber_box );
The challenge of the update_box function is to determine when the user flips the box
over one of the x- or y-axis during creation.
To understand this, first consider that the upper-left corner of the box is placed at
the ButtonPress event location indicating the start of the box creation, and the lower
corner of the box is placed at the ButtonRelease event location ending the creation.
414 Part V Adding Objects to the Editor
When the event location marking the end of the creation process is less than the
event location starting the creation, the object has flipped. This is important to catch
because we don’t want to specify a negative width or height value for the rectangle
defining the Box object. It is the responsibility of the update_box function to account
for a flipped condition.
The update_box function begins by determining whether this is the first call to this
function for the current creation as indicated by the value of upd being NULL:
9: if( upd == NULL ) {
10: fix_x = event->xbutton.x;
11: fix_y = event->xbutton.y;
12: }
At the first invocation for a creation, the static variables fix_x and fix_y must be
assigned the corresponding components of the event point.
This fixed point will enable the function to prevent subsequent events resulting in
the box being flipped over either the x- or the y-axis.
The XRectangle reference by box is then assigned an x, y location that is the smaller
of the values fix_x, fix_y, and the corresponding event point components:
14: box.x = min( fix_x, event->xbutton.x );
15: box.y = min( fix_y, event->xbutton.y );
From the location assignment of box, the width and height are set as the larger of
fix_x, fix_y, and the corresponding event point components less whatever x, y
values were assigned to box:
17: box.width = max( fix_x, event->xbutton.x )-box.x;
18: box.height = max( fix_y, event->xbutton.y )-box.y;
Following the successful update of the interactive box defining the wishes of the user,
the creation process will end when she releases the mouse button. When the creation
ends, the XRectangle used during the creation process must be converted to a GXLine
structure for a point-array–based object to result from this process.
Listing 22.3 introduces the line_from_box function for converting between the two
structures.
8: line.pts[0].x = box->x;
9: line.pts[0].y = box->y;
10:
11: line.pts[1].x = box->x + box->width;
12: line.pts[1].y = box->y;
13:
14: line.pts[2].x = box->x + box->width;
15: line.pts[2].y = box->y + box->height;
16:
17: line.pts[3].x = box->x;
18: line.pts[3].y = box->y + box->height;
19:
20: line.pts[4].x = box->x;
21: line.pts[4].y = box->y;
22:
23: return &line;
24: }
The steps required to convert an XRectangle to a GXLine structure are very simple
and only require that we ensure the resulting GXLine object is closed by repeating the
first point as the last point.
The line_from_box function creates space enough for five points in the GXLine pts
array
5: line.pts = (XPoint *)XtMalloc( sizeof(XPoint) * 5);
6: line.num_pts = 5;
and then it assigns each corner of the rectangle to a corresponding element of the
pts array in lines 8–18, repeating the first point in the last element:
Without this last step, the resulting line object would appear as a sideways U with
the open end facing to the left.
With a GXLine structure representing the box created by the user, the create_line
function is called and management continues for this object as it does for both the
Latex Line and Pencil objects. 22
Considerations similar to those for the Box object exist for the Arrow object intro-
duced in the next section.
Figure 22.2
The Arrow creation
icon.
The Arrow object, like the Box object, is considered a template object because the
Arrow shape is enforced by the creation process. Also similar to the Box object is the
fact that the ButtonPress event begins the creation process and the ButtonRelease
event ends it.
The event point associated with the ButtonPress determines the upper-left corner of
the object being created and the ButtonRelease event point marks the lower-right
corner of the object.
Listing 22.4 introduces the gx_arrow function assigned in the gxDrawIcons array in
Chapter 13.
The gx_arrow function should be immediately familiar because it follows the same
structure as the gx_box (and its predecessors).
The only difference, in fact, between the gx_box function and the gx_arrow function
is the arrow_from_box function used to convert from the XRectangle structure to the
GXLine structure in the following lines:
26: create_line(NULL,arrow_from_box(rubber_box));
38: arrow = arrow_from_box( rubber_box );
continues
418 Part V Adding Objects to the Editor
23:
24: pts[6].x = box->x + ((box->width * 3) / 4);
25: pts[6].y = box->y + (box->height / 2);
26:
27: pts[7].x = box->x + ((box->width * 3) / 4);
28: pts[7].y = box->y + box->height - (box->height / 8);
29:
30: pts[8].x = box->x + box->width - (box->width / 10);
31: pts[8].y = box->y + box->height;
32:
33: arrow.pts = pts;
34: arrow.num_pts = 9;
35:
36: return &arrow;
37: }
Figure 22.3
An illustration of the
arrangement of points in
an Arrow.
Lines 6–31 meticulously assign the point values based on the proportions and ratios
consistent with point positions shown in Figure 22.3.
Note Nothing is truly scientific about dividing a box into a series of points to achieve
the shape of an arrow.
I accomplished it through trial and error until the results were aesthetically
pleasing.
It is important that ratios of the box be used to create the points defining the
arrow, however, in order to enable the aspect ratio (width to height proportion) to
be honored in the resulting GXLine structure.
The point array is then assigned as the pts value of the GXLine structure along with
the correct number of points
33: arrow.pts = pts;
34: arrow.num_pts = 9;
Object Templates Chapter 22 419
and the address of the GXLine structure arrow is returned for use in either drawing
the interactive object being navigated by the user or in the creating the final point-
array–based object:
36: return &arrow;
This completes the introduction of the Box and Arrow template objects supported by
the Graphics Editor.
Next Steps
With the Box and Arrow objects comfortably at rest in the Graphics Editor project,
you have successfully added a total of four objects. Because they are all point-
array–based objects they are able to borrow heavily from a core structure.
In Chapter 23, the Arc object is added to the list of objects supported by the editor.
Because the representation of an arc is not similar to the GXLine objects seen so far,
there will be differences to point out. However, the basic event-driven creation,
moving, and scaling processes introduced for the Latex Line and inherited by the
Pencil, Box, and Arrow objects will remain the same.
22
In this chapter
• Creating an Arc Object
• Drawing and Erasing an Arc Object
Arc Object
By choosing the Arc creation icon seen in Figure 23.1, the user begins the process of
creating an Arc object.
Figure 23.1
Choosing the Arc cre-
ation icon begins the
process of creating an
Arc object.
The interactive process of creating an Arc object is similar to creating the Box or
Arrow objects discussed in Chapter 22, “Object Templates.”
Specifically, a ButtonPress event begins the creation process and a ButtonRelease
event ends it. The event point corresponding to the ButtonPress assigns the location
of the upper-left corner of the bounding box that is to contain the Arc object,
whereas the event point for the ButtonRelease determines the lower-right corner of
this box.
This chapter presents the internal methods and supporting functions defined for the
Arc object. These routines control all aspects of managing the different features of
the object.
Note The code listings introduced in this chapter are targeted for placement in the
gxArc.c source file. Additionally, functions presented in the listings that are not
defined static should have a corresponding prototype placed in the gxProtos.h
header file.
422 Part V Adding Objects to the Editor
assigned the Arc icon the gx_arc function for invocation when selected by the user.
This function is defined in Listing 23.1 and serves as the entry point to the creation
process of the Arc object.
41: break;
42: case MotionNotify:
43: /*
44: * update the XArc structure based on the
45: * new location of the mouse pointer
46: */
47: if( rubber_arc ) {
48: (void)update_arc( event, rubber_arc );
49:
50: /*
51: * redraw the rubberbanding arc
52: */
53: if( rubber_arc->width && rubber_arc->height ) {
54: XDrawArc( XtDisplay(GxDrawArea),
55: XtWindow(GxDrawArea), rubberGC,
56: rubber_arc->x, rubber_arc->y,
57: rubber_arc->width, rubber_arc->height,
58: rubber_arc->angle1, rubber_arc->angle2);
59: }
60: }
61: break;
39: rubber_arc = NULL;
62: }
63: }
64: }
The function begins by testing for a valid event reference passed by the caller:
5: if( event == NULL ) {
6: rubber_arc = NULL;
Remember that a NULL event enables the function to reset and cancel any pending
creation process.
If a valid event has been sent, the gx_arc function tests the presence of a valid Arc
reference stored in the rubber_arc variable:
8: if( rubber_arc &&
9: rubber_arc->width > 0 &&
10: rubber_arc->height > 0) {
If a valid Arc definition exists, the function erases the previously drawn interactive 23
Arc object pending the update of the rubber_arc structure:
The gx_arc function then switches, based on the type of event sent, and takes the
appropriate action:
18: switch( event->type ) {
When the user presses the mouse button, the creation process begins and the
rubber_arc structure is assigned its initial values:
The movement of the cursor further updates the rubber_arc structure to reflect the
user’s navigation:
48: (void)update_arc( event, rubber_arc );
However, when the mouse button is released, an actual Arc object is created:
35: create_arc( NULL, rubber_arc );
Because the structure of the gx_arc function has been thoroughly established with
the introduction of similar functions previously, I won’t go into great detail.
Interesting, however, are the functions unique to the creation of the Arc object,
specifically update_arc, introduced in Listing 23.2, and create_arc found in
Listing 23.3.
the update_arc function makes the necessary initializations for processing subse-
quent events correctly:
10: arc.angle1 = 0*64;
11: arc.angle2 = 360*64;
13: fix_x = event->xbutton.x;
14: fix_y = event->xbutton.y;
Thinking ahead to consecutive calls to the update_arc function during the creation
process, the deltas (or radii) for the x and y-axis are calculated from the fixed points
and the current event point:
17: rx = event->xbutton.x - fix_x;
18: ry = event->xbutton.y - fix_y;
From the x and y-axis radius values rx and ry, the point defining the corners of the
bounding box can be found:
20: x1 = fix_x + rx;
21: x2 = fix_x - rx;
22: y1 = fix_y + ry;
23: y2 = fix_y - ry;
Using these points, you assign the smaller of the pairs as the x and y origin in the
XArc structure 23
25: arc.x = min(x1, x2);
26: arc.y = min(y1, y2);
When the interactive mode of the Arc creation yields the object desired by the user,
she releases the mouse button and ends the creation process. When this happens,
the actual Arc object is created by a call to the create_arc function introduced in
Listing 23.3.
As with the create_line function exhausted in earlier chapters, the create_arc func-
tion accounts for being called with the common object portion already created in
order to support the restore function introduced later in this chapter.
Barring the presence of a GXObj reference, the create_arc function must request one:
7: obj = gx_create_obj();
Arc Object Chapter 23 427
The function creates a new reference to an XArc structure and assigns it to the data
field of the GXObj:
10: arc_data = (XArc *)XtNew( XArc );
11: obj->data = (void *)arc_data;
The values of the XArc defined by the user during creation are transferred to the new
reference:
13: arc_data->width = arc->width;
14: arc_data->height = arc->height;
and
16: arc_data->x = arc->x;
17: arc_data->y = arc->y;
and
19: arc_data->angle1 = arc->angle1;
20: arc_data->angle2 = arc->angle2;
Finally, the function is ready to assign the methods to the GXObj that will properly
manage and control the Arc data portion of this object (lines 22–32).
The last thing the create_arc function does is ensure that the newly created object is
appended to the list of objects currently managed by the editor application:
34: gx_add_obj( obj );
As was true with the Line objects in Chapters 20–22, for an object to be visible on
the canvas it must be drawn. The following section introduces the methods that draw
and erase the Arc object.
continues
428 Part V Adding Objects to the Editor
True for any of the objects added to the editor thus far, if the object is visible it can
be selected by the user. The following section introduces the method for deter-
mining whether an event successfully selected an Arc object.
continues
430 Part V Adding Objects to the Editor
Following steps to ensure that the allocation succeeds for the handles, a for loop is
entered to initialize the width and height fields:
19: gx_arc->handles[i].width = HNDL_SIZE;
20: gx_arc->handles[i].height = HNDL_SIZE;
Finally, a process identical to assigning the positions of the Line object handles intro-
duced in Chapter 20, “Latex Line Object,” in the section “Selecting and Deselecting
a Line Object,” page 392, is used to place the arc bounding handles. One difference,
however, is that the XArc structure stored in the object inherently provides the
bounds of the object, eliminating the need to calculate them.
A selected object is a candidate for deselecting; therefore the arc_deselect method is
defined in Listing 23.7 to support this requirement.
The deselect method ensures that the handles exist for this object and takes steps to
remove them from the screen and release the memory they occupy, resetting the
object fields to reflect their defunct state:
8: arc->handles = NULL;
9: arc->num_handles = 0;
Arc Object Chapter 23 431
Because the user has selected an Arc object for the purpose of manipulating it in
some way, the following sections present the arc_move and arc_scale functions.
continues
432 Part V Adding Objects to the Editor
As you might recall, the move and scale actions are assigned and invoked from the
process_event function introduced in Chapter 16, “Object Manipulation,” in the
section “Processing User Navigation of Objects,” page 334.
The arc_move function follows the same form as the line_move introduced in
Chapter 20, in the section “Moving a Line Object,” page 395. The only difference to
note is that by simply updating the x and y values of the XArc structure, the reposi-
tioning of the Arc object is accomplished:
33: arc_data->x += (event->xbutton.x - x);
34: arc_data->y += (event->xbutton.y - y);
The following section introduces the arc_scale method assigned to Arc objects.
The steps required to scale the Arc object follow those required to scale a Line object
as discussed in Chapter 20, in the section “Scaling a Line Object,” page 398.
23
Note For a review of the calculations preformed by the apply_delta function as well
as the declaration and assignment of the FixedX and FixedY variables, see
Chapter 11, “Graphic Transformations,” in the section “Scaling an Arc,” page 243,
and Listing 11.7 “The apply_delta Function for Arcs,” page 245.
434 Part V Adding Objects to the Editor
The last elements required for satisfying the creation of the Arc object is to add
support for saving and restoring the Arc data.
Arc Object Chapter 23 435
writing the tagged line to indicate the format of the data to follow
5: fprintf( fp, “ARC [x y width height angle1 angle2]\n”);
are the steps required to save the data associated with the Arc object.
The steps to restore the data to the editor are introduced in Listing 23.12.
The data line is the only thing of import to the gxArcLoad function. Defining a local
XArc structure and loading the saved data into the appropriate fields
retrieves the previously saved data and creates an object from it.
This completes the all aspects of introducing the Arc object to the Graphics Editor.
Next Steps
One object remains for introduction, integration, and investigation: namely, the Text
object.
The Text object by far is the most complex of all the objects supported by the
Graphics Editor. However, upon successful completion of the following chapter, you
will have a functional editor capable of drawing, moving, scaling, saving, and restor-
ing a variety of graphics objects.
In this chapter
• Creating a Text Object
• Drawing and Erasing a Text Object
Figure 24.1
The Text creation
icon begins the Text
object creation process.
The creation process for the Text object begins with prompting the user for a text
string from which to create the Text object.
With the string successfully entered by the user, a value reflecting the interactive text
object follows the movement of the cursor. The user places the text on the canvas
window by pressing the mouse button. The resulting ButtonPress event location is
used as the origin of the new Text object.
This chapter presents the internal methods and supporting functions defined for the
Text object. These routines control all aspects of managing the different features of
the object.
Note The code listings introduced in this chapter are targeted for placement in the
gxText.c source file. Additionally, functions presented in the listings that are not
defined static should have a corresponding prototype placed in the gxProtos.h
header file.
438 Part V Adding Objects to the Editor
assigned the Text icon the gx_text function for invocation when selected by the user.
This function is defined in Listing 24.1, and it serves as the entry point to the inter-
active creation process of the Text object.
At the specification of a NULL event reference, the gx_text function initializes local
variables to a known state both in the gx_text function and the place_creation_text
function:
5: if( event == NULL ) {
6: creation_text = NULL;
7: place_creation_text( NULL, NULL );
Vector Text Object Chapter 24 439
Following this, the user is prompted for input with a call to the get_creation_text
function:
12: creation_text = get_creation_text();
If a string is successfully gained from the user, the cursor mode is updated to reflect
the state of the application and
15: set_cursor( TEXT_MODE );
the function finishes for the current iteration. The function is called again when the
user moves the cursor into the canvas window and begins to generate MotionNotify
events. Because the create_text variable is static, its value is available for the succes-
sive calls:
22: if( creation_text ) {
With the presence of the creation_text string value, the event points are adjusted to
account for the hotspot of the cursor
24: event->xbutton.y -= 10;
25: event->xbutton.x += 10;
and then used to locate the interactive text string for the placement:
27: place_creation_text( event, &creation_text );
continues
440 Part V Adding Objects to the Editor
The place_creation_text function determines its actions based on the current event
type.
Notice that the parameters required for the place_creation_text function include
not only a reference to the event being processed but also a pointer to the character
pointer containing the creation_text:
2: place_creation_text( XEvent *event, char **_text )
By passing the address to the creation text, its contents can be cleared when the user
completes the placement process as indicated by a ButtonPress event.
Beyond validating the presence of valid event and copying the text variable to a local
reference in lines 7–10, the place_creation_text function determines whether the
rubber_text variable was previously initialized. A valid rubber_text value indicates
that the function must erase the current interactive text string:
12: if( rubber_text ) {
13: GXDrawText( rubber_text, rubberGC );
14: }
Vector Text Object Chapter 24 441
The function is now ready to determine the event type and execute the appropriate
action body:
16: switch( event->type ) {
must ensure that a valid rubber_text value is assigned as well as current text string
18: if( rubber_text && text ) {
After updating the canvas window with a call to gx_refresh on line 20, the function
frees the memory associated with the rubber_text variable used during the interac-
tive creation:
22: freeGXText( rubber_text );
Several functions referenced during the course of this discussion have yet to be intro-
duced. Review first the function to retrieve a string by prompting the user, intro-
duced in Listing 24.3.
continues
442 Part V Adding Objects to the Editor
The GxDrawText function is responsible for traversing the characters comprising the
Text object
This function also invokes the XDrawLines Graphic Primitive to draw that portion of
the character
13: if( num_pts > 0 ) {
14: XDrawLines( XtDisplay(GxDrawArea),
15: XtWindow(GxDrawArea), gc,
16: text->vpts[c][nsegs], num_pts,
17: CoordModeOrigin );
18: }
Notice the last two parameters passed to the create_gxtext function, plain_simplex
and plain_simplex_p.
Vector Text Object Chapter 24 445
The plain_simplex array is a vector font set. More literally, it contains the defini-
tions of all characters that can be represented by the font set. The plain_simplex_p
is a control array containing the number of points contained in each segment of the
corresponding character found in the plain_simplex array.
The definition of these arrays is shown in Listing 24.6; however, to begin under-
standing it, refer to the Excursion “An Introduction to Vector Fonts,” in Chapter 15,
in the section “Text Object Data Structure,” page 307.
continues
446 Part V Adding Objects to the Editor
The plain_simplex array contains the character definitions for all characters that can
be represented by this vector font set.
Note You’ve probably noticed that the names of the characters in the plain_simplex
array have no relationship to the actual characters defined.
The piece that makes these arrays meaningful is the vector_chars.h header
file. This file defines several vector font sets available to the Graphics Editor pro-
ject. For simplicity, however, only the plain_simplex font is employed.
The vector_chars.h file contains hundreds of character definitions. These char-
acters, when constructed, were numbered sequentially. Later, as I cleaned up the
definitions, removing those not referenced and grouping others into sets, it was
necessary to repeat some characters representing simple punctuation and, in
the end, the sets had a sort of mismatched appearance.
Appendix C, “Additional Vector Font Sets and vector_chars.h,” presents all the vec-
tor fonts sets available to the Graphics Editor project as well as the contents of the
vector_chars.h file. Because the file contains the definitions of all characters from all
font sets (which consist of multiple segment definitions for each character), the file is
literally thousands of lines long. Therefore, you probably do not want to type it in.
In this case, I encourage to you reference the pertinent files on the CD-ROM
accompanying this book.
Note Chapter 28, “Extending the Graphics Editor,” outlines ways to incorporate all
available vector font sets into the Graphics Editor application concurrently.
A sample of the character for the exclamation point is presented in Listing 24.7.
Vector Text Object Chapter 24 447
A second example is provided in Listing 24.8, which contains the definition of the
double quote character.
The relationship between a font set such as plain_simplex (definition of all charac-
ters) and a single character contained in the array (defined by a series of segments),
and each of the segments comprising a character (arrays of XPoints), should be clear
from these examples.
Further, the plain_simplex_p array serves the purpose of providing the number of
points available for each segment defining a character. 24
With an understanding of the structure of a vector font set well in hand, turn your
attention to the create_gxtext function introduced in Listing 24.9.
448 Part V Adding Objects to the Editor
followed by initializing the static fields of the GXText structure. The x and y fields
track the origin of the Text object and are applied to all points of the font set as they
are loaded into the GXText structure. The initial value for these fields will be the
coordinates of the event point:
10: text_data->x = x;
11: text_data->y = y;
The dx and dy fields manage the scale factors for the x and y-axis applied to this Text
object since creation. Their usefulness will be demonstrated shortly, but upon cre-
ation no scale deltas have been requested, so their initial values are 0:
13: text_data->dx = 0;
14: text_data->dy = 0;
A copy of the character string comprising the new object is stored in the GXText
structure along with the string’s width:
16: text_data->text = XtNewString( text );
17: text_data->len = strlen( text );
Finally, to support future expansion of dynamically specifying the font set to apply
for individual Text objects, the font particular to this object is assigned to the GXText
structure. Also assigned is the control array which tracks the number of points for
the segments of the various characters understood by the font set:
19: text_data->font = fnt;
20: text_data->fontp = fntp;
The function then creates space to store the vector characters for the text string
comprising the Text object
22: text_data->vpts = (XPoint ***)
23: XtMalloc(sizeof(XPoint **) * text_data->len);
Because the first character understood by any font set is the space, all characters are
subtracted from the decimal value of the space to ensure the index into the font set is
correctly aligned for the current character:
26: chr = *text - ‘ ‘; 24
450 Part V Adding Objects to the Editor
Using this character, the number of segments needed to define it using the font set is
determined with a while loop:
28: nsegs = 0;
29: while( fnt[chr][nsegs] != NULL ) {
30: nsegs++;
31: }
Finally, the control array indicating the number of points for each segment is
employed to allocate sufficient space to store the points after they are transposed to
be relative to the origin:
36: for( s = 0; s < nsegs; s++ ) {
37: num_pts = fntp[chr][s];
38:
39: text_data->vpts[c][s] = (XPoint *)
40: XtMalloc( sizeof( XPoint ) * num_pts );
With the proper space reserved for the string defining the new GXText structure, the
reset_pts function is used to transpose the points defining the segments of the char-
acters in the text string:
44: reset_pts( text_data,
45: text_data->x, text_data->y,
46: text_data->dx, text_data->dy );
Before presenting the reset_pts function, note the number of allocations performed
for each character of the string assigned to the GXText structure.
The GXText structure is a memory-intensive construct because it requires memory
allocation for the transformed points within the various segments. The actual num-
ber of allocations depends on the number of segments, which is influenced by the
complexity of the font.
Therefore it is important that proper management be applied to this memory and
that it be returned to the heap when no longer used by the application. For this rea-
son, the freeGXText function found in Listing 24.10 has been defined.
The freeGXText function effectively works backward to perform frees where the
create_gxtext performs allocations.
Look now at the reset_pts function used by the create_gxtext function and defined
in Listing 24.11.
The reset_pts function is responsible for first applying the x, y origin specified in
the parameter list to the points defined in the font set and then applying the dx and
dy scale factors to the transformed points.
The function reset_font_pts shown in Listing 24.12 accomplishes the task of trans-
forming the points in the font set to the specified origin.
continues
452 Part V Adding Objects to the Editor
Following the declaration of the variables that will control the multiple levels of
looping necessary to traverse the characters, segments, and points defining the
object, the function resets the x, y origin fields maintained in the GXText structure:
13: text->x = x;
14: text->y = y;
The mechanism for using each character of the text string contained in the GXText
structure to traverse the segments defined by the font set is consistent throughout
the support functions used by the Text object. Therefore focus here is on the pur-
pose of the function.
Vector Text Object Chapter 24 453
In other words, lines 16–22 are identical to those used previously to access the points
which comprise the current character’s definition by the font set.
Unique functionality begins when the vpts structure created at the beginning of the
create_gxtext function is assigned the transformed points taken from the font set.
29: text->vpts[c][nsegs][i].x =
30: (text->font[chr][nsegs][i].x + c_off) +
31: orig_x;
32: text->vpts[c][nsegs][i].y =
33: text->font[chr][nsegs][i].y + y;
consists of applying a character offset c_off and the value of the x component of the
origin to determine the value of the corresponding element in the vpts array,
29: text->vpts[c][nsegs][i].x =
The next step taken by the reset_font_pts function is to track the furthest x point
for this character:
35: maxx = max( maxx, text->vpts[c][nsegs][i].x );
The maxx value will be important for ensuring that subsequent characters are
properly spaced to the right of the current one.
When all the points for all the segments for the current character are transformed
and placed in the appropriate vpts element, the while loop ends
38: }
Finally, the origin of the Text object is advanced by the SPC macro
47: orig_x = SPC(maxx);
and the for loop advances to the next character for the text string of this object. 24
The transformation begins for the next character.
The macro definition should be placed at the beginning of the gxText.c file:
#define SPC(w) (w + 3) /* just a wee gap between chars */
454 Part V Adding Objects to the Editor
The function next_char_min used to calculate the offset of the character to follow is
introduced in Listing 24.13.
The next_char_min function looks at the points defining a character within the font
set and determines the minimum x point for the character cell:
13: minx = min(text->font[(int)_c][nsegs][i].x, minx);
Of course, the points for all segments defining the character are considered
15: nsegs++;
to determine the amount to advance the transformation of points placed into the
vpts array to avoid character overlap.
Two special cases are considered by the next_char_min function. First is the occur-
rence of a space character
20: if( c == ‘ ‘ )
because a space should be the same width despite the character following. And,
second, the formation of the character definitions centered on origin resulting
in a negative minimum value:
22: else if( minx < 0 )
Vector Text Object Chapter 24 455
Note Figure 24.2 is a graphical depiction of character definitions being centered on the
origin and clearly demonstrates the need to account for a negative minimum
value.
Figure 24.2
0, –12
The coordinate
relationships
seg0
for the points
defining each
character of a –5, 2
seg2
5, 2
vector font set. –15 15
seg1
–8, 9 –8, 9
15
The second function employed by the reset_pts function in Listing 24.11 is the
apply_scale function
continues
456 Part V Adding Objects to the Editor
The apply_scale function is critical for ensuring that the integrity of the Text object
is maintained during the many calculations preformed continually on the points con-
tained in the vpts array in the GXText structure.
The role it serves is to enable the user to scale the Text object and, unlike other
objects in the editor, have the Text object revert periodically to the original points
transformed from the character definitions in the font set (accomplished by the
reset_font_pts function). After being reset to the original font points (transformed),
the retention of the dx and dy values (scale deltas for the x and y-axis) is reapplied
without the user even knowing.
The integrity (quality) of the points defining the Text object displayed on the canvas
is critical because many of the line segments within a character definition for the
more complex or fancier vector font sets are quite small. An error as slight as one
pixel will have a detrimental effect on the appearance of the vector text.
The apply_scale function traverses the characters to access the segment and point
arrays defined by the font set, much the same way as other functions introduced
already. Important to the purpose of the apply_scale function, however, is the appli-
cation of the dx and dy values proportionally to the x and y values stored in the vpts
array:
26: text->vpts[c][nsegs][i].x += (int)(dx *
27: ((float)(text->vpts[c][nsegs][i].x -
28: minx)/(float)width)) + x;
Vector Text Object Chapter 24 457
29:
30: text->vpts[c][nsegs][i].y += (int)(dy *
31: ((float)(text->vpts[c][nsegs][i].y -
32: miny)/(float)height)) + y;
The term proportionally refers to the fact that the application of the scale factor is
weighted based on the proximity of the point to the extent of the object.
For this reason, the extents of the object are determined early in the function
10: get_extents( text, &minx, &miny, &maxx, &maxy );
and from the extents the width and height values can be determined.
The get_extents function is found in Listing 24.15.
The get_extents function is, perhaps, the most straightforward of any function
introduced so far.
24
458 Part V Adding Objects to the Editor
It, too, traverses the segments defined by the font set to represent each character of
the GXText text field to obtain the bounds of the points contained in the segment:
17: get_bounds( text->vpts[c][nsegs],
18: text->fontp[chr][nsegs],
19: &x1, &y1, &x2, &y2 );
The bounds of each segment are used to keep track of the minimum and maximum
points used to represent the object:
21: *minx = min( x1, *minx );
22: *miny = min( y1, *miny );
23: *maxx = max( x2, *maxx );
24: *maxy = max( y2, *maxy );
The get_bounds function acts on a single array of XPoints and thus is suitable for use
by the Line object as well.
For an array of points, the minimum and maximum points contained in the array are
returned to the calling function:
9: for( i = 0; i < num_pts; i++ ) {
10: *x1 = min( pts[i].x, *x1 );
11: *y1 = min( pts[i].y, *y1 );
13: *x2 = max( pts[i].x, *x2 );
14: *y2 = max( pts[i].y, *y2 );
15: }
Vector Text Object Chapter 24 459
We started our winding way to this function by waiting for the user to press the
mouse button and position the creation_text, shown in Listing 24.2. When she
finally does, the temporary GXText structure referenced by rubber_text is used to
create a Text object:
19: create_text( NULL, rubber_text );
accounts for an invocation of the procedure with the caller having already created
the common object to contain the text data. This supports the restoring of the object
from a saved file, as is seen later in the chapter.
During interactive creation, however, the function create_text is called with a NULL
24
value as the first parameter forcing the function to create the common object portion
through a call to gx_create_obj.
460 Part V Adding Objects to the Editor
This is introduced in Chapter 17, in the section “Common Object Creation,” page 343:
9: obj = gx_create_obj();
The creation routine then copies the GXText structure defining the object
12: obj->data = copy_gxtext( text, 0, 0 );
following which the object methods and manipulation functions for controlling the
object-specific data structure in lines 14–25 are assigned
14: obj->draw = text_draw;
15: obj->erase = text_erase;
16: obj->find = text_find;
17: obj->move = text_move;
18: obj->scale = text_scale;
19:
20:
21: obj->copy = text_copy;
22: obj->select = text_select;
23: obj->deselect = text_deselect;
24:
25: obj->save = text_save;
after which the object is added to the list of objects managed by the editor appli-
cation:
27: gx_add_obj( obj );
Listing 24.18 shows the contents of the copy_gxtext function used by the
create_text function.
20:
21: num_pts = text_data->fontp[chr][nsegs];
22:
23: for( i = 0; i < num_pts; i++ ) {
24:
25: text_data->vpts[c][nsegs][i].x =
26: text->vpts[c][nsegs][i].x + off_x;
27: text_data->vpts[c][nsegs][i].y =
28: text->vpts[c][nsegs][i].y + off_y;
29: }
30: nsegs++;
31: }
32: }
33:
34: return text_data;
35: }
The copy_gxtext function begins by creating a new GXText structure with the origin
of the Text structure incremented by the offset specified for the x and y components:
7: text_data =
8: create_gxtext( text->text, text->x + off_x, text->y + off_y,
9: text->font, text->fontp );
The function then traverses all points contained in the vpts array and increments
them accordingly as well
25: text_data->vpts[c][nsegs][i].x =
26: text->vpts[c][nsegs][i].x + off_x;
27: text_data->vpts[c][nsegs][i].y =
28: text->vpts[c][nsegs][i].y + off_y;
finally returning the copy of the GXText structure with the offset values:
34: return text_data;
The create_text function specifies 0 for the offset value of the copy created:
12: obj->data = copy_gxtext( text, 0, 0 );
However, this same function will be used in the definition of the text_copy method.
A created object is no longer drawn using the interactive rubber-banding GC, but by
invoking the draw method of the object.
The following section introduces the methods that draw and erase the Text object
from the drawing area canvas.
24
Drawing and Erasing a Text Object
The act of drawing or erasing an object in the Graphics Editor differs only in the
treatment of the tile field of the GC created for the request.
462 Part V Adding Objects to the Editor
Notice in Listing 24.19, lines 17 and 25, that the draw and erase methods differ only
in the value passed for the tile flag required as the second parameter to the
draw_erase function. (Note as well that the erase method is responsible for
removing the object’s handles if present on the screen).
The draw_erase function begins by creating a Graphics Context for use in the
GXDrawText function:
Important to the creation of the Graphics Context is the specification of the tile flag to
the gx_allocate_gc function introduced in Chapter 17, in the section “Creating a Graphics
Context,” page 347.
Note If you recall, this flag indicated whether the background Pixmap of the
GxDrawArea was assigned as the value to the tile field of the GC created.
An appropriately created GC created for the current draw or erase action, the
draw_erase function can request that the object be updated in the canvas window:
6: GXDrawText( (GXTextPtr)text->data, gc );
Because the X Server will attempt to cache the GC for future requests, it is important
to specify our complete use of it with a call to XtReleaseGC:
8: XtReleaseGC( GxDrawArea, gc );
With the object visible on the screen, it is now eligible for manipulation by the user.
However, before it can be moved, scaled, or deleted, it must be selected by the user.
The next section introduces the Text object find method used to determine whether
an event has successfully located the object on the drawing area.
continues
464 Part V Adding Objects to the Editor
The text_find method parses the GXText structure to reduce it to arrays of points
compatible with the near_segment function found in Chapter 10, in the section
“Calculating Point and Line Intersections,” page 211.
If the event point specified to the text_find method intersects any of the many seg-
ments composing the Text object, the loops are interrupted and the True value is
returned to the calling function.
The text_find method serves two purposes in the management of the Text object.
One purpose is to enable the user to select the object for manipulation. A second
purpose is to determine whether the manipulation requested by the user is the move
action.
The following section introduces the step stemming from the text_find method
resulting in the selecting or deselecting of the object.
The function gx_draw_handles was introduced in Chapter 16, section “Managing Object Handles,”
page 327.
The process of creating the handles for the Text object begins with a call to
text_bounding_handles found in Listing 24.22.
Vector Text Object Chapter 24 465
continues
466 Part V Adding Objects to the Editor
After testing for a failure of the allocation routine, the widths of the handles for the
Text object are assigned
and then the extents of the Text object are obtained in order to determine the place-
ment of the handles:
22: get_extents(text, &minx, &miny, &maxx, &maxy);
23: width = maxx - minx; height = maxy - miny;
Last comes the tedious task of placing each of the eight handles at the appropriate
location around the object in lines 25–59.
Note The position of the handles runs clockwise, starting with handles[0] located in
the upper-right corner of the object’s bounds.
The Text object’s deselect method introduced in Listing 24.23 shows the steps nec-
essary to remove the handles, indicating the active state of the Text object.
Vector Text Object Chapter 24 467
The next section provides the functionality of interactively moving a Text object.
continues
468 Part V Adding Objects to the Editor
The text_move function uses the static points x and y to track consecutive calls to
the function for a single move operation:
5: if( x && y ) {
If x and y are non-zero, the text_move function knows to erase a previously draw
rubber-banding copy of the Text object:
6: GXDrawText( (GXTextPtr)text->data, rubberGC );
the GXText structure with the updated points is drawn using the rubber-banding GC
19: GXDrawText( text->data, rubberGC );
If a NULL event reference is passed to the function, the x and y values are reset to 0
and the current move action is cancelled:
23: x = 0;
24: y = 0;
Vector Text Object Chapter 24 469
Listing 24.25 shows the definition of the apply_delta function used by the
text_move method.
The apply_delta function is a pleasant reprieve from the complex support function
introduced in this chapter. By now, you should be comfortable with traversing the
GXText structure using the characters of the text field to index into the font set to
find the segments and access the points.
The apply_delta simply makes this traversal to access the points composing each
character and increments them by the delta values specified for the x and y compo-
nents accomplishing a move for the Text object.
The following section addresses the steps required to scale the Text object.
The text_scale method introduced in Listing 24.26 manages the scale action for the
Graphics Editor Text object.
The text_scale method of the Text object is responsible for managing the event ref-
erences passed to the function. Identical in structure to scale methods for previously
introduced objects, focus on the portion of this function that is unique to the Text
object:
16: calc_apply_scale( text->data,
17: event->xbutton.x - x, event->xbutton.y - y );
18: x = event->xbutton.x;
19: y = event->xbutton.y;
Listing 24.27 shows the calc_apply_scale function used by the text_scale method
to accomplish the scale request.
5: case 0:
6: apply_scale_top ( text, dx, dy );
7: apply_scale_left( text, dx, dy );
8: break;
9:
10: case 1:
11: apply_scale_top( text, dx, dy );
12: break;
13:
14: case 2:
15: apply_scale_top ( text, dx, dy );
16: apply_scale_right( text, dx, dy );
17: break;
18:
19: case 3:
20: apply_scale_right( text, dx, dy );
21: break;
22:
23: case 4:
24: apply_scale_right ( text, dx, dy );
25: apply_scale_bottom( text, dx, dy );
26: break;
27:
28: case 5:
29: apply_scale_bottom( text, dx, dy );
30: break;
31:
32: case 6:
33: apply_scale_bottom( text, dx, dy );
34: apply_scale_left ( text, dx, dy );
35: break;
36:
37: case 7:
38: apply_scale_left( text, dx, dy );
39: break;
40:
41: default:
42: setStatus( “TEXT: The end is nigh!” );
43: }
44:
45: /* resetting to the original points ensures we don’t */
46: /* compound rounding errors for points that have */
47: /* already been scaled */
48: reset_pts( text, text->x, text->y, text->dx, text->dy );
49: }
The function switches on the GxActiveHandle value set at the start of the scale action
4: switch( GxActiveHandle ) {
24
to determine which direction the scale action should be applied. Then one of the
several apply_scale_<direction> functions found in Listing 24.28 is invoked to
adjust the x, y, dx, or dy fields of the GXText structure appropriate for the direction
the object is being scaled.
472 Part V Adding Objects to the Editor
Following the adjustment of the necessary fields within the GXText structure, the
text_scale function invokes the reset_pts function to do the work:
48: reset_pts( text, text->x, text->y, text->dx, text->dy );
The call to reset_pts ensures that no rounding errors from previous actions have
adversely affected the quality of the Text object being displayed.
The critical support functions required by the text_copy feature introduced in the
next section have already been seen, but let’s go there anyway.
By first deselecting the active Text object, the handles associated with it are removed
from the screen
5: (*obj->deselect)( obj );
following which the copy_gxtext function can be employed, specifying the value of
OFFSET to prevent the copy from exactly overlaying the original object:
The following section presents the last area of functionality required by the Text
object—saving and restoring the object-specific data.
The text_save function extracts the GXText structure from the common object
structure
3: GXTextPtr text = (GXTextPtr)obj->data;
writes a simple tag line showing the format of the data to follow
5: fprintf( fp, “TEXT [ str x y ]\n” );
which consists of simply the location of the object and text string associated with it.
Notice the newline character inserted after the %s format token in the data line.
Because it is possible for the user to embed spaces in the text value, we must ensure
that the restore function can distinguish completely the text string from the location.
Listing 24.31 shows how the data written by the text_save method is retrieved to
the editor.
The GxLoadText function reads first the string using the C fgets function reads until
it finds the newline character written by the text_save method
8: fgets( text, 256, fp );
Vector Text Object Chapter 24 475
and then strips the newline character from the string because it cannot be repre-
sented by a vector font set:
9: if( (ptr = strchr( text, ‘\n’ )) != NULL ) {
10: *ptr = ‘\0’;
11: }
the restore function has the necessary components to create a GXText structure
14: data = create_gxtext( text, x, y,
15: plain_simplex, plain_simplex_p );
which it passes to the create_text function to create the Text object and add it to
the list managed by the editor application:
17: create_text( obj, data );
With the completion of the discussion concerning saving and restoring the Text
object, you have completed the addition of all objects supported by the editor appli-
cation. Congratulations!
Next Steps
Chapter 25, “Introduction to PostScript,” will introduce the PostScript program-
ming language as we work our way closer to the next goal of adding print capability
to the Graphics Editor application.
24
Part VI
Introduction to PostScript
The usefulness of the Graphics Editor is significantly advanced by the addition of
print capability. There are several ways to approach the task of printing; therefore,
I have selected a simple approach that easily allows for future enhancements.
To accomplish the Graphics Editor print function, the PostScript page description
language will be used. PostScript enables us to communicate with a PostScript-
compatible printer to effectively describe the page to be printed.
The following sections introduce the PostScript language that is used in future
chapters to print the graphics objects drawn by the Graphics Editor.
Note This chapter covers only a portion of PostScript Level 1 and Level 2, which are
the earlier versions of PostScript created by Adobe. The current version is Level 3.
PostScript
PostScript is a programming language that describes the appearance of a printed page.
It was developed by Adobe in 1985 and has become an industry standard for printing
and imaging.
All major printer manufacturers make printers that contain or can be loaded with
PostScript interpreter software. A PostScript file can generally be identified by a ps
suffix. Like C language source code files, PostScript files are plain text and can be
viewed using your favorite editor.
PostScript is an interpreted, stack-oriented language for describing—in a device-
independent fashion—the way in which pages can be composed of characters,
shapes, and digitized images in black and white, grayscale, or color.
480 Part VI Adding a Print Driver
Concerning the fields of text and graphics, PostScript is the most widely used printer
controller in the industry. It gives computer users total control over text, graphics,
color-separations, and halftones.
The page description language recognizes a page the user creates as a unit and con-
verts the elements of the page into control data for the output device. The printing
engine receives the control data in its own format and resolution. For the printed
page the resolution can be up to 300 dots per inch (dpi).
PostScript language programs are used for communication between a software prod-
uct such as the Graphics Editor and a printing system, and they enable the user to
mix text, graphics, and images from various sources.
The following section introduces the PostScript language by drawing on what you
already know about the C programming language.
Learning PostScript
The PostScript page description language serves as an interface between an applica-
tion program and a printing system (usually a printer). A C program can generate
the PostScript language code needed by a printer for rendering the pages described.
Using the C programming language classic “Hello World” example,
main()
{
printf(“hello world\n”);
}
to print the character string hello world to the screen, we will begin our instruction
in PostScript.
The translation of this C program into PostScript would look like the following:
1: %!
2: /Courier findfont 10 scalefont setfont
3: 0 100 moveto
4: (hello world) show
5: 0 88 moveto
6: showpage
At first glance, you don’t see similarities between the C program sample and its
PostScript translation. Because PostScript is a graphical programming language, you
must specify how the character string will be printed. This requires specifying the
typeface and point size:
/Courier findfont 10 scalefont setfont
Introduction to PostScript Chapter 25 481
Courier is the font that is specified with the findfont command, and 10 is the point
size PostScript is told to scale the font to with the scalefont command. The last
keyword, setfont, instructs the PostScript interpreter to make the font active.
25
You next must specify the origin for placing the string:
0 100 moveto
This instruction moves 0 points to the right and 100 points up from the lower-left
corner of the paper sheet.
Next, you designate the line feed by a vertical motion downward from the previous
location:
0 88 moveto
Finally, you instruct the interpreter to display the page that’s been described:
showpage
Notice the order of the sample PostScript commands relative to the parameters
required by the command. As with the moveto command
0 88 moveto
the values 0 and 88 are the x and y coordinates needed by the command, but they
precede moveto. This syntax is necessary because the PostScript language is stack
based.
Stacks
A stack is a data structure implemented as a last in, first out (LIFO) list. On a stack,
the last item added to the structure is the first item removed.
Everything read by PostScript is placed (pushed) on a stack internal to the inter-
preter. When the interpreter reads a command or instruction requiring arguments,
the appropriate number of parameters are popped (removed) from the stack to satisfy
the command.
Several stacks are in a PostScript system, but only two are important for this discus-
sion: the operand stack and the dictionary stack.
The operand stack is a stack with arguments to procedures (or operators, using
PostScript vernacular) that are pushed prior to use. The dictionary stack is, as the
name implies, for dictionaries, and it provides storage for variables.
Looking again at the moveto command, envision a list where everything read by the
interpreter is placed. In the end, the list would appear as follows:
482 Part VI Adding a Print Driver
2. moveto
1. 88
0. 0
When the moveto command is reached, the elements placed on the list are removed
in the reverse order they were inserted.
The following section introduces the commands needed by the Graphics Editor for
printing the objects drawn on the canvas.
PostScript Commands
PostScript is a highly capable language. The following sections discuss many
PostScript operators used by the editor application and provide a description for
each.
Comments
Inserting comments into a PostScript file is done using the percent (%) character. As
with other languages, anything following on the same line as a comment token is
ignored by the interpreter.
The special comment %! token used as the first two characters of a PostScript pro-
gram is seen as a tag marking the file as PostScript code by many systems, including
the UNIX lpr command.
It is a good idea to start every PostScript document with the %! token, because it
ensures that every spooler and printer the document can encounter recognizes it as
PostScript code.
In addition to the information found in the comment specifiers, PostScript under-
stands numerous commands. The following section introduces PostScript conven-
tions for forming the commands used by the Graphics Editor for printing the canvas
window.
PostScript Programming
Programming in PostScript is quite easy. The fundamentals are that you push
operands onto the operand stack by naming them, and then you invoke the operands
to employ them.
The challenge in programming with PostScript is in knowing which operand to use,
and when.
Operators to draw and put text on the screen make up the bulk of PostScript
operands. A couple of operands, however, are used for maintaining the program
itself.
Introduction to PostScript Chapter 25 483
The first of these operators is the def operand, which is responsible for entering a
definition into the top-most dictionary on the dictionary stack.
25
Note A dictionary is a collection of name-value pairs. All named variables are stored in
dictionaries. In addition, all available operators are stored in dictionaries along
with their code. The dictionary stack is a stack of all currently open dictionaries.
When a program refers to some key, the interpreter wanders down the stack
looking for the first instance of that key in a dictionary. In this manner, names can
be associated with variables and a simple form of managing scope is imple-
mented. Conveniently, dictionaries can be given names and can be stored in
other dictionaries.
The top operand on the operand stack is the value, and the operand below the value
is the key and is typically a name.
For instance, defining the name x and assigning it a value of 5 in PostScript would be
done as follows:
/x 5 def
Notice the use of the forward slash before the x. This ensures that the name x, and
not any value it might represent, will be pushed onto the operand stack in any
defined dictionary stack.
The def operand is also used to define new operators. The value in this case is just a
procedure. The following code defines an operator foo, which adds its top-most two
operands and multiplies the result with the next operand on the stack:
/foo {add mul} def
Remember that operands, which return results, push them onto the stack so they can
be used later.
An important point to know when defining procedures is that the elements in a pro-
cedure are not evaluated until the procedure is invoked.
That means that in the procedure
{1 2 add 3 mul}
the actual names add and mul are stored in the procedure array. This is different from
an actual array in which the components are evaluated when the array is created.
This delayed evaluation of procedure components has two important effects. First,
the definition of an operator used in a procedure is the one that is in effect when the
procedure is run, not when it is defined. Second, because each operator must be
looked up each time the procedure is invoked, things can be a little slow.
484 Part VI Adding a Print Driver
Fortunately, PostScript provides the bind operator to replace each name in a proce-
dure object with its current definition:
/foo {add mul} bind def
If add or mul is redefined after defining foo, foo will have the same behavior as
before. Without the use of bind, the behavior of foo would change. Comfortable
with the basic syntax forming a PostScript operator, we will look at a summary of
some of the commands understood by PostScript.
Table 25.1 shows many of the commands provided with Level 1 PostScript.
closepath closepath Adds a line segment to the current path from the current
point to the first point in the path. This closes the path so 25
it can be filled.
charpath string bool Takes the given string and appends the path, which the
charpath characters define to the current path. The result can be
used as any other path for stroking, filling, or clipping. The
Boolean argument informs charpath what to do if the font
is not designed to be stroked. If the Boolean is true, the
path will be modified to be filled and clipped (but not
stroked). If the Boolean is false, the path will be suitable
to be stroked (but not filled or clipped).
curveto x1 y1 x2 y2 x3 y3 Draws a curve from the current point to the point
curveto (x3, y3) using points(x1, y1) and (x2, y2) as control points.
The curve is a Bézier cubic curve. In such a curve, the tan-
gent of the curve at the current point will be a line segment
running from the current point to (x1, y1), and the tangent
at (x3, y3) is the line running from (x3, y3) to (x2, y2).
def name value def Associates the name with a value in the dictionary at the
top of the dictionary stack. This operator essentially
defines names to have values in the dictionary and is
used to define variables and operators.
div num1 num2 div num3 Returns the result of dividing num1 by num2. The result is
always a real.
dup object dup object Pushes a second copy of the topmost object on the
object operand stack. If the object is a reference to an array,
string, or similar composite object, only the reference is
duplicated; both references will still refer to the same
object.
end end Pops the topmost dictionary from the dictionary stack. The
dictionary below it becomes the new current dictionary.
exch value1 value2 exch Simply exchanges the top two items on the operand
value2 value1 stack. It does not matter what the operands are.
fill fill Closes and fills the current path with the current color.
Any ink within the path is obliterated. Note that fill
blanks out the current path as if it had called newpath. If
you want the current path preserved, you should use
gsave and grestore to preserve the path.
findfont name findfont font Looks for the named font in the font dictionary. If it finds
the font, it pushes the font on the stack for later process-
ing. It signals an error if the font cannot be found.
continues
486 Part VI Adding a Print Driver
for initial increment Executes proc repeatedly. The first time proc is
limit proc for executed, it will be given initial as the top operand.
Each time it is executed after that, the top operand will be
incremented by increment. This process will continue
until the argument exceeds the limit.
grestore grestore Sets the current graphics state to the topmost graphics
state on the graphics state stack and pops that state off
the stack. This operator is usually used in conjunction with
gsave.
gsave gsave Pushes a copy of the current graphics state onto the
graphics state stack. The graphics state consists of
(among other things): the current font and the current
color.
if bool proc if Executes proc if bool is true.
ifelse bool proc1 proc2 Executes proc1 if bool is true, and proc2 otherwise.
ifelse
index value_n ... value_ Grabs the nth item off the operand stack (item is the
0 n index value_ one just under the index you push on the stack for the
n ... value_0 operator) and pushes it on top of the stack.
value_n
lineto x-coord y-coord Adds a line into the path. The line is from the current point
lineto to the point (x-coord y-coord). After the line is added to
the path, the current point is set to (x-coord y-coord). It
is an error to call lineto without having a current point.
moveto x-coord y-coord Moves the current point of the current path to the given
moveto point in user space. If a moveto operator immediately fol-
lows another moveto operator, the previous one is erased.
mul value1 value2 mul Multiplies the first two operands on the stack and pushes
product the result back onto the stack. The result is an integer if
both operands are integers and the product is not out of
range. If the product is too big or one of the operands is a
real, the result will be a real.
newpath newpath Clears the current path and prepares the system to start
a new current path. This operator should be called before
starting any new path, although some operators call it
implicitly.
pop value pop Removes the top-most item from the operand stack.
restore state restore Restores the total state of the PostScript system to the
state saved in state.
Introduction to PostScript Chapter 25 487
rlineto dx dy rlineto Adds a line into the path. The line is from the current point
to a point found by adding dx to the current x and dy to 25
the current y. After the line is added to the path, the cur-
rent point is set to the new point. It is an error to call
lineto without having a current point.
rmoveto dx dy rmoveto Moves the current point of the current path by adding dx
to the current x and dy to the current y.
rotate angle rotate Rotates the user space counter-clockwise by angle
degrees (negative angles rotate clockwise). The rotation
occurs around the current origin.
save save state Gathers the complete state of the PostScript system and
saves it in state. Possible errors resulting from this call
include limitcheck and stackoverflow.
scale sx sy scale Scales the user coordinates. Sx in the horizontal direction
and sy in the vertical direction will multiply all coordinates.
The origin will not be affected by this operation.
scalefont font size scalefont Takes the given font and scales it by the given scale
font factor. The resulting scaled font is pushed onto the stack.
A size of 1 produces the same sized characters as the
original font, 0.5 produces half-size characters, and so on.
setfont font setfont Sets the current font to be font. This font can be the
result of any font creation or modification operator. This
font is used in all subsequent character operations like
show.
setgray gray-value setgray Sets the current intensity of the ink to gray-value, which
must be a number from 0 (black) to 1 (white). This will
affect all markings stroked or filled onto the page. This
applies even to path components created before the call
to setgray, as long as they have not yet been stroked.
setlinewidth width setlinewidth Sets the width of all lines to be stroked to width, which
must be specified in points. A line width of 0 is possible
and is interpreted to be a hairline, as thin as can be ren-
dered on the given device.
show string show Draws the given string onto the page. The current graph-
ics state applies, so the current font, font size, gray value,
and current transformation matrix all apply. The current
point determines the location for the text. The current
point will specify the leftmost point of the baseline for the
text.
continues
488 Part VI Adding a Print Driver
The file description page created using the PostScript commands listed in Table 25.1
can be viewed on your computer screen using a PostScript viewer utility.
Note As described in the text, the prolog is the initial area of the PostScript file. The
prolog, when it is used, contains any procedures that are used in the body of the
document. These are surrounded by
%%BeginProlog
definitions go here
%%EndProlog
In keeping with the BeginProlog and EndProlog directives, the word prolog is
often used in lieu of what might be grammatically more correct to refer to as pro-
logue.
The terms, however, in the context of a PostScript document, are synonymous.
continues
490 Part VI Adding a Print Driver
Many of these comments are used in the page description file created by the
Graphics Editor in the following chapters.
Next Steps
The use of color in a PostScript file poses a certain challenge that is complicated by
trying to form a single page description file to work when interpreted by both color
and black-and-white printers.
The next chapter introduces a PostScript color conversion function that will enable
color images to be converted to grayscale during interpretation by black-and-white
printers.
In this chapter
• Determining a Printer’s Capability
• Defining Color Images for Black and
The color conversion function in Listing 26.1 can seem overwhelming if your intro-
duction to PostScript syntax began with Chapter 25, “Introduction to PostScript.”
However, this function is really not difficult to understand and will serve as an inter-
mediate introduction to the language before proceeding to implement print capabil-
ity in the Graphics Editor in Chapter 27, “Working with XImages and Colormaps.”
The function begins by determining whether the operand colorimage is defined in
the existing dictionaries:
7: “/colorimage where
Lines 11–15 invoke the rgbdata, define the number of color components, and pre-
pare to substitute the color values with proportionally weighted gray values based on
the intensity of the corresponding color component:
494 Part VI Adding a Print Driver
Next, lines 31–48 define mergeprocs, a process which performs some extensive stack
manipulation to merge the colorimage and colortogray functions. This is defined
for execution in line 52:
52: “ {colortogray} mergeprocs \n”,
When the mergeprocs is placed on the stack, it is given the image for processing
53: “ image \n”,
Next Steps
In the next chapter we will look closely at the implementation of the print capability
for the Graphics Editor, and see how the color conversion functions introduced here
fit into the overall print system.
In this chapter
• Printing the Canvas
• Creating an XImage
Note The code introduced in this chapter is targeted for the gxGx.c source file.
Important too is that non-static function definitions have prototypes for them
placed in the gxProtos.h header file. Finally, it will be necessary to ensure that
the headers time.h, unistd.h, and sys/stat.h have preprocessor include
directives for each of them placed at the beginning of the gxGx.c file.
found, the destination is treated like a file with the PostScript page description for
the canvas being written to it.
The gx_print function was previously defined as a stump function to satisfy the link
phase of building the application. The actual function definition is presented in
Listing 27.1.
The gx_print function defines a temporary storage file for constructing the
PostScript page definition of the canvas window:
3: char *_filename = “/tmp/gx-print-data.ps”;
Working with XImages and Colormaps Chapter 27 497
and prompting the user for the destination of the print action:
20: char *filename = gxGetFileName();
The gx_print function then creates an XImage from the canvas window and writes
the dimensions of the image in a prolog for the print file being constructed:
22: get_image( GxDrawArea, &xi, &width, &height );
23: write_ps_prolog( fp, width, height ); 27
These functions are defined in listings discussed later in this chapter.
If an XImage was successfully created, the image data is parsed to determine the pixel
values of the cells forming the image:
26: data =
27: set_color_data(GxDrawArea, xi, width, height,
28: cm, Red, Green, Blue, &numcols);
and the destination specified by the user is processed with a call to doPrintTo:
36: doPrintTo( _filename, filename );
It is now necessary to look at the function in more detail, starting with the creation
of an XImage from the drawing area.
Creating an XImage
Listing 27.2 defines the get_image function for creating an XImage from the canvas
window.
continues
498 Part VI Adding a Print Driver
The width and height of the image extracted from the GxDrawArea are important to
the generation of the PostScript description for it. Therefore, the width and height
values are returned separately to the calling function for use elsewhere:
7: XtVaGetValues( w,
8: XtNwidth, width,
9: XtNheight, height,
10: NULL );
to ensure that the requested image is completely contained in the window. An even
number of columns must be specified in the raster image passed to the PostScript
interpreter as the language requires it:
18: *height -= ((*height)%2);
The XGetImage function creates an XImage structure definition from the window
specified as the second parameter.
The image created does not have to encompass the entire window. Through use of
the x, y and width, height parameters, an image smaller than the entire window can
be created.
Working with XImages and Colormaps Chapter 27 499
The constants AllPlanes and ZPixmap are used to specify which planes should be
employed when creating the image and what the format of the image data should be.
If the format argument is XYPixmap, the image contains only the bit planes you
passed to the plane mask if something other than AllPlanes is used as the argument.
If the plane mask argument only requests a subset of the planes of the display, the
depth of the returned image will be the number of planes requested. If the format
argument is ZPixmap, XGetImage returns as 0 the bits in all planes not specified in the
plane mask argument.
Having determined the width and height of the image, you can write the prolog for 27
the page description file.
Although some of the data written in the prolog is for informational purposes only,
as discovered in Chapter 25, in the section “Comments Understood by
Ghostscript,” page 489, the prolog is an important element of the output file.
Following the creation of the PostScript prolog, you are ready to parse the image
created by XGetImage to determine the colors used in the many cells that compose
the image, as seen in Listing 27.4.
500 Part VI Adding a Print Driver
42: }
43:
44: setStatus( “Processing color data ...” );
45: for (i = 0; i < MAXCOLORMAPSIZE; i++) {
46: if( colused[i] ) {
47:
48: mapcols[i] = *n;
49:
54: (*n)++;
55: }
56: }
57:
58: setStatus( “Transferring image colors...” );
27
59: dptr = data;
60: for (i = 0; i < width*height; i++) {
61: *dptr = mapcols[*dptr];
62: dptr++;
63: }
64:
65: return data;
66: }
The actual image data comprising the XImage data field is an array of pixel values
where each pixel corresponds to an entry in the Colormap used to draw the image to
the screen.
The set_color_data function in lines 22–28 creates an array of all color data for the
pixels contained in the Colormap.
It then traverses the image data to determine which color is used:
38: colused[*iptr] = True; /* mark this color as used */
The color data extracted from the Colormap is then traversed to see whether the
color was used:
46: if( colused[i] ) {
If the color was used, the number of the color (assigned according to order of occur-
rence) is used as an index into an array, tracking the colors used from the Colormap:
48: mapcols[i] = *n;
The pixel values stored to reflect the colors used from the Colormap are transferred
to the raster data array that is used as the image definition passed to the PostScript
interpreter:
60: for (i = 0; i < width*height; i++) {
61: *dptr = mapcols[*dptr];
62: dptr++;
63: }
502 Part VI Adding a Print Driver
Finally, the data is returned to the calling function by passing back the pointer of the
beginning of the data block:
65: return data;
With the raster image data formed for the page description file, the data can now be
written to the temporary file using the write_ps function defined in Listing 27.5.
Following the writing of the PostScript prolog, the current state of the interpreter is
stored
6: fprintf(fp, “%% remember original state\n/saveorig save def\n\n”);
a variable is defined to store a single line of data from the raster image
8: fprintf(fp, “/imgstr %d string def\n\n”, w );
and it’s time to start working by assigning the placement of the image corner:
14: fprintf(fp, “%% corner of image\n%d %d translate\n\n”, 25, 25);
The image being described in the file is then scaled to fit the paper
16: fprintf(fp, “%% size of image on paper\n%d %d scale\n\n”,
17: w > 550 ? 550 : w,
18: h > 740 ? 740 : h ); /* could be changed for scaling */
Finally, the image data is written to the file using the write_ps_data function found
in Listing 27.6.
The command to show the page is ordered
30: fprintf( fp, “showpage\n\nend\n\nsaveorig restore\n\n”);
31: fprintf( fp, “%%%%Trailer\n\n%% End-of-file\n”);
Using the XImage captured from the GXDrawArea window, the write_ps_data loops
over the width and height of the image to generate a row and column coordinate.
This row and column is then passed to the XGetPixel to get the pixel value defining
the position:
9: cell = XGetPixel( xi, rows, cols );
The pixel value returned by XGetPixel is used as an index into the cm array generated
to reflect the colors in use by the image. Each color component is stored indepen-
dently to the PostScript file in the range of 0–256:
11: fprintf( fp, “%2.2x”, cm[cell].red / 256 );
12: fprintf( fp, “%2.2x”, cm[cell].green / 256 );
13: fprintf( fp, “%2.2x”, cm[cell].blue / 256 );
As the entirety of the image cells are considered, the data gets written to the page
description file.
With the contents of the PostScript description file complete, it is possible to parse
the destination entered by the user at the beginning of the gx_print function to
determine where the PostScript file should placed.
could be the name of a printer configured for the system or, optionally, the name
of a file.
Working with XImages and Colormaps Chapter 27 505
The doPrintTo function in Listing 27.7 attempts to determine whether the destina-
tion is a printer by looking for a corresponding device in the /dev/ directory; other-
wise, it treats the user input as a file.
Listing 27.7 The doPrintTo Function for Determining the Print Data Destination
1: static void doPrintTo( char *datafile, char *printTo )
2: {
3: struct stat sbuf;
4: char cmd[128], lpDev[128];
5:
6: FILE *fp = NULL; 27
7:
8: if( printTo ) {
9: sprintf( lpDev, “/dev/%s”, printTo );
10:
11: if( stat( lpDev, &sbuf ) < 0 ) {
12: sprintf( cmd, “mv %s %s”, datafile, printTo );
13: } else {
14: sprintf( cmd,
15: “unalias lp; lp -c -d %s %s;rm %s”,
16: printTo, datafile, datafile );
17: }
18: } else {
19: sprintf( cmd,
20: “unalias lp;lp -c %s;rm %s”,
21: datafile, datafile );
22: }
23:
24: if((fp = popen(cmd, “r”)) != NULL) {
25: char buf[128];
26: fgets( buf, sizeof( buf ), fp );
27: pclose( fp );
28: }
29:
30: setStatus( “Printing complete!” );
31: }
If a printer of the name entered by the user does not match the name of a device in
the /dev directory, a command is assembled to move the temporary PostScript page
definition file to a file of the name entered:
12: sprintf( cmd, “mv %s %s”, datafile, printTo );
Otherwise, if the stat command reflects that a printer of the same name as entered
by the user exists, the contents of the temporary PostScript file are directed to the lp
command, specifying the printer name entered as the destination device:
14: sprintf( cmd,
15: “unalias lp; lp -c -d %s %s;rm %s”,
16: printTo, datafile, datafile );
506 Part VI Adding a Print Driver
If nothing was entered by the user, it is assumed that the system default printer is the
desired destination
19: sprintf( cmd,
20: “unalias lp;lp -c %s;rm %s”,
21: datafile, datafile );
As the stream is opened for reading, it is possible to get the status of the command
returned to the application:
26: fgets( buf, sizeof( buf ), fp );
The stream must be closed when you are finished with it because a finite number of
streams can be opened concurrently by an application:
27: pclose( fp );
Next Steps
The final chapters of this text propose ideas for furthering the Graphics Editor pro-
ject and integrating it into other applications that could benefit from an annotation
subsystem.
Part VII
What’s Next?
In this chapter
• Attributes
• Rotating Objects
Attributes
The definition of the common object data structure in Chapter 15, “Common
Object Definition,” page 305, provided fields within the structure to store the fore-
ground and background colors as well as the line width and line style attributes of
the editor objects.
These fields were included in the Save and Restore function of the editor application
introduced in Chapter 19, in the section “Common Object Save and Restore,”
page 367.
However, the construction of the application did not provide for the selection of
colors or line attributes by the user.
The following sections pose ideas to lead you in the extension of the Graphics
Editor application to support the assignment of object attributes.
Color
The first consideration when extending the Graphics Editor project is how to pro-
vide a method for the user to assign, alter, and manipulate colors. Specific to this is
the interface mechanism for making colors available to the user for selection and
assignment.
510 Part VII What’s Next?
Either by adding an attributes button panel adjacent to the control panel or a drop-
down menu accessed from the canvas window, a palette should be defined for pre-
senting allowable colors.
After the interface mechanism is decided, the active objects selected by the user
could have their color values altered.
In addition to affecting color settings, the Graphics Editor should account for the
changing of line attributes, as described in the next section.
Line Attributes
Similar to assigning color values to the editor objects, the extension of the Graphics
Editor project requires providing the capability to alter the line width and line style
of the point-array–based editor objects.
Line Width
Valid values assigned as the line width attribute of point-array objects are any integer
describing the thickness of the line.
Note A Graphics Context created with the line_width field of the XGCValues structure
set to 0 instructs the X Server to draw the line using the fastest algorithm pos-
sible.
Extending the Graphics Editor Chapter 28 511
The interface mechanism to support this could simply be an entry field where the
user could provide a number. This clearly would require validation to ensure that
characters are not erroneously entered. Optionally, an up and down arrow could be
created to increment or decrement the object’s line width respectively.
Line Style
Valid values for the line style attribute assignment of editor objects include
LineSolid, LineDashed, and LineDoubleDashed.
These draw either a solid, dashed, or double-dashed line, as indicated by their name.
Note A double-dashed line is drawn with one segment of the dash drawn in the fore-
ground color assigned to the Graphics Context created for the invocation of the 28
X Window Graphic Primitive and the next segment drawn in the background
color of the GC.
A normal dashed line uses only the foreground color and skips alternate seg-
ments by drawing nothing.
Arc Angles
Complementing the line attributes of the previous section, attributes of the Arc
object are provided by the XArc data structure but not addressed by the interface of
the Graphics Editor application.
These are, specifically, the angle1 and angle2 fields of the structure. Review Chapter
8, “Vector Versus Raster Graphics,” page 197, for a discussion of these fields and
valid assignments.
Rotating Objects
In Chapter 11, “Graphic Transformations,” in the section “Rotating,” page 247, the
mathematics required to rotate the point-array editor objects was introduced.
Considered an advanced feature of the editor, structuring the capability of assigning
and altering object attributes should include a degree factor of rotation as an editable
field.
The interface mechanism most suited for altering the degree field you’ll add to the
common object definition is the scale bar.
The scale should be assigned a minimum scale factor of 0 and a maximum of 360,
enabling the user to apply a meaningful value for degrees of rotation.
512 Part VII What’s Next?
Next Steps
After the management of attributes outlined in this chapter is implemented, the
Graphics Editor application will be complete. The steps remaining are limited only
by your imagination.
The following chapter structures the addition of a context-sensitive help system to
the editor application.
In this chapter
• Processing Help-Related Events
• Widget Paths
Note The purpose of this chapter is to pose an idea for your continued learning using
the Graphics Editor project.
As with the XtAppMainLoop, our event loop will need access to the XtAppContext
structure created from the call to XtVaAppInitialize.
Adding Context-Sensitive Help Chapter 29 515
The only requirement of the event loop is that the events be removed and dispatched
continuously:
6: XtAppNextEvent( app, &event );
7: XtDispatchEvent( &event );
The sample loop from Listing 29.1 enables the help system to be optional by testing
an implied global variable helpEnabled:
9: if( helpEnabled == G_FALSE ) continue;
In the case of the help system being enabled, the HelpAppMainLoop event type is
tested in search of the KeyPress event
12: case KeyPress:
and when it is found, the value of the key pressed by the user is determined in a call
to the processKeyEvent function found in Listing 29.2.
Two critical areas are addressed by the processKeyEvent function. The first entails
deciphering the key pressed
11: keysym = XKeycodeToKeysym(XtDisplay(GxDrawArea),
12: xe->xkey.keycode, 0);
516 Part VII What’s Next?
and the second is waiting for the next ButtonPress event to determine the widget for
which context-sensitive help is being requested:
17: help_widget = trackingEvent( toplevel, cursor, True, &e );
The KeyPress event structure contains a field xe->xkey.keycode, which holds, for
the purpose of supporting multiple language definitions, the encoded key value for
the button pressed. The value of the encoded key is converted to a KeySym for com-
parison by the XKeycodeToKeysum Xlib function.
The trackingEvent function left for your definition must return the widget receiving
the very next button press event that signals the user’s desire for help on the entity.
Using the widget’s explicit path within the application’s instance hierarchy, as discussed
in the next section, a correlation is made to the help text specific for this widget.
Widget Paths
Every widget has an explicit place in the hierarchy of all widgets contained in the
application. This placement in the hierarchy is known as the widget’s path. If the
programmer chooses unique widget names (as assigned by the first parameter of the
XtVaCreateManagedWidget function), the path will uniquely identify the widget.
The widget’s instance name preceded by the instance name of its parent forms a por-
tion of the widget’s path. If you continually prepend the name of the parent widget’s
parent (and so forth), an explicit widget path for the initial widget is determined.
The getWidgetPath and getWidgetPathComponents functions defined in Listing 29.3
demonstrate how to find a widget path for the widget returned by trackingEvent
seen earlier.
15: /*
16: * grow if necessary
17: */
18: if( strlen(wname) + 1 >= *len ) {
19: /*
20: * find the current end position
21: */
22: int endPos = strlen( *path ) - 1;
23:
24: (*len) += 512;
25: *path = (char *)realloc( *path, *len);
26:
27: /*
28: * zero out what has just been alloc’d
29: */
30: memset( *path + endPos, 0, (*len) - endPos );
31: }
32:
33: /*
34: * store the name of this widget and a ‘.’
35: * if there are more components to follow 29
36: */
37: strcat( *path, wname );
38: if( parent != w )
39: strcat( *path, “.\0” );
40: }
41: }
42:
43: /*
44: ** getWidgetPath
45: **
46: ** follow the widget up its tree (hierarchy) assembling names
47: */
48: static char *getWidgetPath( Widget w )
49: {
50: static char *wpath = NULL;
51: static int wpathLen = 0;
52:
53: /* get a starting point for our buffer */
54: if( wpath == NULL || wpathLen == 0 ) {
55: wpathLen = 2048; /* hopefully it won’t grow */
56: wpath = (char *)malloc( wpathLen );
57: }
58:
59: /* clear it out */
60: memset( wpath, 0, wpathLen );
61: getWidgetPathComponents( w, w, &wpath, &wpathLen );
62:
63: return wpath;
64: }
518 Part VII What’s Next?
Invoking getWidgetPath
48: static char *getWidgetPath( Widget w )
for the widget returned from trackingEvent constructs a widget path to uniquely
identify it within the application.
The getWidgetPath begins by creating sufficient memory space to store the path
56: wpath = (char *)malloc( wpathLen );
which it initializes
60: memset( wpath, 0, wpathLen );
and then begins the recursive process of extracting instance names for the widget’s
ancestors:
61: getWidgetPathComponents( w, w, &wpath, &wpathLen );
and the names are assembled in the memory set aside for its use:
37: strcat( *path, wname );
This widget path is related to either a help text string or the name of an HTML file
that is displayed to satisfy the user’s query for help, as described in the next section.
The purpose of the configuration file entries is to associate each widget path of the
application with help information.
This information can either be a string displayed in a help dialog created by the
application, or as illustrated in Listing 29.4, an HTML file that the application uses
to invoke an external browser to display help for the item selected by the user.
Next Steps
This chapter did not fully implement the context-sensitive help system but formed a
to-do list outlining the key elements required to accomplish the task.
Sufficient information and structure is provided for the reader to work independently
solving the proposed context-sensitive help system..
29
Part VIII
Appendixes
Appendix A
Note Every user is assigned a default shell in the last field of the /etc/passwd file for
the entry defining their account. This is the shell that executes automatically
when the user’s login process completes.
Knowing the different command shells enables users to execute and employ
other shells that are not their default.
Optionally, some versions of UNIX enable users to change their default shell
through use of the passwd command by specifying the -s flag. Read the man
page for the passwd command available under the version of UNIX you are run-
ning to determine whether this option is available; otherwise, a request to your
System Administrator might be necessary to change your default shell.
EXCURSION
Controlling Jobs from Within a Command Shell
The job control feature enables processes to be moved from foreground to background
and vice versa without closing and restarting the program.
A foreground process running in your command shell does not enable you to continue to
issue commands until the process has finished. A background process (executed with a
trailing ampersand) enables use of the command shell by the user for issuing commands
and navigating the system to continue.
A shell that supports job control will enable a foreground process, meaning a process exe-
cuted without a trailing ampersand (&), to be suspended by holding down the Control key
and pressing the letter Z in the window where the program is running. Further, the process
can be continued by use of the fg command to return it to the foreground or the bg com-
mand to return it to the background.
The command jobs is used to list the processes that a shell controls.
Each process displayed in the list is preceded with a number assigned sequentially as
consecutive commands are executed. This number is referred to as a job number, and
when prefaced with a percent sign (%) can be used in place of the process’s ID obtained
with the ps (process status) command.
how too
pro nouns it The C Shell is pronounced as if it were written sea shell and the TC shell as if it
were written tea sea shell.
Command Shells and Scripting Appendix A 525
speak The C shell first introduced job control. It is a commonly assigned interactive shell
but a poor choice for scripting because the method of implementing functions is
geek
non-standard between implementations of the shell and because nested if statements
often do not work as expected.
The Korn shell is an extension of the Bourne shell and made significant improve-
ments to the Bourne shell’s interactive friendliness. As a derivative of the Bourne
shell, the Korn shell is well suited for shell programming.
The Korn shell is proprietary to AT&T but is available as the Bourne Again shell
(bash) on non-AT&T versions of UNIX.
Shell Variables
The method of defining variables within a command shell varies based on the syntax A
understood by the shell. The following sections show the differing ways of defining
variables for common command shells.
geek
526 Part VIII Appendixes
Note An internal command refers to the fact that only the shell will interpret the com-
mand. Other shells might not understand it, and it will not physically reside on
the UNIX system.
To read about an internal shell command, you will have to display the man page
for the shell to which it is internal. An example of this is the export command,
introduced for making variables available to subshells.
The export command can be combined with the variable declaration and value
assignment.
export VAR=value
C Shell Variables
Setting variables in the C shell or one of its derivatives requires the internal shell
command setenv.
setenv VAR value
Note Syntactically, it is incorrect to use an equal sign (=) with the C shell variable dec-
laration because the equal sign is unique to the Bourne shell. In other words,
attempting the Korn shell syntax
VAR=value
Unlike the Bourne shell, the C shell enables a variable to unset by use of the
unsetenv internal shell command.
unsetenv VAR
To display all the variables set within an environment, use the env command. A spe-
cific variable’s value can be displayed by either echoing its value using the syntax
echo $VAR
Aliases
Most shells enable UNIX commands to have an alias defined in the shell environ-
ment to simplify the command’s use.
Command Shells and Scripting Appendix A 527
For instance, using C shell syntax, the following alias can be defined for performing
long directory listings:
alias ll ls -l
In this example, the alias internal shell command defines the alias ll to be the ls
command with the -l flag. After executing the alias command, the ll alias is avail-
able as if it were a valid UNIX or shell command.
Variables and aliases defined on the command line are destroyed when the shell exits
unless they are placed in an environment file read by the shell when it executes.
Environment Files
Command shells available under UNIX search for and interpret the contents of an
environment file when it executes. This environment file enables a user to make vari-
ables and aliases available between user sessions.
Which file to create or modify within your user environment depends upon the shell
being used.
Note Notice the dot (.) preceding the filename. A file preceded by a dot is called a hid-
den file because this makes the file invisible to the directory listing obtained by
the ls command unless the -a (all) flag is used.
The contents of the .profile file can be any valid UNIX or internal shell command,
one per line or separated by a semicolon (;), and entered exactly as if typed on the
command line.
Listing A.1 shows a sample .profile file for initializing an environment when using
the Bourne command shell or derivative.
continues
528 Part VIII Appendixes
Understanding Listing A.1 only requires that you apply many of the things you
learned in previous chapters of this book.
Starting after the comment of line 1, consider
2: PATH=”$PATH:/usr/X11R6/bin”
which sets an environment variable called PATH. All command shells employ a PATH
variable for identifying where the shell should look for UNIX commands. A UNIX
command not contained in one of the elements of the PATH variable will be reported
as command not found unless an explicit path is specified.
As this setting is in the .profile file, the value is set every time the Bourne shell
executes.
Following the assignment of the PATH variable, the variable called PS1 is set:
3: PS1=”[\u@\h ]\\$ “
The PS1 variable is a secondary or command continuation prompt. When the com-
mand shell is told that a command will extend to a second line through use of the
line continuation character backslash (\) followed by return, the value of PS1 is used
as the secondary prompt.
Every command shell will employ the PS1 variable for this purpose; however, the
syntax understood by the shell when interpreting this variable depends upon the shell
being used.
The syntax demonstrated in Listing A.1 for the Bourne Again Shell (bash) will yield
the prompt
[username@hostname] $
because the token \u specifies the username, \h the hostname and \\$ simply ‘$’.
The next few lines of Listing A.1 set several useful environment variables using stan-
dard UNIX commands and variables after they are defined.
Command Shells and Scripting Appendix A 529
5: USER=`id -un`
6: LOGNAME=$USER
7: MAIL=”/var/spool/mail/$USER”
8: HOSTNAME=`/bin/hostname`
Pay close attention to the syntax for calling a UNIX command from within a shell
script and employing the result. The shell must be told to evaluate the command
before performing the assignment. This is accomplished by encasing the command
between tick marks (`).
The HISTSIZE and HISTFILESIZE variables determine the number of commands
remembered by the shell during a session and how many are retained in the
.history file.
9: HISTSIZE=1000
10: HISTFILESIZE=1000
Shell command history enables the user to repeat commands by either using the up
arrow key to have previously executed commands repeated at the command prompt
or by using the history command to see and select commands from a list.
EXCURSION
A
Repeating Commands from the Shell History
To repeat a command from the history list, preface the sequential number assigned
every command with an exclamation point on the command line.
bash[100]: !23
would repeat the last command executed that started with the letter m.
bash[102]: !ps
export the variables set to ensure that they are visible to any sub-shells the current
shell might parent.
530 Part VIII Appendixes
In the sample .cshrc shown in Listing A.2, a test is conducted following the com-
ment to see whether the PATH variable is already set in the environment.
3: if ($?PATH) then
4: setenv PATH “${PATH}:/usr/X11R6/bin”
5: else
6: setenv PATH “/bin:/usr/bin:/usr/local/bin:/usr/X11R6/bin”
7: endif
By now, the syntax $VAR should be understood as the method for accessing the value
of a variable.
The syntax shown in the sample .cshrc introduces the question mark in conjunction
with the variable access as the means for testing whether the variable value is of non-
zero length to indicate whether a value is set.
The sample either appends a value to an existing value to ensure the X client com-
mands are seen by the shell
4: setenv PATH “${PATH}:/usr/X11R6/bin”
The next section of Listing A.2 determines whether the prompt value has been set. If
not, based on which shell is being used, this section sets it accordingly:
9: if ($?prompt) then
10: if ($?tcsh) then
11: set prompt=’[%n@%m %c]$ ‘
12: else
13: set prompt=\[`id -nu`@`hostname -s`\]\$\
14: endif
15: endif
The test for which shell is currently being executed is necessary because the syntax
understood by the TC Shell differs from the syntax understood by the C shell for
specifying the prompt value.
The final step is the setting of the variable HOSTNAME
17: setenv HOSTNAME `/bin/hostname`
The following section describes how to write shell scripts using the Bourne shell.
A
Scripting with the Bourne Shell
Note Although targeted for the Bourne shell, this section applies to the Bourne shell
enhancement Korn shell and the Korn shell clone Borne Again shell.
Shell scripting gives a UNIX software developer the ability to automate simple tasks
in a portable and extensible manner.
Scripts are easy to create and modify and are immediately available.
To begin a shell script, the first line must specify the UNIX system that is the
intended shell for interpreting the script. This designator looks very much like a
comment, but is read and used by the system.
The shell designator uses the tokens #! followed by the command for the shell,
including an explicit path.
#!/bin/sh
After specifying the interpreter, there are no other requirements beyond obeying the
syntax for the commands and conventions used in the body of the shell.
532 Part VIII Appendixes
Shell Variables
Declaring variables in shell script follows syntax that you have already seen in the
.profile read by the Bourne shell when it executes.
VAR=value
A syntactical requirement is that no spaces exist between the components of the vari-
able definition VAR, =, and value. If a space is present, the Bourne shell will perceive
that an attempt is being made to execute a command and attempt to invoke VAR
leading to a statement of the sort
VAR: command not found
A default value can be specified when using a variable by encasing the variable in
braces and using a dash (-) separator between the variable name and the default value
${VAR-default}
Quoting Variables
When learning shell programming, you often won’t know when to quote, what to
quote, and which quotes to use.
Single forward quotes protect against all processing when spaces are used during
variable assignments, as in the following example:
VAR=’value1 value2’
Without the use of single quotes when a variable contains nested spaces, the shell
will attempt to invoke VAR as a command.
Double quotes enable variable substitutions, with the result being a single argument,
shown in the following example:
VAR=’value1 value2’
VAR2=”$VAR”
Performing Tests
Performing tests is a crucial part of creating useful programs, as was discussed when
reviewing programming conventions in Chapter 2, “Programming Constructs.”
Tests in the Bourne shell are performed with either the if command or by simply
nesting the test expression in square braces ([ ]).
The if statement in Bourne shell implies its own body; therefore, start and end body
markers are not required. Instead, the if must be closed with the fi keyword.
Command Shells and Scripting Appendix A 533
Test expressions understood by the Bourne shell depend on the variable types being
tested. Unlike C, Bourne test expressions yield zero (0) if the condition is true.
Table A.1 shows the test expressions understood by Bourne shell and examples of
their use.
continues
534 Part VIII Appendixes
Note Notice the syntax for specifying an else condition to a test as demonstrated in
the last line of Table A.1.
Similar to the C switch statement, the Bourne shell provides for establishing actions
for multiple conditions using the case statement.
Case Tables
The syntax for performing a case is
case “$file” in
*.c) echo “$file is a C source file”
;;
*.h) echo “$file is a C header file”
;;
.*) echo “$file is a hidden file”
;;
*) echo “$file is an unknown file type”
;;
A feature of the Bourne shell case statement is the capability to use wildcards in the
conditions being tested. These fields, as shown in the previous example, are termi-
nated with a double semicolon (;;) following the condition body.
The syntax for conditions in a case statement can optionally be ORed as well
case “$input” in
Q*|q*) echo “Exiting...”
exit 1
;;
*) echo “Input not recognized ($input)”
;;
Looping
Two loop constructs, the while loop and the for loop, exist in the Bourne shell.
The sample while loop simply counts to 10, printing the value of the variable cnt for
each iteration. The use of do and done statements for marking the code body of the
loop is important to all loops understood by the Bourne shell.
EXCURSION
Use the expr Command to Perform Mathematical Equations in a Shell Script
The command expr is a standard UNIX command used to manipulate variables in a
variety of ways. It is a useful command. Review its man page for a full description. A
Because the Bourne shell has no mathematical capability, you must use the expr com-
mand for mathematical operations. Give each component of the expression to expr as a
separate argument.
In the while loop example, expr is used to add 1 to the variable cnt. Notice the tick marks
instructing the shell to interpret the UNIX call before making the assignment. In this way,
the return value of the evaluated expr call is used as the new value of cnt.
The for loop expands the ls command and makes the result a token, assigning one
token per loop iteration to the variable file.
One shortcoming of the Bourne shell for loop is that it will always perform one iter-
ation of the loop, even when the evaluated statement yields no tokens.
536 Part VIII Appendixes
Many other internal commands and features exist in the Bourne shell script. The
preceding sections provide an excellent starting point for writing shell scripts.
Note After saving the file, be sure to add execute permission using the command
chmod +x filename
Listing A.3 shows a sample shell script for printing a menu and responding to user
input.
Two new conventions appear in Listing A.3, specifically, the print_menu function
definition and the commands sleep, read, and exit.
A function is defined in a Bourne shell by providing a name followed by open and
close parentheses. Open and close curly braces are used to mark the beginning and
ending of the function body.
The function is invoked by simply entering its name:
22: print_menu
The commands sleep, read, and exit perform the function implied by their name.
538 Part VIII Appendixes
Unlike other languages, Bourne shell uses the exit value of zero (0) to indicate
speak
success.
geek
This is useful because many errors can cause a shell script to exit. By returning a
value greater than zero when there is an error, the value can be used to determine
the failure’s cause.
When an error occurs because of unexpected behavior of the script, it can be
debugged.
Note Running a shell script within a shell, as demonstrated in the previous example for
enabling debug, works for executing a script for which you don’t have execute
permission.
When a shell script is in debug mode, it prints every line prior to execution. This
enables you to see the value of variables as they are evaluated by the script.
Appendix B
Figure B.1
Project directory
structure.
540 Part VIII Appendixes
The directory 2d-editor is the root directory for the project; it contains a source
directory called src and an object directory called i86-Linux.
These directories can be created using the commands discussed in Chapter 1,
“UNIX for Developers.”
38: #
39: # Configure for Solaris running on a Sparc
40: #
41: ifeq ($(TARGET),$(TARGET_SPARC_SUNOS))
42: X11INC = -I/usr/openwin/include
43: X11LIB = -L/usr/openwin/lib
44: INCS = -I${GxSRCDIR}/include
45:
46: CC = gcc
47: OPTS = -g -Wall -ansi
48: endif
49:
50: #
51: # Configure for Solaris running on a PC
52: #
53: ifeq ($(TARGET),$(TARGET_i86_SOLARIS))
54: X11INC = -I/usr/openwin/include
55: X11LIB = -L/usr/openwin/lib
56: INCS = -I${GxSRCDIR}/include
57:
58: CC = gcc
59: OPTS = -ansi -Wall -g
60: endif
61:
62: #
63: # Force all Makefiles using this file to check the configuration
64: # of the environment before building the target
65: #
66: all: make-env-check make-target B
67:
68: #
69: # Check to environment variables need to build are set
70: #
71: make-env-check:
72: ifndef TARGET
73: @echo
74: @echo “TARGET not defined!”
75: @echo “Set environment variable TARGET to:”
76: @echo “ sun4u-SunOS or”
77: @echo “ i86pc-SunOS or”
78: @echo “ i86-Linux”
79: @echo
80: @exit 1
81: endif
82:
83: clean:
84: @rm -f *~ *.o $(PROGRAM)
85: #
86: # end of make.defines
87: #
As discussed in Chapter 1 where this file was introduced, the variable TARGET must be
set in your environment for this portion of the GNUmakefile to work.
542 Part VIII Appendixes
Note It is necessary to create a symbolic link to this file in the object directory where
the project will be built. See Chapter 1 for a discussion of symbolic links and how
to create them.
40: WhitePixelOfScreen(XtScreen(parent)),
41: XtNtop, XawChainTop,
42: XtNleft, XawChainLeft,
43: XtNbottom, XawChainBottom,
44: XtNright, XawChainRight,
45: XtNheight, 220,
46: XtNwidth, 250,
47: NULL );
48:
49: XtAddEventHandler( GxDrawArea, PointerMotionMask, False,
50: (XtEventHandler)drawAreaEventProc,
➥ (XtPointer)NULL);
51: XtAddEventHandler( GxDrawArea, ButtonPressMask, False,
52: (XtEventHandler)drawAreaEventProc,
➥ (XtPointer)NULL);
53: XtAddEventHandler( GxDrawArea, ButtonReleaseMask, False,
54: (XtEventHandler)drawAreaEventProc,
➥ (XtPointer)NULL);
55:
56: return GxDrawArea;
57: }
58:
59: /*
60: * create_status
61: */
62: void create_status( Widget parent, Widget fvert )
63: {
64: GxStatusBar =
➥ XtVaCreateManagedWidget( “statusBar”, B
65: labelWidgetClass, parent,
66: XtNtop, XawChainBottom,
67: XtNleft, XawChainLeft,
68: XtNbottom, XawChainBottom,
69: XtNright, XawChainRight,
70: XtNfromVert, fvert,
71: XtNborderWidth, 0,
72: NULL );
73: setStatus( “2D-GX (c)Starry Knight Software - Ready...” );
74: }
75:
76: /*
77: * statusProc
78: */
79: void statusProc( Widget w,
➥ XtPointer msg, XEvent *xe, Boolean flag )
80: {
81: if( msg == NULL )
82: setStatus( “\0” );
83: else
84: setStatus( msg );
85: }
86:
continues
546 Part VIII Appendixes
50:
51: /**
52: ** end of gxGx.c
53: */
Clearly, the files representing the various objects in the Graphics Editor project are B
simply stubs. Beginning with Chapter 13, “Application Structure,” the text will
advance these files.
continues
550 Part VIII Appendixes
continues
552 Part VIII Appendixes
16: 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
17: 0x00,0x00,0x00,0x00,0x20,0x00,0x04,0x00,0x00,0x30,0x00,
18: 0x0c,0x00,0x00,0x50,0x00,0x0c,0x00,0x00,0x48,0x00,0x14,
19: 0x00,0x00,0x88,0x00,0x14,0x00,0x00,0x84,0x00,0x14,0x00,
20: 0x00,0x04,0x01,0x22,0x00,0x00,0x02,0x01,0x22,0x00,0x00,
21: 0x02,0x02,0x22,0x00,0x00,0x01,0x02,0x42,0x00,0x00,0x01,
22: 0x04,0x42,0x00,0x80,0x00,0x04,0x42,0x00,0x80,0x00,0x02,
23: 0x01,0x00,0x40,0x00,0x01,0x01,0x00,0x40,0x80,0x00,0x01,
24: 0x00,0x20,0x40,0x00,0x01,0x00,0x20,0x20,0x00,0x01,0x00,
25: 0x00,0x10,0x00,0x01,0x00,0x00,0x08,0x80,0x00,0x00,0x00,
26: 0x30,0x40,0x00,0x00,0x00,0xc0,0x20,0x00,0x00,0x00,0x00,
27: 0x13,0x00,0x00,0x00,0x00,0x0c,0x00,0x00,0x00,0x00,0x00,
28: 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
29: 0x00,0x00,0x00,0x00,0x00,0x00};
30: icon_static( line_icon, line_bits, 36, 32 );
31:
32: static unsigned char pencil_bits[] = {
33: 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
34: 0x00,0xc0,0x00,0x00,0x00,0x00,0xe0,0x01,0x00,0x00,0x00,0xd0,
35: 0x03,0x00,0x00,0x00,0x88,0x03,0x00,0x00,0x00,0x14,0x01,0x00,
36: 0x00,0x00,0xa6,0x00,0x00,0x00,0x00,0x49,0x00,0x00,0x00,0x80,
37: 0x30,0x00,0x00,0x00,0x40,0x10,0x00,0x00,0x00,0x20,0x08,0x00,
38: 0x00,0x00,0x10,0x04,0x00,0x00,0x00,0x08,0x02,0x00,0x00,0x00,
39: 0x04,0x01,0x00,0x00,0x00,0x82,0x00,0x00,0x00,0x00,0x41,0x00,
40: 0x00,0x00,0x80,0x20,0x00,0x00,0x00,0x40,0x10,0x00,0x00,0x00,
41: 0xa0,0x08,0x00,0x00,0x00,0x10,0x05,0x00,0x00,0x00,0x10,0x02,
42: 0x00,0x00,0x00,0x30,0x01,0x00,0x00,0x28,0xf0,0x00,0x00,0x00,
43: 0x44,0x10,0x00,0x00,0x00,0x84,0x20,0x00,0x00,0x00,0x04,0x41,
44: 0x00,0x00,0x00,0x08,0x42,0x00,0x00,0x00,0x10,0x44,0x00,0x00, B
45: 0x00,0x20,0x38,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
46: 0x00,0x00,0x00,0x00};
47: icon_static( pen_icon, pencil_bits, 36, 32 );
48:
49: static unsigned char arc_bits[] = {
50: 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
51: 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
52: 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x80,0x3f,0x00,0x00,0x00,
53: 0x70,0xc0,0x01,0x00,0x00,0x0c,0x00,0x06,0x00,0x00,0x02,0x00,
54: 0x08,0x00,0x00,0x01,0x00,0x10,0x00,0x80,0x00,0x00,0x20,0x00,
55: 0x40,0x00,0x00,0x40,0x00,0x40,0x00,0x04,0x40,0x00,0x20,0x00,
56: 0x04,0x80,0x00,0x20,0x00,0x1f,0x80,0x00,0x20,0x00,0x04,0x80,
57: 0x00,0x40,0x00,0x04,0x40,0x00,0x40,0x00,0x00,0x40,0x00,0x80,
58: 0x00,0x00,0x20,0x00,0x00,0x01,0x00,0x10,0x00,0x00,0x02,0x00,
59: 0x08,0x00,0x00,0x0c,0x00,0x86,0x00,0x00,0x70,0xc0,0x81,0x00,
60: 0x00,0x80,0x3f,0xe0,0x03,0x00,0x00,0x00,0x80,0x00,0x00,0x00,
61: 0x00,0x80,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
62: 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
63: 0x00,0x00,0x00,0x00};
64: icon_static( arc_icon, arc_bits, 36, 32 );
65:
66: static unsigned char box_bits[] = {
67: 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
continues
554 Part VIII Appendixes
118:
119: #endif /* _GX_BITMAPS_H_INC_ */
120:
121: /**
122: ** end of gxIcons.h
123: */
continues
556 Part VIII Appendixes
continues
558 Part VIII Appendixes
continues
562 Part VIII Appendixes
continues
564 Part VIII Appendixes
continues
566 Part VIII Appendixes
continues
568 Part VIII Appendixes
412: NULL,
413: };
414: static int char_p552[] = {
415: XtNumber(seg0_552),XtNumber(seg1_552),
416: };
417: static XPoint seg0_553[] = {
418: {2,-6},{2,-5},{3,-4},{5,-4},{7,-5},
419: {8,-7},{8,-9},{7,-11},{5,-12},{2,-12},{-1,-11},{-3,-9},
420: {-5,-6},{-6,-4},{-7,0},{-7,4},{-6,7},{-5,8},{-3,9},
421: {-1,9},{2,8},{4,6},{5,4},
422: };
423: static XPoint *char553[] = {
424: seg0_553,
425: NULL,
426: };
427: static int char_p553[] = {
428: XtNumber(seg0_553),
429: };
430: static XPoint seg0_554[] = {
431: {2,-12},{0,-11},{-1,-9},{-2,-5},{-3,1},{-4,4},{-5,6},{-7,8},
432: {-9,9},{-11,9},{-12,8},{-12,6},{-11,5},{-9,5},{-7,6},{-5,8},
433: {-2,9},{1,9},{4,8},{6,6},{8,2},{9,-3},{9,-7},{8,-10},{7,-11},
434: {5,-12},{2,-12},{0,-10},{0,-8},{1,-5},{3,-2},{5,0},{8,2},
435: {10,3},
436: };
437: static XPoint *char554[] = {
438: seg0_554,
439: NULL,
440: };
441: static int char_p554[] = {
442: XtNumber(seg0_554),
443: };
444: static XPoint seg0_555[] = { C
445: {4,-8},{4,-7},{5,-6},{7,-6},{8,-7},{8,-9},{7,-11},{4,-12},
446: {0,-12},{-3,-11},{-4,-9},{-4,-6},{-3,-4},{-2,-3},{1,-2},
447: {-2,-2},{-5,-1},{-6,0},{-7,2},{-7,5},{-6,7},{-5,8},{-2,9},
448: {1,9},{4,8},{6,6},{7,4},
449: };
450: static XPoint *char555[] = {
451: seg0_555,
452: NULL,
453: };
454: static int char_p555[] = {
455: XtNumber(seg0_555),
456: };
457: static XPoint seg0_556[] = {
458: {0,-6},{-2,-6},{-4,-7},{-5,-9},{-4,-11},{-1,-12},{2,-12},
459: {6,-11},{9,-11},{11,-12},
460: };
461: static XPoint seg1_556[] = {
462: {6,-11},{4,-4},{2,2},{0,6},{-2,8},{-4,9},{-6,9},{-8,8},
463: {-9,6},{-9,4},{-8,3},{-6,3},{-4,4},
464: };
continues
570 Part VIII Appendixes
continues
572 Part VIII Appendixes
618: NULL,
619: };
620: static int char_p566[] = {
621: XtNumber(seg0_566),
622: };
623: static XPoint seg0_567[] = {
624: {3,-6},{2,-4},{1,-3},{-1,-2},{-3,-2},{-4,-4},{-4,-6},{-3,-9},
625: {-1,-11},{2,-12},{5,-12},{7,-11},{8,-9},{8,-5},
626: {7,-2},{5,1},{1,5},{-2,7},{-4,8},{-7,9},{-9,9},{-10,8},{-10,6},
627: {-9,5},{-7,5},{-5,6},{-2,8},{1,9},{4,9},{7,8},{9,6},
628: };
629: static XPoint *char567[] = {
630: seg0_567,
631: NULL,
632: };
633: static int char_p567[] = {
634: XtNumber(seg0_567),
635: };
636: static XPoint seg0_568[] = {
637: {1,-10},{2,-9},{2,-6},{1,-2},{0,1},{-1,3},{-3,6},{-5,8},
638: {-7,9},{-8,9},{-9,8},{-9,5},{-8,0},{-7,-3},{-6,-5},{-4,-8},
639: {-2,-10},{0,-11},{3,-12},{7,-12},{9,-11},{10,-10},{11,-8},
640: {11,-5},{10,-3},{9,-2},{7,-1},{4,-1},{1,-2},{2,-1},{3,1},
641: {3,6},{4,8},{6,9},{8,8},{9,7},{11,4},
642: };
643: static XPoint *char568[] = {
644: seg0_568,
645: NULL,
646: };
647: static int char_p568[] = {
648: XtNumber(seg0_568),
649: };
650: static XPoint seg0_569[] = { C
651: {-10,9},{-8,8},{-6,6},{-3,2},{-1,-1},{1,-5},{2,-8},{2,-11},
652: {1,-12},{0,-12},{-1,-11},{-2,-9},{-2,-7},{-1,-5},{1,-3},{4,-1},
653: {6,1},{7,3},{7,5},{6,7},{5,8},{2,9},{-2,9},{-5,8},{-7,6},
654: {-8,4},{-8,2},
655: };
656: static XPoint *char569[] = {
657: seg0_569,
658: NULL,
659: };
660: static int char_p569[] = {
661: XtNumber(seg0_569),
662: };
663: static XPoint seg0_570[] = {
664: {0,-6},{-2,-6},{-4,-7},{-5,-9},{-4,-11},{-1,-12},{2,-12},
665: {6,-11},{9,-11},{11,-12},
666: };
667: static XPoint seg1_570[] = {
668: {6,-11},{4,-4},{2,2},{0,6},{-2,8},{-4,9},{-6,9},{-8,8},
669: {-9,6},{-9,4},{-8,3},{-6,3},{-4,4},
670: };
continues
574 Part VIII Appendixes
continues
580 Part VIII Appendixes
1030: {5,9},
1031: };
1032: static XPoint seg1_620[] = {
1033: {-3,-5},{4,-5},
1034: };
1035: static XPoint *char620[] = {
1036: seg0_620,seg1_620,
1037: NULL,
1038: };
1039: static int char_p620[] = {
1040: XtNumber(seg0_620),XtNumber(seg1_620),
1041: };
1042: static XPoint seg0_621[] = {
1043: {-5,-5},{-5,5},{-4,8},{-2,9},{1,9},{3,8},
1044: {6,5},
1045: };
1046: static XPoint seg1_621[] = {
1047: {6,-5},{6,9},
1048: };
1049: static XPoint *char621[] = {
1050: seg0_621,seg1_621,
1051: NULL,
1052: };
1053: static int char_p621[] = {
1054: XtNumber(seg0_621),XtNumber(seg1_621),
1055: };
1056: static XPoint seg0_622[] = {
1057: {-6,-5},{0,9},
1058: };
1059: static XPoint seg1_622[] = {
1060: {6,-5},{0,9},
1061: };
1062: static XPoint *char622[] = { C
1063: seg0_622,seg1_622,
1064: NULL,
1065: };
1066: static int char_p622[] = {
1067: XtNumber(seg0_622),XtNumber(seg1_622),
1068: };
1069: static XPoint seg0_623[] = {
1070: {-8,-5},{-4,9},
1071: };
1072: static XPoint seg1_623[] = {
1073: {0,-5},{-4,9},
1074: };
1075: static XPoint seg2_623[] = {
1076: {0,-5},{4,9},
1077: };
1078: static XPoint seg3_623[] = {
1079: {8,-5},{4,9},
1080: };
1081: static XPoint *char623[] = {
1082: seg0_623,seg1_623,seg2_623,seg3_623,
continues
582 Part VIII Appendixes
1133: {-3,9},{-1,9},{1,8},{2,6},{4,0},{3,5},
1134: {3,8},{4,9},{5,9},{7,8},{8,7},{10,4},
1135: };
1136: static XPoint *char651[] = {
1137: seg0_651,
1138: NULL,
1139: };
1140: static int char_p651[] = {
1141: XtNumber(seg0_651),
1142: };
1143: static XPoint seg0_652[] = {
1144: {-5,4},{-3,1},{0,-4},{1,-6},{2,-9},{2,-11},{1,-12},{-1,-11},
1145: {-2,-9},{-3,-5},{-4,2},{-4,8},{-3,9},{-2,9},{0,8},{2,6},
1146: {3,3},{3,0},{4,4},{5,5},{7,5},{9,4},
1147: };
1148: static XPoint *char652[] = {
1149: seg0_652,
1150: NULL,
1151: };
1152: static int char_p652[] = {
1153: XtNumber(seg0_652),
1154: };
1155: static XPoint seg0_653[] = {
1156: {2,2},{2,1},{1,0},{-1,0},{-3,1},{-4,2},{-5,4},{-5,6},
1157: {-4,8},{-2,9},{1,9},{4,7},{6,4},
1158: };
1159: static XPoint *char653[] = {
1160: seg0_653,
1161: NULL,
1162: };
1163: static int char_p653[] = {
1164:
1165: };
XtNumber(seg0_653),
C
1166: static XPoint seg0_654[] = {
1167: {3,3},{2,1},{0,0},{-2,0},
1168: {-4,1},{-5,2},{-6,4},{-6,6},{-5,8},{-3,9},{-1,9},{1,8},{2,6},
1169: {8,-12},
1170: };
1171: static XPoint seg1_654[] = {
1172: {4,0},{3,5},{3,8},{4,9},{5,9},{7,8},{8,7},{10,4},
1173: };
1174: static XPoint *char654[] = {
1175: seg0_654,seg1_654,
1176: NULL,
1177: };
1178: static int char_p654[] = {
1179: XtNumber(seg0_654),XtNumber(seg1_654),
1180: };
1181: static XPoint seg0_655[] = {
1182: {-3,7},{-1,6},{0,5},{1,3},{1,1},{0,0},{-1,0},{-3,1},{-4,3},
1183: {-4,6},{-3,8},{-1,9},{1,9},{3,8},{4,7},{6,4},
1184: };
continues
584 Part VIII Appendixes
1236: {2,-4},{2,-5},{1,-5},
1237: };
1238: static XPoint seg1_659[] = {
1239: {-2,4},{0,0},{-2,6},{-2,8},{-1,9},{0,9},{2,8},{3,7},
1240: {5,4},
1241: };
1242: static XPoint *char659[] = {
1243: seg0_659,seg1_659,
1244: NULL,
1245: };
1246: static int char_p659[] = {
1247: XtNumber(seg0_659),XtNumber(seg1_659),
1248: };
1249: static XPoint seg0_660[] = {
1250: {1,-5},{1,-4},{2,-4},{2,-5},{1,-5},
1251: };
1252: static XPoint seg1_660[] = {
1253: {-2,4},{0,0},{-6,18},{-7,20},{-9,21},{-10,20},{-10,18},{-9,15},
1254: {-6,12},{-3,10},{-1,9},{2,7},{5,4},
1255: };
1256: static XPoint *char660[] = {
1257: seg0_660,seg1_660,
1258: NULL,
1259: };
1260: static int char_p660[] = {
1261: XtNumber(seg0_660),XtNumber(seg1_660),
1262: };
1263: static XPoint seg0_661[] = {
1264: {-5,4},{-3,1},{0,-4},{1,-6},
1265: {2,-9},{2,-11},{1,-12},{-1,-11},{-2,-9},{-3,-5},{-4,1},{-5,9},
1266: };
1267: static XPoint seg1_661[] = {
1268: {-5,9},{-4,6},{-3,4},{-1,1},{1,0},{3,0},{4,1},{4,3}, C
1269: {2,4},{-1,4},
1270: };
1271: static XPoint seg2_661[] = {
1272: {-1,4},{1,5},{2,8},{3,9},{4,9},{6,8},{7,7},{9,4},
1273: };
1274: static XPoint *char661[] = {
1275: seg0_661,seg1_661,seg2_661,
1276: NULL,
1277: };
1278: static int char_p661[] = {
1279: XtNumber(seg0_661),XtNumber(seg1_661),XtNumber(seg2_661),
1280: };
1281: static XPoint seg0_662[] = {
1282: {-3,4},{-1,1},{2,-4},{3,-6},{4,-9},{4,-11},{3,-12},{1,-11},
1283: {0,-9},{-1,-5},{-2,2},{-2,8},{-1,9},{0,9},{2,8},{3,7},{5,4},
1284: };
1285: static XPoint *char662[] = {
1286: seg0_662,
1287: NULL,
1288: };
continues
586 Part VIII Appendixes
1443: {-9,7},{-8,9},{-6,9},{-4,8},{-2,6},
1444: };
1445: static XPoint seg1_673[] = {
1446: {0,0},{-2,6},{-2,8},{-1,9},{1,9},{3,8},{5,6},{6,3},
1447: {6,0},
1448: };
1449: static XPoint seg2_673[] = {
1450: {6,0},{7,4},{8,5},{10,5},{12,4},
1451: };
1452: static XPoint *char673[] = {
1453: seg0_673,seg1_673,seg2_673,
1454: NULL,
1455: };
1456: static int char_p673[] = {
1457: XtNumber(seg0_673),XtNumber(seg1_673),XtNumber(seg2_673),
1458: };
1459: static XPoint seg0_674[] = {
1460: {-8,4},{-6,1},{-4,0},
1461: {-2,0},{-1,1},{-1,8},{0,9},{3,9},{6,7},{8,4},
1462: };
1463: static XPoint seg1_674[] = {
1464: {5,1},{4,0},{2,0},{1,1},{-3,8},{-4,9},{-6,9},{-7,8},
1465: };
1466: static XPoint *char674[] = {
1467: seg0_674,seg1_674,
1468: NULL,
1469: };
1470: static int char_p674[] = {
1471: XtNumber(seg0_674),XtNumber(seg1_674),
1472: };
1473: static XPoint seg0_675[] = {
1474: {-6,4},{-4,0},{-6,6},{-6,8},{-5,9},{-3,9},{-1,8},{1,6},{3,3},
1475: }; C
1476: static XPoint seg1_675[] = {
1477: {4,0},{-2,18},{-3,20},{-5,21},{-6,20},{-6,18},{-5,15},{-2,12},
1478: {1,10},{3,9},{6,7},{9,4},
1479: };
1480: static XPoint *char675[] = {
1481: seg0_675,seg1_675,
1482: NULL,
1483: };
1484: static int char_p675[] = {
1485: XtNumber(seg0_675),XtNumber(seg1_675),
1486: };
1487: static XPoint seg0_676[] = {
1488: {-6,4},{-4,1},{-2,0},{0,0},{2,2},{2,4},{1,6},{-1,8},{-4,9},
1489: {-2,10},{-1,12},{-1,15},{-2,18},{-3,20},{-5,21},{-6,20},
1490: {-6,18},{-5,15},{-2,12},{1,10},{5,7},{8,4},
1491: };
1492: static XPoint *char676[] = {
1493: seg0_676,
1494: NULL,
1495: };
continues
590 Part VIII Appendixes
continues
596 Part VIII Appendixes
continues
598 Part VIII Appendixes
continues
604 Part VIII Appendixes
continues
606 Part VIII Appendixes
continues
608 Part VIII Appendixes
continues
610 Part VIII Appendixes
continues
614 Part VIII Appendixes
continues
616 Part VIII Appendixes
continues
618 Part VIII Appendixes
continues
620 Part VIII Appendixes
continues
622 Part VIII Appendixes
3197: XtNumber(seg15_3008),XtNumber(seg16_3008),XtNumber(seg17_3008),
3198: XtNumber(seg18_3008),XtNumber(seg19_3008),XtNumber(seg20_3008),
3199: XtNumber(seg21_3008),XtNumber(seg22_3008),XtNumber(seg23_3008),
3200: XtNumber(seg24_3008),XtNumber(seg25_3008),XtNumber(seg26_3008),
3201: };
3202: static XPoint seg0_3009[] = {
3203: {-1,-12},{-1,9},
3204: };
3205: static XPoint seg1_3009[] = {
3206: {0,-11},{0,8},
3207: };
3208: static XPoint seg2_3009[] = {
3209: {1,-12},{1,9},
3210: };
3211: static XPoint seg3_3009[] = {
3212: {-4,-12},{4,-12},
3213: };
3214: static XPoint seg4_3009[] = {
3215: {-4,9},{4,9},
3216: };
3217: static XPoint seg5_3009[] = {
3218: {-3,-12},{-1,-11},
3219: };
3220: static XPoint seg6_3009[] = {
3221: {-2,-12},{-1,-10},
3222: };
3223: static XPoint seg7_3009[] = {
3224: {2,-12},{1,-10},
3225: };
3226: static XPoint seg8_3009[] = {
3227: {3,-12},{1,-11},
3228: };
3229: static XPoint seg9_3009[] = { C
3230: {-1,8},{-3,9},
3231: };
3232: static XPoint seg10_3009[] = {
3233: {-1,7},{-2,9},
3234: };
3235: static XPoint seg11_3009[] = {
3236: {1,7},{2,9},
3237: };
3238: static XPoint seg12_3009[] = {
3239: {1,8},{3,9},
3240: };
3241: static XPoint *char3009[] = {
3242: seg0_3009,seg1_3009,seg2_3009,seg3_3009,seg4_3009,
3243: seg5_3009,seg6_3009,seg7_3009,seg8_3009,seg9_3009,
3244: seg10_3009,seg11_3009,seg12_3009,
3245: NULL,
3246: };
3247: static int char_p3009[] = {
3248: XtNumber(seg0_3009),XtNumber(seg1_3009), XtNumber(seg2_3009),
3249: XtNumber(seg3_3009),XtNumber(seg4_3009), XtNumber(seg5_3009),
continues
624 Part VIII Appendixes
continues
626 Part VIII Appendixes
continues
630 Part VIII Appendixes
continues
632 Part VIII Appendixes
continues
634 Part VIII Appendixes
continues
636 Part VIII Appendixes
continues
638 Part VIII Appendixes
continues
640 Part VIII Appendixes
continues
646 Part VIII Appendixes
continues
648 Part VIII Appendixes
continues
652 Part VIII Appendixes
continues
654 Part VIII Appendixes
4950: };
4951: static XPoint seg0_3063[] = {
4952: {-5,-12},{-11,8},
4953: };
4954: static XPoint seg1_3063[] = {
4955: {-5,-11},{-4,7},{-4,9},
4956: };
4957: static XPoint seg2_3063[] = {
4958: {-4,-12},{-3,7},
4959: };
4960: static XPoint seg3_3063[] = {
4961: {-3,-12},{-2,6},
4962: };
4963: static XPoint seg4_3063[] = {
4964: {9,-12},{-2,6},{-4,9},
4965: };
4966: static XPoint seg5_3063[] = {
4967: {9,-12},{3,9},
4968: };
4969: static XPoint seg6_3063[] = {
4970: {10,-12},{4,9},
4971: };
4972: static XPoint seg7_3063[] = {
4973: {11,-12},{5,9},
4974: };
4975: static XPoint seg8_3063[] = {
4976: {-8,-12},{-3,-12},
4977: };
4978: static XPoint seg9_3063[] = {
4979: {9,-12},{14,-12},
4980: };
4981: static XPoint seg10_3063[] = {
4982: {-14,9},{-8,9}, C
4983: };
4984: static XPoint seg11_3063[] = {
4985: {0,9},{8,9},
4986: };
4987: static XPoint seg12_3063[] = {
4988: {-7,-12},{-5,-11},
4989: };
4990: static XPoint seg13_3063[] = {
4991: {-6,-12},{-5,-10},
4992: };
4993: static XPoint seg14_3063[] = {
4994: {12,-12},{10,-10},
4995: };
4996: static XPoint seg15_3063[] = {
4997: {13,-12},{10,-11},
4998: };
4999: static XPoint seg16_3063[] = {
5000: {-11,8},{-13,9},
5001: };
continues
658 Part VIII Appendixes
continues
660 Part VIII Appendixes
continues
662 Part VIII Appendixes
continues
664 Part VIII Appendixes
5362: seg10_3070,seg11_3070,seg12_3070,seg13_3070,seg14_3070,
5363: seg15_3070,seg16_3070,seg17_3070,
5364: NULL,
5365: };
5366: static int char_p3070[] = {
5367: XtNumber(seg0_3070), XtNumber(seg1_3070), XtNumber(seg2_3070),
5368: XtNumber(seg3_3070), XtNumber(seg4_3070), XtNumber(seg5_3070),
5369: XtNumber(seg6_3070), XtNumber(seg7_3070), XtNumber(seg8_3070),
5370: XtNumber(seg9_3070), XtNumber(seg10_3070),XtNumber(seg11_3070),
5371: XtNumber(seg12_3070),XtNumber(seg13_3070),XtNumber(seg14_3070),
5372: XtNumber(seg15_3070),XtNumber(seg16_3070),XtNumber(seg17_3070),
5373: };
5374: static XPoint seg0_3071[] = {
5375: {-4,-12},{-7,-1},{-8,3},{-8,6},{-7,8},{-4,9},
5376: {0,9},{3,8},{5,6},{6,3},{10,-11},
5377: };
5378: static XPoint seg1_3071[] = {
5379: {-3,-12},{-6,-1},{-7,3},{-7,7},{-6,8},
5380: };
5381: static XPoint seg2_3071[] = {
5382: {-2,-12},{-5,-1},{-6,3},{-6,7},{-4,9},
5383: };
5384: static XPoint seg3_3071[] = {
5385: {-7,-12},{1,-12},
5386: };
5387: static XPoint seg4_3071[] = {
5388: {7,-12},{13,-12},
5389: };
5390: static XPoint seg5_3071[] = {
5391: {-6,-12},{-3,-11},
5392: };
5393: static XPoint seg6_3071[] = {
5394: {-5,-12},{-4,-10}, C
5395: };
5396: static XPoint seg7_3071[] = {
5397: {-1,-12},{-3,-10},
5398: };
5399: static XPoint seg8_3071[] = {
5400: {0,-12},{-3,-11},
5401: };
5402: static XPoint seg9_3071[] = {
5403: {8,-12},{10,-11},
5404: };
5405: static XPoint seg10_3071[] = {
5406: {12,-12},{10,-11},
5407: };
5408: static XPoint *char3071[] = {
5409: seg0_3071,seg1_3071,seg2_3071,seg3_3071,seg4_3071,seg5_3071,
5410: seg6_3071,seg7_3071,seg8_3071,seg9_3071,seg10_3071,
5411: NULL,
5412: };
5413: static int char_p3071[] = {
5414: XtNumber(seg0_3071),XtNumber(seg1_3071),XtNumber(seg2_3071),
continues
666 Part VIII Appendixes
continues
668 Part VIII Appendixes
continues
674 Part VIII Appendixes
continues
676 Part VIII Appendixes
continues
680 Part VIII Appendixes
continues
682 Part VIII Appendixes
continues
686 Part VIII Appendixes
6497: NULL,
6498: };
6499: static int char_p3117[] = {
6500: XtNumber(seg0_3117),XtNumber(seg1_3117),XtNumber(seg2_3117),
6501: XtNumber(seg3_3117),XtNumber(seg4_3117),XtNumber(seg5_3117),
6502: XtNumber(seg6_3117),XtNumber(seg7_3117),XtNumber(seg8_3117),
6503: XtNumber(seg9_3117),XtNumber(seg10_3117),
6504: };
6505: static XPoint seg0_3118[] = {
6506: {-4,-5},{-4,9},
6507: };
6508: static XPoint seg1_3118[] = {
6509: {-3,-4},{-3,8},
6510: };
6511: static XPoint seg2_3118[] = {
6512: {-7,-5},{-2,-5},{-2,9},
6513: };
6514: static XPoint seg3_3118[] = {
6515: {5,-3},{5,-4},{4,-4},{4,-2},{6,-2},{6,-4},{5,-5},{3,-5},
6516: {1,-4},{-1,-2},{-2,1},
6517: };
6518: static XPoint seg4_3118[] = {
6519: {-7,9},{1,9},
6520: };
6521: static XPoint seg5_3118[] = {
6522: {-6,-5},{-4,-4},
6523: };
6524: static XPoint seg6_3118[] = {
6525: {-5,-5},{-4,-3},
6526: };
6527: static XPoint seg7_3118[] = {
6528: {-4,8},{-6,9},
6529: }; C
6530: static XPoint seg8_3118[] = {
6531: {-4,7},{-5,9},
6532: };
6533: static XPoint seg9_3118[] = {
6534: {-2,7},{-1,9},
6535: };
6536: static XPoint seg10_3118[] = {
6537: {-2,8},{0,9},
6538: };
6539: static XPoint *char3118[] = {
6540: seg0_3118,seg1_3118,seg2_3118,seg3_3118,seg4_3118,seg5_3118,
6541: seg6_3118,seg7_3118,seg8_3118,seg9_3118,seg10_3118,
6542: NULL,
6543: };
6544: static int char_p3118[] = {
6545: XtNumber(seg0_3118),XtNumber(seg1_3118),XtNumber(seg2_3118),
6546: XtNumber(seg3_3118),XtNumber(seg4_3118),XtNumber(seg5_3118),
6547: XtNumber(seg6_3118),XtNumber(seg7_3118),XtNumber(seg8_3118),
6548: XtNumber(seg9_3118),XtNumber(seg10_3118),
6549: };
continues
688 Part VIII Appendixes
continues
690 Part VIII Appendixes
continues
692 Part VIII Appendixes
continues
694 Part VIII Appendixes
continues
696 Part VIII Appendixes
continues
700 Part VIII Appendixes
continues
702 Part VIII Appendixes
continues
704 Part VIII Appendixes
7425: {3,-4},{1,-2},{-1,2},
7426: };
7427: static XPoint *char3168[] = {
7428: seg0_3168,seg1_3168,seg2_3168,seg3_3168,
7429: NULL,
7430: };
7431: static int char_p3168[] = {
7432: XtNumber(seg0_3168),XtNumber(seg1_3168),XtNumber(seg2_3168),
7433: XtNumber(seg3_3168),
7434: };
7435: static XPoint seg0_3169[] = {
7436: {6,-2},{6,-3},{5,-3},{5,-1},{7,-1},{7,-3},
7437: {6,-4},{3,-5},{0,-5},{-3,-4},{-4,-3},{-4,-1},{-3,1},{-1,2},
7438: {2,3},{4,4},{5,6},
7439: };
7440: static XPoint seg1_3169[] = {
7441: {-3,-4},{-4,-1},
7442: };
7443: static XPoint seg2_3169[] = {
7444: {-3,0},{-1,1},{2,2},{4,3},
7445: };
7446: static XPoint seg3_3169[] = {
7447: {5,4},{4,8},
7448: };
7449: static XPoint seg4_3169[] = {
7450: {-4,-3},{-3,-1},{-1,0},{2,1},{4,2},{5,4},{5,6},{4,8},
7451: {1,9},{-2,9},{-5,8},{-6,7},{-6,5},{-4,5},{-4,7},{-5,7},
7452: {-5,6},
7453: };
7454: static XPoint *char3169[] = {
7455: seg0_3169,seg1_3169,seg2_3169,seg3_3169,seg4_3169,
7456: NULL,
7457: }; C
7458: static int char_p3169[] = {
7459: XtNumber(seg0_3169),XtNumber(seg1_3169),XtNumber(seg2_3169),
7460: XtNumber(seg3_3169),XtNumber(seg4_3169),
7461: };
7462: static XPoint seg0_3170[] = {
7463: {2,-12},{-1,-1},{-2,3},{-2,6},{-1,8},{0,9},{2,9},{4,7},
7464: {5,5},
7465: };
7466: static XPoint seg1_3170[] = {
7467: {3,-12},{0,-1},{-1,3},{-1,8},
7468: };
7469: static XPoint seg2_3170[] = {
7470: {2,-12},{4,-12},{0,2},{-1,6},
7471: };
7472: static XPoint seg3_3170[] = {
7473: {-4,-5},{6,-5},
7474: };
7475: static XPoint *char3170[] = {
7476: seg0_3170,seg1_3170,seg2_3170,seg3_3170,
7477: NULL,
7478: };
continues
706 Part VIII Appendixes
continues
708 Part VIII Appendixes
continues
710 Part VIII Appendixes
continues
712 Part VIII Appendixes
continues
714 Part VIII Appendixes
continues
716 Part VIII Appendixes
continues
718 Part VIII Appendixes
continues
720 Part VIII Appendixes
continues
722 Part VIII Appendixes
continues
724 Part VIII Appendixes
continues
726 Part VIII Appendixes
continues
728 Part VIII Appendixes
continues
730 Part VIII Appendixes
continues
732 Part VIII Appendixes
continues
736 Part VIII Appendixes
last in, first out (LIFO) less than symbol (<), 53 line_save function, 402
lists, 481 lexical analysis, 56, 98-99 line_save method, 403
last-line mode, 24 libraries, 29 line_scale method, 242,
Latex Line icon, 380 locating, 30 398-399
layout (user interfaces), names, determining, 29 line_width field, 187, 510
262. See also structure LIFO (last in, first out) linear equations, 217
aesthetics, 264-265 lists, 481 LineDashed value, 511
feature lists, 263-264 line attributes, 510 LineDoubleDashed value,
Graphics Editor features, Graphics Editor, 510 511
263-264 line style, 511 lines
gxArc.c file (Graphics line width, 510-511 Graphics Editor, 226-231
Editor), 288-289 line intersections, calculat- multiple, wrapping, 33
gxGraphics.c file ing, 212-216 objects, scaling, 235-238
(Graphics Editor), Line object, 305 rotating, 248-251
271-276 bounds, calculating, slope, 216
gxGraphics.h header file 204-207 tagged, 365
(Graphics Editor), converting Box objects to, LineSolid value, 511
268-269 415 lineto PostScript operator,
gxGx.c file (Graphics copying, 400-401 486
Editor), 285-288
creating, 385, 388 link (ln) command, 44
gxIcons.h header file
deselecting, 394-395 linked lists, 318, 346-347
(Graphics Editor),
drawing/erasing, 388-390 adding objects, 387
276-284
finding, 390-392 creating, 318
gxMain.c file (Graphics
handles, creating, 392-394 heads, 318
Editor), 265-271,
284-285 moving, 395, 397 pointers, managing,
gxProtos.h header file point-array base, 305 323-324
(Graphics Editor), restoring, 401-403 links, 15, 43-44, 542
268-271 saving, 401-402 Linux, 8
gxText.c file (Graphics scaling, 398-400 case-sensitivity, 18
Editor), 289 selecting, 392, 395 file system, navigating, 18
header files (Graphics tagged line, 365 make utility, 24-25
Editor), 266-267 line_copy function, X Clients, 117
intuitiveness, 264-265 400-401 listing (ls) command, 14,
planning, 264-265 line_find function, 390 527
LeaveNotify event, 116, line_find method, 392 lists
119 line_from_box conversion arguments, 146
length routine, 413 feature lists (user inter-
arrays, 60, 311 line_from_box function, faces), 263-264
handle bit data array, 329 415 LIFO, 481
less than or equal to sym- line_move function, 396
bol (<=), 53 line_move method, 395
762 lists