home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
OS/2 Professional
/
OS2PRO194.ISO
/
os2
/
info
/
prgramer
/
edmi
/
issue_1
/
edmi1.inf
(
.txt
)
Wrap
OS/2 Help File
|
1993-02-28
|
121KB
|
2,973 lines
ΓòÉΓòÉΓòÉ 1. Title Page ΓòÉΓòÉΓòÉ
Welcome to EDM/2 - The Electronic OS/2 Developers' Magazine!.
Portions Copyright (C)1993 by Steve Luzynski.
Issue #1 - March 1993.
(Press 'Forward' to page.)
ΓòÉΓòÉΓòÉ 2. Copyright Notice and other Legal Stuff ΓòÉΓòÉΓòÉ
EDM/2 Copyright (C)1993 by Steve Luzynski. This publication may be freely
distributed in electronic form provided that all parts are present in their
original unmodified form. A reasonable fee may be charged for the physical act
of distribution; no fee may be charged for the publication itself.
All articles are copyrighted by their authors. No part of any article may be
reproduced without permission from the original author.
Neither this publication nor Steve Luzynski is affiliated with International
Business Machines Corporation.
OS/2 is a registered trademark of International Business Machines Corporation.
Other trademarks are property of their respective owners. Any mention of a
product in this publication does not constitute an endorsement or affiliation
unless specifically stated in the text.
ΓòÉΓòÉΓòÉ 3. From the Editor ΓòÉΓòÉΓòÉ
Hello, and welcome to the first issue of EDM/2, the Electronic Developer's
Magazine!
This project was the result of my own struggle to get information on
programming OS/2, particularly in the case of writing Presentation Manager
programs. What information there is available seems to be spread out amongst
.INF files, books written for OS/2 1.3, and old magazine articles. My goal was
to provide a single resource where developers, both new and old, could get
information on topics ranging from "How do I create a window?" to "How do I
write an IFS?"
The tone of this magazine may be a bit irreverent at times - not all computer
programmers are antisocial hermits who only leave their rooms to get more
Doritos (that's just me). Consider yourself warned. (insert smile here)
In later issues, I'll use this space to actually talk about topics related to
developing for OS/2. This time, however, I have some administrative business to
get off my chest...
Contributions
If you have an idea for an article, by all means contact me! Since this
magazine is targeted at developers of all levels, no topic is too general or
too esoteric to be appreciated by someone. You can generally assume basic
familiarity with the C programming language - beyond that, use your judgement.
Someone reading an article on writing device drivers will probably be more
advanced than someone reading about resource files.
One area I would like to see covered that hasn't been in this issue is the
commercial developer's software that is available out there. Reviews of Zortech
C++, IBM C Set/2, etc. would make nice inclusions.
Money
This magazine is free for the taking. All of the material is copyrighted by the
person who wrote it - the authors retain all rights to the text as well as
their code unless stated otherwise. I can't pay them for their efforts, so by
leaving their work as theirs I leave open the possibility for paid reprints in
traditional paper works.
Since I really could in no way control the random distribution of this work,
I'm not even attempting to charge for it. However, if you do feel a deep seated
desire to pay someone for it, by all means feel free to send your donation:
o to me. Your money will all be pushed back into the magazine in the form of
storage space, Compuserve fees, etc. My address can be located by emailing me
at the address listed at the end of this article.
o to the Free Software Foundation. They are responsible for emx/gcc and a lot
more really nice FREE packages available for a variety of machines. They also
have administrative overhead just like any other organization. They can be
reached at:
Free Software Foundation, Inc.
675 Mass Ave
Cambridge, MA 02139, USA
Etc
This articles presented in this magazine were written by people with a wide
variety of expertise in the use of IPF (the Information Presentation Facility).
Because of this, the style of the articles is different for each one - from a
straightforward scrolling window to multilevel windows with custom icons and
hotlinks to glossary items. As we all get better at this, eventually everything
will look as nice as Gavin's article. (editorial smile) Please bear with me
until then and let me know if you have any suggestions on style or format. But
enough from me. Let's get to it.
-Steve Luzynski, Editor.
sal8@po.cwru.edu
ΓòÉΓòÉΓòÉ 4. This Month's Features ΓòÉΓòÉΓòÉ
o Getting Started with emx/gcc
o The Making of MineSweeper
o Palette Manager
o Advanced GPI:Retained Segments and Transformations
ΓòÉΓòÉΓòÉ 4.1. Getting Started with emx/gcc ΓòÉΓòÉΓòÉ
Installing EMX
A Free 'C' Compiler
for OS/2 2.0.
ΓòÉΓòÉΓòÉ 4.1.1. Introduction ΓòÉΓòÉΓòÉ
(Note: This document was originally written by Brooke P. Anderson. It has been
modified slightly to conform to the .INF format. The original may be obtained
from the same source as emx itself.)
Introduction
This document describes a free GNU software-development system for OS/2 v2.0.
It tells you how to acquire all of the necessary software, how to install it,
how to start using it, and where to look for more information.
The GNU software-development system includes a C and C++ compiler, a debugger,
an assembler, a make utility (for automating the compilation of programs made
of many source files), and a hypertext reader (for reading the documentation).
The compiler generates full 32-bit, optimized code, and it supports all of the
OS/2 API calls, so you software developers out there can use it for PM
programming, manipulating semaphores, manipulating threads, using named pipes,
etc. Optional packages include curses (a standard library for manipulating
screenfuls of text and for moving the cursor), a collection of sample programs,
and full source code for the system.
GNU software is originally developed by the Free Software Foundation, an
organization which produces a lot of free software for UNIX. After the Free
Software Foundation releases the UNIX versions, people often port them to many
other operation systems (such as OS/2). Despite the fact that the software is
free, the UNIX community considers it a standard and often prefers it over
other products because of its high quality. The compilers, for example,
produce well-optimized code.
Sometimes, there is more than one port of a GNU program. For OS/2, there are
two different ports of the GNU compiler (called "gcc"). This document
discusses only one of them (the EMX port) since the EMX port provides faster
floating point routines and works with a debugger. This document deals with
version 0.8f of the EMX port, which is the latest version. People frequently
produce new versions of and enhancements for the GNU software and the ports
based on it.
IMPORTANT: I have not tested the software on FAT file systems. I think it
will work, but I have not checked all of the file names to make sure they
comply with the egregious 8.3 file-naming convention.
The following topics may be selected by clicking on the individual topics of
interest or by simply pressing the 'Forward' button on the bottom of the
window.
How to get emx
How to install emx
UNZ50x32.EXE
WHLINF8F.ZIP
GNUMK362.ZIP
GNUDEV.ZIP, EMXDEV.ZIP, GPPDEV.ZIP, and OBJCDEV.ZIP
The final steps
Using info
Using the compiler
Using the debugger
Using make
Using the assembler
Where to get more information
Conclusions
Optional packages
Sources of distribution
Distributing this document
ΓòÉΓòÉΓòÉ 4.1.1.1. How to get emx ΓòÉΓòÉΓòÉ
The full software-development system (and the various optional packages
described later in this document) are available from a variety of sources. If
you have access to Internet, you can get the files from anonymous-ftp sites.
In the USA, the main anonymous-ftp site for OS/2 is ftp-os2.nmsu.edu. In
Germany, the main anonymous-ftp site for OS/2 is rusinfo.rus.uni-stuttgart.de.
Also, see the end of this document for a list of people who are willing to
distribute the whole system through regular mail.
You need to obtain the following files (the sizes of the files are listed next
to the names): emxdev.zip (620k), gnudev.zip (873k), gppdev.zip (944k),
gobjcdev.zip (426k), gnumk362.zip (255k), whlinf8f.zip (814k), and unz50x32.exe
(114k). This file (emxst31.doc) is available in emxst31.zip (10k).
On ftp-os2.nmsu.edu, these files are available in
pub/os2/2.0/programming/emx-0.8f -- except for emxst31.zip, whlinf8f.zip, and
the correct version of gnumk362.zip, which are temporarily in pub/uploads. On
rusinfo.rus.uni-stuttgart.de, check in pub/os2/emx-0.8f.
ΓòÉΓòÉΓòÉ 4.1.1.2. How to install it ΓòÉΓòÉΓòÉ
The following subsections describe how to install the various pieces of the
GNU software-development system. Go through the procedures step by step as
described -- the order is important. Don't feel compelled to read through any
of the readme files or other documentation spit out during the unarchiving
process -- I think you will have a much easier time if you go through this
document beforehand.
ΓòÉΓòÉΓòÉ 4.1.1.3. UNZ50X32.EXE ΓòÉΓòÉΓòÉ
You will need unzip v5.0 to unarchive the files. Earlier versions of unzip
(including PKZip versions earlier than 1.9) will not work.
To unarchive unz50x32.exe, you simply move it to a convenient directory and
type "unz50x32". Add the name of the directory unzip is in to your path. For
example, I have unzip in c:\apps\unzip, so I appended "c:\apps\unzip;" to the
"SET PATH" statement in my config.sys file. Then reboot. Now, you can
unarchive any zip archive by typing "unzip filename", where "filename.zip" is
the name of the archive.
ΓòÉΓòÉΓòÉ 4.1.1.4. WHLINF8F.ZIP ΓòÉΓòÉΓòÉ
Copy whlinf8f.zip to any convenient directory and unarchive it. It will
disgorge a program called "info.exe" (the hypertext reader), several auxiliary
files, and a bunch of documentation files for itself, the compiler, the
debugger, and make.
ΓòÉΓòÉΓòÉ 4.1.1.5. GNUMK362.ZIP ΓòÉΓòÉΓòÉ
Copy gnumk362.zip to any convenient directory and unarchive it. The archive
will create the directory ".\make" and disgorge several files into it. In
other words, if you unarchive gnumk362.zip in the directory "\apps", the
process will create the directory "\apps\make". You can delete the info
directory (the one in the make directory) as its contents are duplicated in
whlinf8f.zip.
Add the name of the directory make is in to your path statement in config.sys.
(I have make in c:\apps\make, so I appended "c:\apps\make;" to the "SET PATH"
statement in my config.sys.)
ΓòÉΓòÉΓòÉ 4.1.1.6. GNUDEV.ZIP, EMXDEV.ZIP, GPPDEV.ZIP, and OBJCDEV.ZIP ΓòÉΓòÉΓòÉ
These archives create the directory ".\emx" and a bunch of directories under
that. Thus, unarchive the files from the directory in which you want this emx
directory. (In other words, if you unarchive gnudev.zip in the directory
"\apps", the process will create \apps\emx, \apps\emx\bin, \apps\emx\lib, and
many other such directories.)
ΓòÉΓòÉΓòÉ 4.1.1.7. The final steps ΓòÉΓòÉΓòÉ
Now, you need to modify your config.sys file. The examples below are from my
config.sys file, and I have the emx directory installed under c:\apps. Thus,
my system has the directories "c:\apps\emx\dll", "c:\apps\emx\lib",
"c:\apps\emx\include", "c:\apps\emx\bin", and so on -- you will need to modify
the following examples so that the directories are specified correctly.
First, you need to specify in your libpath the location of emx.dll and other
dll files. In my config.sys, I have
LIBPATH=.;C:\OS2\DLL;C:\OS2\MDOS;C:\; C:\OS2\APPS\DLL;c:\apps\emx\dll;
Second, add the name of the emx\bin directory to your path statement in
config.sys. (I appended "c:\apps\emx\bin;" to the "SET PATH" statement in my
config.sys.)
Third, you need to set a few environmental variables so that the compiler
knows where to find and how to use various files:
set C_INCLUDE_PATH=c:/apps/emx/include
set LIBRARY_PATH=c:/apps/emx/lib
set CPLUS_INCLUDE_PATH=C:/apps/emx/include.cpp;C:/apps/emx/include
set PROTODIR=c:/apps/emx/include.cpp/gen
set OBJC_INCLUDE_PATH=c:/apps/emx/include
set TERM=mono
set TERMCAP=c:/apps/emx/etc/termcap.dat
IMPORTANT: you need to use forward slashes ("/") and not backward slashes
("\") when setting these environmental variables. However, do NOT use forward
slashes in your libpath statement.
Now, reboot your machine so that these definitions take effect. Then, go into
an OS/2 window and type "cd \apps\emx\lib" (or whatever you would type to get
to emx\lib), and type "omflib". This completes the installation process.
ΓòÉΓòÉΓòÉ 4.1.1.8. Using info ΓòÉΓòÉΓòÉ
Go into the directory in which info.exe resides. Type "info". You are now
looking at a screen that has some information on the top half (information such
as "Typing 'd' returns here, 'q' quits, '?' lists all info commands, 'h' gives
a primer for first-timers . . .") and a list of subjects near the bottom
(subjects such as "Info", "Make," "Gcc", "Gdb", etc.).
Go ahead and type "h" for a tutorial. The most basic functions to remember
are: type "q" to quit, type "?" to get a list of commands, hit the space bar
or PgDn key to go down a page, press the Del key or the PgUp key to go up a
page, press "n" to go to the next node (think of a node as being a collection
of one or more pages dealing with a single topic), press "p" to go to the
previous node, use the up and down arrow keys to highlight choices of new nodes
to jump to, and press enter to jump to the highlighted node.
Just play around with it a little while, and you will get the hang of it.
Type "d" to get back to the main directory, use the down arrow to highlight
"Gcc", press enter, press the down arrow to highlight "Contributors", press
enter, scan through the pages of text by hitting the PgDn key a couple of
times, type "?" to see a list of commands, type "p" a couple of times, etc.
ΓòÉΓòÉΓòÉ 4.1.1.9. Using the compiler ΓòÉΓòÉΓòÉ
To compile a C source file called "myprog.c", type "gcc -o myprog.exe
myprog.c". The -o switch tells gcc that it should call the resulting
executable "myprog.exe". To compile the C++ source file "myprog.cc", type "gcc
-o myprog.exe myprog.cc -lgpp". C++ source files should have the extension
".cc". The -lgpp switch tells gcc to link the C++ libraries. You can also
tell gcc to optimize your code by using the -O switch. There are two levels of
optimization (-O and -O2, the highest being -O2). Thus, for the
fastest-executing code (at the expense of time to compile and of size of the
executable), you would type "gcc -O2 -o myprog.exe myprog.c" for myprog.c and
"gcc -O2 -o myprog.exe myprog.cc -lgpp" for myprog.cc.
Note: Specifying "-o myprog.exe" is important. If you don't specify ".exe"
as the suffix of the output file name, the compiler will generate a UNIX-style
executable which will not run under OS/2 even if you subsequently rename the
file so that it has a .exe extension.
ΓòÉΓòÉΓòÉ 4.1.1.10. Using the debugger ΓòÉΓòÉΓòÉ
To debug a program, you need to compile it with the -g switch: "gcc -g -o
myprog.exe myprog.c" for myprog.c and likewise for myprog.cc. After compiling,
you then type "gdb myprog.exe" to start the debugger. Type "h" at the debugger
prompt to get a list of help topics. gdb supports all sorts of breakpoints
(including conditional ones), and you can watch variables, set variables to
different values, etc. For example, to get help on running programs from
within the gdb, you can type "help running". That will give you a list of
commands such as run, kill, step, etc. You can get help on individual commands
by typing, for example, "help step".
The following is a sample compile and debug session. Go into the emx\test
directory and type "gcc -g -o hello.exe hello.cc -lgpp" to compile hello.cc.
Type "hello" to run it, to see if the compiler is functioning. The program
will print "Hello, world!" on the screen. Now type "gdb hello.exe" to start
gdb. At the prompt "(gdb)", type "list main" to list the function "main". Then
type "break 5" to cause execution to stop at line 5 in the main function. Type
"run" to start execution -- it will stop at line 5. Type "print argc" to see
the value of the variable "argc". Type "step" to run line 5 then halt
execution at the next line. Type "quit" to quit. To list a function (main(),
say) that is longer than a screenful, type "list main"; then type "list" again
to list the next screenful of main.
ΓòÉΓòÉΓòÉ 4.1.1.11. Using make ΓòÉΓòÉΓòÉ
Assume you have a program made of the following source files: "myprog1.c" and
"myprog2.c". You might manually compile these files by typing "gcc -o
myprog.exe myprog1.c myprog2.c". Of course, if you change only one of the
files, typing such a command causes the recompiling of both source files.
During development, this could be tiresome if the files take a long time to
compile. A better way would be to type "gcc -c myprog1.c" which compiles
myprog1.c into the object file "myprog1.o" (the -c switch tells gcc to make an
object file), then to type "gcc -c myprog2.c" to generate myprog2.o, and
finally to type "gcc -o myprog.exe myprog1.o myprog2.o". This way, if you
change only myprog1.c, you can recompile it and relink it with myprog2.o to
create myprog.exe (skipping the "gcc -c myprog2.c" step). This will be much
faster if the source files take a long time to compile (or if you have a lot of
source files).
Of course, doing all this typing is tiresome, too. Also, if myprog1.c happens
to depend on myprog1.h, and you change myprog1.h, you must recompile myprog1.c.
Thus, you have to keep track of all the file dependencies in order to know,
after changing one header file, which other files need to be recompiled.
Fortunately, make takes care of all of this automatically. All you have to do
is create one text file that describes the various dependencies and the various
steps to compile the program. You name the text file "Makefile". From then
on, whenever you type "make", make examines the Makefile, looks for files which
have changed since the last compile, recompiles any files which depend on the
changed files, and relinks everything into a new executable.
For example, suppose that myprog.exe is made from myprog1.c and myprog2.c,
that myprog1.c contains the lines "#include "myprog1.c"" and "#include
"mainhead.h"", and that myprog2.c includes myprog2.h and mainhead.h. The
Makefile describing all of this is
myprog.exe: myprog1.o myprog2.o
gcc -o myprog.exe myprog1.o myprog2.o
myprog1.o: myprog1.c myprog1.h mainhead.h
gcc -c myprog1.c
myprog2.o: myprog2.c myprog2.h mainhead.h
gcc -c myprog2.c
The first line shows that myprog.exe depends on myprog1.o and myprog2.o. If
either of those has changed since the last time make was invoked, make will
relink them to create myprog.exe by giving the command under the first line.
The fourth line shows that myprog1.o depends on myprog1.c, myprog1.h, and
mainhead.h. If any of these three files have changed since the last time make
was run, make will recompile myprog1.o by issuing the command on line five. It
will also realize that myprog.o has changed, that myprog.exe depends on
myprog.o, and will relink myprog.exe. If mainhead.h is changed, make will
recompile and relink everything since myprog1.o needs to be changed, myprog2.o
needs to be changed, and thus myprog.exe needs to be changed.
The example above shows the general form of a Makefile. You give a target
(like "myprog.exe" or "myprog1.o") followed by a colon, followed by a space,
followed by a space-delimited list of files the target depends on. The next
line specifies the action to be taken when any of the dependencies change: the
first character MUST be a tab (not just a bunch of spaces used for
indentation); then you type the command make should issue. A Makefile is just
a list of such targets, dependencies, and actions.
ΓòÉΓòÉΓòÉ 4.1.1.12. Using the assembler ΓòÉΓòÉΓòÉ
The compiler provided with this system (gcc) can handle not only C and C++
code but assembly as well. It can also generate assembly language from C or
C++ source. To assemble and link assembly-language programs, type "gcc -o
myprog.exe myprog.s" where "myprog.exe" is the name of the executable and where
"myprog.s" is the name of the source-code file. Assembly-language source-code
files should have the extension ".s". To generate assembly from C code, type
"gcc -S myprog.c" where "myprog.c" is the name of the C-source-code file -- the
resulting assembly language will be put into the file "myprog.s".
ΓòÉΓòÉΓòÉ 4.1.1.13. Where to get more information ΓòÉΓòÉΓòÉ
The GNU software-development system does not come with documentation like that
you would get with, for example, Borland C++. However, emxdev.doc (in the
emx\doc directory) does contain a complete list of library functions, including
a list of headers you need to include, what the functions do, and what
parameters they accept.
Also, since the C compiler is ANSI-C compliant (or at least close to it) and
since the C++ compiler is close to AT&T-C++-2.0 compliant, you can use just
about any reference manuals for ANSI C and AT&T C++ 2.0. I use the ones I got
with an old version of Borland Turbo C++. If you don't have such manuals, you
should be able to find something suitable in a bookstore. If you want a C
reference manual, I recommend C: A REFERENCE MANUAL, by S. P. Harbison and G.
L. Steele, Jr. (Prentice-Hall, 1991). If you are just learning C or C++, there
is a large number of books to choose from, and you shouldn't have any trouble
finding one that is suitable.
For those of you developing applications that use the PM or that use special
OS/2 functions, the system DOES support all of the OS/2 API functions,
including ones for semaphores, PM programming, named pipes, threads, etc., and
it supports Kbd, Mou, and Vio functions. See emxdev.doc (in the emx\doc
directory) for a list of the supported functions. The documentation does not
contain a manual on how to use these API calls -- you need an OS/2 programming
book for that. For information on programming the PM, take a look at:
o OS/2 2.0 Presentation Manager GPI: A Programming Guide to Text, Graphics, and
Printing, by G. C. E. Winn (Van Norstrand Reinhold, 1992)
o Learning to Program OS/2 2.0 Presentation Manager by Example: Putting the
Pieces Together, by Stephen Knight (Van Norstrand Reinhold, 1992).
o Programming the OS/2 Presentation Manager, by Charles Petzhold (Microsoft
Press, 1989).
The book by Petzhold was written for OS/2 1.3, but the information in it is
still valid. You can also get the IBM redbooks (which are quite economical and
of which there is a large assortment of titles).
For using assembly language, the best choice would be a book about
assembly-language programming in OS/2, supplemented perhaps with a book on
programming the 80386 or 80486.
Also, way back when you were unarchiving, you might have been itching to
examine the various readme files and other documentation. Now is the time to
do that to your heart's content. Browse through the files in the emx/doc
directory and the information available from the hypertext reader.
Additional sources of information include GEnie (the OS/2 category of the
IBMPC bulletin board), Usenet news (the comp.os.os2 newsgroups), and
CompuServe. These are places where you can exchange information with other
people who use and program for OS/2. In particular, CompuServe is one of the
official homes for the OS/2 developers' assistance program. If you are a
member of the program, IBM will (for only $15) provide you with a CD that
contains a beta version of the software development kit (including the C and
C++ compiler and debugger and a full set of on-line documentation), a beta
version of OS/2 (which contains enhancements such as Windows 3.1
compatibility), and many other goodies. For more information, type "go os2dap"
on CompuServe or call 1-407-982-6408. The people at 1-800-3-ibm-os2 might also
be able to provide more information.
ΓòÉΓòÉΓòÉ 4.1.1.14. Conclusions ΓòÉΓòÉΓòÉ
I wrote this to help people get started with a free -- yet powerful -- 32-bit
software-development system for OS/2. For (at most) the price of a few books,
you have a full programming system for C++, C, and assembly language. For the
additional price of an OS/2 programming book, you have a bargain-basement SDK.
If you find errors in this document, or if you have suggestions for its
improvement, please let me know. My GEnie address is "BROOKE", and my Internet
address is "brooke@hope.caltech.edu".
ΓòÉΓòÉΓòÉ 4.1.1.15. Optional Packages ΓòÉΓòÉΓòÉ
There are three optional packages you can get for this software-development
system, packages which are not necessary but which can nevertheless be
important. All of them are in the pub/os2/2.0/programming/emx-0.8f directory
on ftp-os2.nmsu.edu. On rusinfo.rus.uni-stuttgart.de, they are probably
available in a directory such as pub/os2/emx-0.8f.
The first package contains curses. Curses is a library of functions that
allow you to move the cursor around on the screen, manipulate screenfuls of
text, and get input. You need the files "bsddev.zip" and "bsddoc.zip". The
source code is available in "bsdsrc.zip".
The second package contains full source code to the software-development
system. Most people do not need the source code to everything, but the source
code to the libraries (i.e., to all the functions) is sometimes useful. The
source code to the C libraries is in "emxlib.zip", and the source to the C++
libraries is in "gccsrc.zip". install.doc (in the emx\doc directory) gives the
names of all the other relevant archives.
The third package is a collection of sample and test programs that you can use
to test the compiler and to learn about various aspects of programming. These
programs are in the file "emxtest.zip."
ΓòÉΓòÉΓòÉ 4.1.1.16. Sources of Distribution ΓòÉΓòÉΓòÉ
This document already described some places from which you can get the
necessary archives: ftp-os2.nmsu.edu and rusinfo.rus.uni-stuttgart.de.
However, some people don't have access to these sites or don't have modems fast
enough to download megabytes of data in a reasonable amount of time. For these
people, I am including the following list of people who are willing to
distribute the whole system through regular mail. Keep in mind that people
might change their prices, cease distributing the software, move, etc., so
contact them first to get current details. Make sure you ask them if they have
the latest version of the EMX port (which is currently version 0.8f).
Brooke Anderson
1155 E. Del Mar #312
Pasadena, CA 91106
USA
Phone: (818) 577-7555
GEnie: BROOKE
Internet: brooke@hope.caltech.edu
Cost: $18 in US; in other countries, shipping + US$10
Provides: the basic set of archives and bsddev.zip, bsddoc.zip,
bsdsrc.zip, emxtest.zip, emxlib.zip, and gccsrc.zip.
Juergen Egeling
Werderstr. 41
7500 Karlsruhe
Germany
Phone: 0721-373842
FAX: 0721-373842
BITNET: ry90@dkauni2
Internet: ry90@ibm3090.rz.uni-karlsruhe.dbp.de
X.400: S=ry90;OU=ibm3090;OU=rz;P=uni-karlsruhe;A=dbp;C=de
Cost: disks + shipping + DM 25
Wey J. Ho
Department of Physics
Monash University
Clayton
VIC 3168
Australia
Phone: +613-565-3615 (or Australia (03) 565 3615)
Fax: +613-565-3637 (or Australia (03) 565 3637)
Internet: sci240s@monu6.cc.monash.edu.au
Cost: disks + shipping + AU$10
Doug Robison
1311 Webster
Chillicothe, MO 64601
USA
Phone: (816) 646-1085
GEnie: D.ROBISON
Cost: disks + shipping + US$5
If you would like to get on this list and if you have access to Internet or an
on-line service, just send me your name, a description of how people can
contact you (including your e-mail address), how much money you want for the
job (such as "$20", "disks + shipping + $30", or whatever you want to charge),
and what you are offering for that price. It is helpful to have a list that
includes people in various countries.
ΓòÉΓòÉΓòÉ 4.1.1.17. Distributing this Document ΓòÉΓòÉΓòÉ
I give permission to use, to distribute, and to copy this document freely. If
you want to upload it to any bulletin-board or on-line service, please do so.
I do update this document occasionally (and put the latest version on GEnie and
ftp-os2.nmsu.edu), so you might want to make sure you have the latest version
before distributing it.
Brooke Anderson
1155 E. Del Mar #312
Pasadena, CA 91106
USA
Phone: (818) 577-7555
GEnie: BROOKE
Internet: brooke@hope.caltech.edu
ΓòÉΓòÉΓòÉ 4.2. The Making of MineSweeper ΓòÉΓòÉΓòÉ
The Making of
MineSweeper
ΓòÉΓòÉΓòÉ 4.2.1. Introduction ΓòÉΓòÉΓòÉ
This document and its contents are copyright (C) 1993, by David Charlap.
This document describes some of the design decisions and problems that I
encountered when making my MineSweeper for OS/2 program. It assumes a basic
knowledge of C and Presentation Manager.
I decided to write an OS/2 version of MineSweeper for many various reasons.
The main reason was to learn a bit more about programming in the OS/2
presentation manager environment. I also wanted an OS/2 version of this game,
since loading the Windows environment is a slow procedure. Although there is
already a version of MineSweeper for OS/2 available, I found it to be lacking
features of the Microsoft game that I wanted.
I chose to develop MineSweeper using the GCC/2 compiler, since I already had it
installed on my computer at home. The code I have written should compile
without change on IBM's C Set/2 compiler or on the EMX/GCC compiler or on any
other OS/2-compatible compiler.
ΓòÉΓòÉΓòÉ 4.2.2. The game ΓòÉΓòÉΓòÉ
Before discussing the design, allow me to explain the basic concept of the
game.
MineSweeper is a game of logic, pitting the player against a clock. A grid of
squares is presented, some of which contain mines. All the squares are covered
at the game's outset. The object is to discover which covered squares contain
mines under them using clues provided by the program.
A player uncovers a square by clicking on it with the mouse. If the square
contains a mine, the player loses and play stops. If it does not contain a
mine, a number is displayed on the square which indicates the number of mines
that are adjacent to that square. When all the squares that do not contain
mines are uncovered, the player wins and play stops.
To prevent mistakes, a player may place a flag on a covered square to indicate
a suspected mine position. The computer will not allow a flagged square to be
uncovered. The player may also place a question mark on a square to aid in
visualizing the problem. The computer will allow a question-marked square to
be uncovered.
The game is timed.
ΓòÉΓòÉΓòÉ 4.2.3. The implementation ΓòÉΓòÉΓòÉ
While there are many ways to implement such a game, I chose to copy the
implementation that Microsoft used for their game. Partly because I was
already familiar with it, and partly because it is an intuitive approach to the
game.
ΓòÉΓòÉΓòÉ 4.2.3.1. How the game should behave ΓòÉΓòÉΓòÉ
The game is played in a window whose size is directly proportional to the size
of the game grid. When the grid's dimensions are changed, the window is
re-sized to accomodate the new grid. The top of the window contains the
current elapsed time, a count of the number of mines left to be flagged, and a
button that may be clicked for a quick restart of the game. Below this is the
game grid.
Covered squares are drawn in a way to resemble buttons.
A user uncovers a square by clicking on it with mouse button 1. When the button
is depressed over a covered square, the square is drawn as a depressed button.
If the mouse is dragged over the game grid, the "depressed" button will follow
the pointer. When the button is released, the square beneath it will be
uncovered.
If an uncovered square has no adjacent mines, the program will automatically
uncover all squares adjacent to it.
Flags and question marks are placed by clicking on covered squares with mouse
button 2. The marks are placed as soon as the button is pressed.
As a shortcut, and an aid to solving, mouse button 3 is used to uncover all the
squares around an uncovered square. When button 3 is clicked on an uncovered
square, and there are sufficient flags surrounding the square to satisfy the
number shown, all remaining squares will be uncovered. For two-button mouse
users, pressing buttons 1 and 2 together (chord) will also cause this effect.
When the shortcut button is pressed, the squares surrounding the pointer will
be drawn in the depressed position. As the mouse is dragged, the 3x3 area of
depressed squares will track the mouse. When button 3 (or either button 1 or
2, if the chord is used) is released, the shortcut process will begin.
ΓòÉΓòÉΓòÉ 4.2.3.2. How to code this ΓòÉΓòÉΓòÉ
Coding this game play was not simple. My first attempt involved creating
separate buttons for each square on the playfield. This way, the user could
just click on a button, and the program would receive a WM_COMMAND message
indicating which button was clicked.
This proved difficult for many reasons. First, it is hard to get a bitmap
image on the face of a button. Although I have seen it done, I do not know
how, and I did not want to take the time to learn. Second, and more important,
buttons to not behave properly. Ordinary buttons revert to their "up" state if
the mouse moves off of them while the mouse button is depressed, but there is
no facility provided to then depress the adjacent button that the pointer is
subsequently above. Also, facilities are not provided to have more than one
button draw itself on the "down" state when one is clicked on.
In other words, getting the depressed button images to follow the mouse as it
is dragged across the playfield is difficult to do, if not impossible, using
button windows.
Instead, I decided to fake it. I would have multiple bitmaps in memory. One
for each possible image that could be drawn on a grid square. I would then
just draw the bitmaps on the squares. If the bitmaps are properly drawn, they
should look just like buttons. For making the buttons depress, I would simply
have additional bitmaps and draw them where appropriate.
There are only a few downsides to this approach. First of all, bitmaps can not
be stretched without introducing distortions. This means that whatever size I
pick for them is going to be used throughout the game, regardless of user
preferences. Second, bitmaps are difficult to re-color when loaded out of
resource files, so the colors will not necessarily match the system-defined
button colors. But these are minor concerns, I feel, given the difficulty of
implementing the game in any other way.
ΓòÉΓòÉΓòÉ 4.2.3.2.1. The basic internal structure ΓòÉΓòÉΓòÉ
Many variables are needed to keep track of the game. I chose to make most of
the critical game variables global, so that they can be accessed from any
procedure without large amounts of parameter passing. While this may be poor
programming practice, it makes a game like this much easier to write and
understand, in my opinion.
In addition to the expected variables, like the size of the grid and the number
of mines, three arrays are used. The arrays are dynamically allocated and
re-allocated whenever the game grid changes size. All three arrays correspond
to the game grid, one element corresponding to each square.
One array is boolean, containing the map of mines - TRUE if the square has a
mine, FALSE otherwise.
The second array contains the visible screen. Each element contains a number,
indicating what the user sees at that coordinate. This array is initialized to
values corresponding to a blank raised button, but will change as play
progresses. This is used primarily for displaying the screen.
The third array contains the count of mines adjacent to each square. Rather
than compute the number each time a square is uncovered, the entire set is
computed in advance. This way, it's a quick operation to get the correct
number when a square is uncovered.
Other game variables include an array of bitmap handles for the drawing
procedures, flags to an end-of-game condition, and the current timer count.
ΓòÉΓòÉΓòÉ 4.2.3.2.2. Using bitmaps as buttons ΓòÉΓòÉΓòÉ
To implement the game grid, 18 bitmaps would be needed. I would need a blank
square, squares with the numbers 1 through 8 on them, a "button" in the raised
and depressed state, a "button" with a flag on it, a "button" with a question
mark on it in the raised and depressed state, and bitmaps to indicate errors:
an exploded mine, a misplaced flag, and an unflagged mine.
All these bitmaps were drawn (on 16x16 16 color bitmaps) using the
system-supplied icon editor. These were then all included in the resource file
with lines like the following in mine.rc:
BITMAP MINE_BLANK_UP MineBlankUp.BMP
BITMAP MINE_BLANK_DOWN MineBlankDown.BMP
BITMAP MINE_FLAG MineFlag.BMP
BITMAP MINE_QUESTION_UP MineQuestionUp.BMP
Where names like MINE_BLANK_UP are constants defined in the program's header
file.
Using bitmaps from a program's resources is fairly simple. First, a
presentation space is required for drawing into. While a WinBeginPaint call
will create a presentation space, I want to be able to draw bitmaps at times
other than during WM_PAINT message processing. For this reason, I create a
presentation space during processing of the WM_CREATE message and store its
handle in a global variable. A presentation space may be created outside of
paint message processing in the following way:
hdc = WinOpenWindowDC(hwnd);
sizl.cx = sizl.cy = 0;
hps = GpiCreatePS(hab, hdc, &sizl, PU_PELS | GPIF_DEFAULT |
GPIT_MICRO | GPIA_ASSOC );
Where hwnd is the window handle passed as part of the WM_CREATE message, sizl
is a variable of type SIZEL, and hab is the anchor block created as part of the
program's WinInitialize function. The value of hps returned is used for all
painting operations throughout the program. It is also passed to WinBeginPaint
during WM_PAINT message processing to prevent spurious presentation spaces from
being created.
Once the presentation space exists, the bitmaps are all loaded from the
program's resources with GpiLoadBitmap calls during the WM_CREATE message
processing:
hbitmap=GpiLoadBitmap(hps, NULLHANDLE, ID, BOX_WIDTH, BOX_HEIGHT);
Where ID is the resource-ID for the bitmap, as defined in the resource file,
and BOX_WIDTH and BOX_HEIGHT are constants indicating the size of the bitmap.
If the height and width of the bitmaps do not match the constants provided,
they will be stretched to fit the dimensions requested. The bitmap handles
returned in hbitmap are all stored so that they do not need to be loaded again.
Once handles are available for the bitmaps, displaying them is simple, using
the WinDrawBitmap call:
WinDrawbitmap (hps, hbitmap, NULL, &ptl, CLR_NEUTRAL, CLR_BACKGROUND,
DBM_NORMAL);
Where hbitmap is the handle of the bitmap requested, and ptl is a POINTL
structure containing the window-relative coordinate of the lower-left corner of
the bitmap's target position.
The only other thing to consider is cleaning up when the program terminates.
Although presentation spaces and handles will be released back to the system
when the WinTerminate call is placed, it is good practice to release them
manually, just to be sure nothing is left behind by accident. (Bugs in new
systems, like OS/2 can cause this to happen, so it's better safe than sorry.)
I place all cleanup instructions in the WM_DESTROY message handler. The
following statements will delete bitmap handles and presenation spaces:
GpiDeleteBitmap(hbitmap);
GpiDestroyPS(hps);
Where hbitmap is a valid bitmap handle and hps is the presentation space
created earlier. The GpiDeleteBitmap function should be called repeatedly,
once for each bitmap handle used in the program.
ΓòÉΓòÉΓòÉ 4.2.3.2.3. Using the mouse ΓòÉΓòÉΓòÉ
Another significant problem of implementing the desired interface is that of
getting a depressed "button" to track the mouse. For example, if the pointer
is moved from one square to another while button 1 is pressed, the depressed
button image on the screen should follow it.
For reasons outlined previously, normal buttons won't provide this effect.
Instead, using bitmaps, the entire effect must be simulated by handling
appropriate messages. To properly implement button 1's behavior, we must
handle the following messages: WM_BUTTON1DOWN, WM_BUTTON1UP, and WM_MOUSEMOVE.
WM_BUTTON1DOWN is generated whenever mouse button 1 is pressed. This may be
the left or right button, depending on whether OS/2 is configured for
left-handed mouse operation or not. Since we want the left-right buttons to
swap when in left-handed operating mode anyway, we can simply deal with button
1 and not concern ourselves with left or right.
The WM_BUTTON1DOWN message provides the coordinates (window-relative) that the
pointer is over when the button was pressed. These are delivered as the high
and low words of the first message parameter and may be extracted with the
following macros:
x = SHORT1FROMMP(mp1);
y = SHORT2FROMMP(mp1);
Before taking action, we must be sure these coordinates are valid. Any time the
button is pressed while the pointer is within the window's client area will
cause a WM_BUTTON1DOWN message to be sent, and these coordiantes may not be
over a segment of the game grid. ;p.If the coordinates are invalid, we simply
break from the switch statement, and allow the WinDefWindowProc to handle the
message.
If the coordinate is valid, we then compute which grid square the coordinate is
over. Since the grid is composed of same-sized rectangular regions, we have
simply to subtract any margins and divide the coordinate by the width (or
height) of a square to get the identity of the square.
Once the square is identified, we re-draw it in its depressed form. In general,
the depressed form of a square is identical to it's non-depressed form. The
exceptions are the blank covered square and the question-mark covered square.
These are depressable "buttons", and have different bitmaps for the depressed
state and the raised state.
A few other steps must also be taken, aside from drawing the depressed button.
We must set a flag in a static variable indicating that the mouse button is
down, so that the WM_MOUSEMOVE message handler will know the mouse button's
position. We must also store the grid coordiante of the depressed button.
Finally, we must capture the mouse. When the mouse is captured, the program
will get all mouse messages, even if the pointer is over some other
application's space. This is necessary, since the user may drag the pointer
outside of our window and release the button there. If this happens, and the
mouse isn't captured, the program will not receive the WM_BUTTON1UP message,
and would think that the button is still down.
The mouse is captured and released with the following functions:
WinSetCapture (HWND_DESKTOP, hwnd); /* To capture the mouse */
WinSetCapture (HWND_DESKTOP, NULLHANDLE); /* To release the capture */
Where hwnd is the window handle provided to the message handler.
The WM_BUTTON1UP message is returned when the user releases mouse button 1.
Compared to the button down handler, this is simple. First, the capture on the
mouse is released. Then, if the coordinate is over a covered-but-not-flagged
square, we call the uncover function.
If this is the first click of the game, then the timer is started.
The WM_MOUSEMOVE message is sent to the application whenever the pointer
position changes over the window. Normally, we will ignore these, passing them
to the system's handler, WinDefWindowProc, but if button 1 is down, we want to
take action first.
First, we must check if the pointer's position has moved to another square. If
it is over the same square as our saved position, then we do nothing. If it is
not over the same square, then we have to draw the previous square in it's
raised position, and draw the square it's now over in the depressed position
and save the new coordinate.
Managing the mouse button 2 is simpler, since flags and question marks are
placed as soon as the button is depressed. When the WM_BUTTON2DOWN message is
received and the pointer is over an appropriate square, the array element
corresponding to the square is changed and the square is re-drawn.
Managing the chord and button-3 sequences are similar to the button 1, except
that nine boxes must now be drawn in the depressed state instead of one. This
only gets a little ugly when the button 1 and button 2 messages must do
double-duty to manage the chord sequence as well as their individual functions,
but this is managed easilly with some well-placed if() statements.
ΓòÉΓòÉΓòÉ 4.2.3.2.4. Uncovering squares ΓòÉΓòÉΓòÉ
Uncoverig a square seems like a simple procedure, but introduces interesting
problems. The concept is simple. The program checks if the square is
uncoverable and aborts if it is not. It then checks if a mine is being
uncovered and ends the game if it is. Then it does the actual uncovering,
ending the game if the player wins.
In actuality, it's more complicated than this. As a courtesey, the program
should never allow the user to uncover a mine on the first click, so if a mine
is uncovered on the first click, the program should move that mine elsewhere.
Also, if a square has no mines adjacent to it, the program should automatically
uncover all the surrounding squares.
Step one is simple. Check if the square is covered and does not have a flag on
it. If not, just return and do nothing. There should also be some sanity
checking here, in case the program tries (in error) to uncover a square that
isn't on the grid.
For step 2, the program must check if the square has a mine under it. If so,
then the program must check if it is the first click. Moving the mine is tricky
- the program must place a new mine, delete the old mine, and adjust the
adjacency counters so all the numbers will still read proper values. Finally,
it must then loop back to the start and retry uncovering the square.
If it's not the first click, the finding a mine is game over. The timer is
stopped and the endgame flag is set, which effectively prevents further play,
since all of the mouse button message handlers check if the game is over as a
part of their processing.
If the square does not have a mine, however, it must be uncovered. In which
case, the count of adjacent mines is fetched. If there are one or more
adjacent mines, the bitmap for that number is assigned to that square, and it
is displayed.
If the uncovered square has zero adjacent mines, all the adjacent squares must
be uncovered. At first, I simply had the uncover procedure call itself
recursively, but I found that very large amounts of empty space would cause
stack overflow problems, so I had to use an iterative solution, instead. I
have a separate procedure to handle this, now.
To iteratively calculate which squares to uncover, the program loops through
all the squares. If it finds a covered square that is adjacent to an uncovered
square with no adjacent mines, it sets the square to its uncovered state
without calling the uncover procedure. It loops through the grid repeatedly
until no changes are made to the grid.
The program maintains a count of covered squares at all times. The count is
initialized to the number of squares in the grid and is decremented whenever a
square is uncovered. When the count of covered squares equals the number of
mines, a win situation is declared.
When the player wins, the timer is stopped, the endgame flag is set, and (if
one of the three standard games is chosen) the score is compared to the high
score, and if the high score is beaten, the score is recorded and the player
may enter his name.
ΓòÉΓòÉΓòÉ 4.2.3.2.5. Saving and restoring settings ΓòÉΓòÉΓòÉ
MineSweeper saves all critical game settings to an initialization file
(MINE.INI) when it terminates and restores these settings from this file when
restarting. The window's position, the high scores, and the game settings are
among the items saved. This is all done using OS/2's profile management
system.
The profile management system is a set of PM calls (all beginning with Prf)
that manage INI files. An INI file is opened by calling PrfOpenProfile, closed
by calling PrfCloseProfile, and is read from and written to with other API
calls.
The calls I used, and the syntax for them are:
hini = PrfOpenProfile (hab, "MINE.INI");
PrfCloseProfile(hini);
PrfWriteProfileData(hini, pszApp, pszKey, &data, sizeof(data));
PrfWriteProfileString(hini, pszApp, pszKey, pszString);
PrfQueryProfileData(hini, pszApp, pszKey, &buf, &buflen);
PrfQueryProfileSize(hini, pszApp, pszKey, &buflen);
PrfQueryProfileString(hini, pszApp, , pszKey, NULL, pszString, buflen);
PrfOpenProfile takes two arguments. The first is the anchor block that is
created when WinInitialize is called. The second is the file name of the INI
file. A variable of type HINI is returned. This HINI variable is used to
reference the INI file for reading and writing.
Two preset HINI handles are also available: HINI_USERPROFILE and
HINI_SYSTEMPROFILE may be used to access the user and system INI files, which
are normally OS2.INI and OS2SYS.INI. I chose not to use these files, however,
since software is required to delete entries from INI files. By keeping data
in a separate initialization file, a user may erase all settings by simply
deleting the MINE.INI file.
PrfCloseProfile takes one argument: the HINI variable returned by the
PrfOpenProfile call. It closes the profile. The user and system INI files are
not closeable.
Data in an ini file is referenced by two keys: and application name and a key
name. These two keys, together, are used to reference arbitrary-sized blocks
of data in the INI file. These blocks may be either strings or binary data.
Almost all functions that read or write an INI file take the same first three
paramters. The first being a valid HINI ini handle, the second being a string
containing the application name, and the third being the key name.
PrfWriteProfileData is used to write binary data to an INI file. It takes five
parameters. The first three are the standard handle, app name and key name.
The fourth is a pointer to the data, and the fifth is the length of the data.
PrfWriteProfileString is used to write null-terminated string data to an INI
file. It takes four paramters. The first three are the standard three, and
the fourth is the string.
PrfQueryProfileData is used to read binary data from an INI file. It takes
five paramters. The first three are the standard three. The fourth parameter
is a pointer to a buffer area to hold the data, and the fifth is a pointer to a
long variable containing the size of the buffer. When the call completes, this
variable will contain the actual number of bytes transferred.
PrfQueryProfileString is used to read string data from an INI file. It takes
six paramters. The first three are the standar three. The fourth is a default
string that will be supplied if the key can not be found in the INI file; I
leave this parameter as a NULL, indicating that I don't want a default string.
The fifth paramter is a pointer to the buffer that will contain the string, and
the sixth paramter is the maximum string length. The call will return the
actual number of bytes transfered into the buffer.
Finally, since I am dynamically allocating storage for these strings, I must
find out the length of these strings before I actually read them in, in order
to allocate a large enough buffer first. This is done with the
PrfQueryProfileSize API call.
PrfQueryProfileSize takes four parameters. The first three are the usual
three, and the fourth is a pointer to a ULONG variable. This variable will
contain the number of bytes that the referenced data block contains.
ΓòÉΓòÉΓòÉ 4.2.3.2.6. Sizing the window ΓòÉΓòÉΓòÉ
In MineSweeper, the mines are always the same size. And I do not want any
margins around the minefield. This means that the window must be re-sized to
fit the minefield.
This is a two step process. First, the proper size must be calculated, then
the window must be re-sized to fit.
Calculating the window size is trivial, but not immediately obvious.
Obviously, the width must be greater than the width of one row of squares, and
the height must be greater than the height of one columns of squares. But this
is not enough. The size of the window includes ALL of the window, including
borders, menus, and title bars. So, the border width must by qyeried from the
system and added to the width estimate. And the menu bar height, the title bar
height, and the border height must also be queried and added to the height
estimates.
These values can be extracted with the WinQuerySysValue function:
menuHeight = WinQuerySysValue(HWND_DESKTOP, SV_CYMENU);
captionHeight = WinQuerySysValue(HWND_DESKTOP, SV_CYTITLEBAR);
borderHeight = WinQuerySysValue(HWND_DESKTOP, SV_CYBORDER) * 2;
borderWidth = WinQuerySysValue(HWND_DESKTOP, SV_CXBORDER) * 2;
WinQuerySysValue takes two parameters. The first is a valid desktop handle,
which (under OS/2 version 2.0) is always HWND_DESKTOP, and the second is a
constant indicating which value to extract. The following were used:
SV_CYMENU The minimum height for a menu bar. If the menu font is
too large, a menu bar will actually become larger than
this height. Additionally, if a menu bar is too long
for the window, and wraps onto two or more lines, this
value will only be the height of one line. In other
words, it's not really very accurate.
SV_CYTITLEBAR The height of a titlebar
SV_CYBORDER The height of a thin border. Multiply this by two to
get both borders.
SV_CXBORDER The width of a thin border. Multiply this by two to
get both borders.
There are many other system values, but these are the only ones I needed. The
actual width of the window is the width of the window's contents plus the
border width. Unfortunately, the ambiguities of the menu bar's height do not
make it that simple to generate the window's height.
While there may be better ways to calculate the menu bar's actual height, I
chose a quick-and-dirty approach. I first make a best guess of the window's
height, by adding the system height values to the height of the playfield
(consisting of the game grid and the score region). I then set the window to
that size with the WinSetWindowPos command. After that I use the
WinQueryWindowRect command to get the actual size of the menu bar. This works,
because everything in OS/2 is a window - I get the window handle for the menu,
and then query the window's size - giving me the size of the menu. Using this
size, I re-calculate the height of the window and re-size it if it has changed.
This all happens quickly, and no painting occurs during processing, so the user
does not see the window change sizes.
The window's size and position is set with the following call:
WinSetWindowPos(hwnd, HWND_TOP, x, y, cx, cy, SWP_SIZE | SWP_MOVE |
SWP_ZORDER | SWP_SHOW | SWP_ACTIVATE);
Where hwnd is the window handle. The second parameter is for placing the
window in the stack of open windows; HWND_TOP is a constant that tells the
system to put this window above all the others. x, and y are coordinates for
the lower-left hand corner of the window. cx, and cy are the horizontal and
vertical sizes of the window. The last parameter is a set of flags. The ones
presented here tell OS/2 to re-size the window, re-position it, change it's
"stack" position, make it visible, and give it focus, respectively.
The menu bar's size is fetched with the following calls:
hwndMenu = WinWindowFromID(hwndFrame, FID_MENU);
WinQueryWindowRect(hwndMenu, &rcl);
Where hwndMenu is the window handle of the menu bar, hwndFrame is the window
handle of the frame window that owns the menu bar, and rcl is a RECLT structure
that contains the dimensions of the window.
WinWindowFromID is an API call that returns the window handle of a child
window. In this case, I want to know the handle of a menu-bar window, but I
only know the handle of the frame window. So I use WinWindowFromID to extract
the handle. It takes two parameters. The first is the handle of the parent
window, and the second is the ID of the child. Menu bars that are created as
part of a standard window always have an ID of FID_MENU.
WinQueryWindowRect fills in a RECTL structure with the extents of a window.
Since the coordinates are window-relative, the bottom and left extents are
always 0 and 0, leaving the other two extents containing the width and height
of the window.
ΓòÉΓòÉΓòÉ 4.3. The Unofficial Guide to the Palette Manager ΓòÉΓòÉΓòÉ
The Unofficial Guide
to the
Palette Manager
ΓòÉΓòÉΓòÉ 4.3.1. The Guide ΓòÉΓòÉΓòÉ
IMPORTANT NOTE: If you aren't going to read this whole article, at the very
least please read about the FATAL ET4000 bug (search for FATAL ET4000 BUG if
you're reading this as ASCII).
Credit where it's due: When Windows 3.0 first came out with its Palette Manager
in 256-color mode, I fell in love. Finally, a GUI that understood palettes and
"did the right thing" when two programs tried to use the palette
simultaneously. I was elated to find out that when OS/2 2.0 came out, it would
also have a Palette Manager. My elation turned to disgust when I discovered the
following paragraph in the READ.ME file for the OS/2 Programmer's Toolkit:
Palette Manager functions are available, but no devices currently allow the
physical palette to be changed. This means applications attempting to put
customized colors into the palette will get the nearest colors from the default
palette.
As far as I'm concerned, this is tantamount to saying, "You can do anything you
want ... as long as you don't want to do anything!" Fortunately, when the
Service Pack came out later in 1992, the 32-bit XGA, Trident, and ET4000
drivers all included good Palette Manager support (though with a few bugs, one
of them serious). Sadly, as of this writing, I believe that people with other
256-color displays (such as 8514/A's and clones) still don't have decent
Palette Manager support.
So what's Palette Manager, and why should you care? VGA-standard adapters can
display any of 262,144 different colors. Unfortunately, you can't display them
all at once; you can only see 256 at a time. Fortunately, you can pick any 256
of these colors at once (unlike the early CGA, where you had a choice of two
sets of four fixed colors). The 256 colors that your adapter is showing at any
time make up your physical palette. Under Presentation Manager, your program
creates a logical palette. For example, you may want eight shades of red. You
ask Palette Manager for eight colors, specifying the RGB values that you want.
Palette Manager then gives you eight spaces in the physical palette and sets
them to the RGB values you specified. If it runs out of space in the physical
palette, then it looks at the colors it couldn't get for you and returns the
colors in the physical palette that come closest to matching them. In either
case, you don't have to worry about which spots in the physical palette you
got; you just draw lines in color 3 (for instance), confident that they will be
medium red.
(At this point, some of you might say, "Couldn't you do this with a Logical
Color Table?" I think the answer is yes [I've never used LCTs myself], but I
think Palette Manager is easier to use and more flexible. Also, I'm sort of
getting the impression that LCTs are being phased out.)
There are other things you can do with Palette Manager. Broadly speaking, there
are four different types of tasks you can carry out using Palette Manager:
o Examining (but not changing) the physical palette
o Creating a logical palette for drawing (e.g., using GpiBox)
o Creating a logical palette for displaying a bitmap with special color needs
o Creating and animating a logical palette
In an ideal world, the third task would just be a special case of the second.
However, bugs in the Palette Manager require you to do a few special things to
get it to work, so I'll treat it separately.
My program PHYSCOLO demonstrates the first task, SCALE demonstrates the second,
GRAYBITM demonstrates the third, and ZOOM demonstrates the fourth. Although
these programs do very different things, they have many similarities. They (and
any other program that uses Palette Manager) carry out the following steps:
1. Make sure Palette Manager is available.
2. Create a PS (and DC)
3. Create the logical palette
4. Select the palette into the PS
5. Realize the palette
6. Use the palette
7. Clean up by deselecting the palette, destroying it, and destroying the PS.
Let's look at how we carry out these tasks. At this point, I strongly suggest
that you follow along with a copy of PHYSCOLO.C. Either print it out or open it
up in another window.
STEP ONE: Make sure Palette Manager is available.
In the "main" part of PHYSCOLO, you'll find the following block:
{
LONG sColors;
HDC hdcScr;
hdcScr = GpiQueryDevice (WinGetPS (HWND_DESKTOP));
DevQueryCaps (hdcScr, CAPS_ADDITIONAL_GRAPHICS, 1, &hasPalMan);
hasPalMan &= CAPS_PALETTE_MANAGER;
...
}
(There are two more lines in the actual listing that check how many colors are
available on your computer. While all existing drivers with Palette Manager
support have exactly 256 colors, this could change in the future.)
After running this block, hasPalMan will be zero if the computer doesn't
support Palette Manager. PHYSCOLO just keeps going, but it might be more
appropriate to give an error message and exit. (See ZOOM, for instance.)
So if hasPalMan is nonzero, are you okay? Not necessarily. Remember the OS/2
Toolkit READ.ME: OS/2 may report that you have Palette Manager support even if
the device won't allow your physical palette to change. Unfortunately, I don't
know any way to tell if you "really" have Palette Manager support; if anyone
knows, I'd like to hear from them.
STEP TWO: Create a PS (and DC)
Here's the First Commandment of using Palette Manager:
IF YOU'RE GOING TO USE PALETTES, !!DON'T!! USE A CACHED MICRO-PS!
If someone had told me this, it would have saved me a couple of weeks. If you
only learn two things from this article, learn this and the Fatal ET4000 bug
(below).
In other words, don't do the following when handling WM_PAINT:
case WM_PAINT:
{
HPS hps;
hps = WinBeginPaint (...);
...
}
Why? When you create a cached micro-PS, it "forgets" everything between
WM_PAINT messages. While that's fine for simple graphics, it's not appropriate
when you're using a palette, because the cached micro-PS will forget the
palette!
Instead, we create an uncached micro-PS. Looking at the "ClientWinProc"
procedure in PHYSCOLO, we see the following lines:
static HPS hps;
static HDC hdc;
(These are static, of course, since they have to be remembered between calls to
ClientWinProc.) Later on, we see the rest of the lines for creating the PS:
case WM_CREATE:
{
SIZEL sizl;
sizl.cx = sizl.cy = 0;
hdc = WinOpenWindowDC (hwnd);
hps = GpiCreatePS (hab, hdc, &sizl, PU_PELS | GPIF_DEFAULT
| GPIT_MICRO | GPIA_ASSOC);
}
STEP THREE: Create the Palette
This step (and Step Five) will differ depending on which task you carry out
(e.g., whether the logical palette is for examining the physical palette or for
palette animation). In the case of PHYSCOLO, we do the following:
static HPAL hpal;
...
WM_CREATE:
{
...
ULONG tbl [MAX_PAL_SIZE];
INT j;
for (j = 0; j < colorsToShow; j++) {
tbl [j] = PC_EXPLICIT * 16777216 + j;
}
hpal = GpiCreatePalette (hab, 0L, LCOLF_CONSECRGB,
colorsToShow, tbl);
}
Four things worth pointing out:
1. Ordinarily, it's a good idea to sort the palette, with the most important
RGB values closer to the beginning of tbl. Here, since PHYSCOLO merely
examines the physical palette, we don't sort entries.
2. The PC_EXPLICIT flag in each tbl entry means, "This number represents an
entry in the physical palette, and NOT a color." So, for instance,
PC_EXPLICIT * 16777216 + 3 means the third entry in the physical palette.
3. The LCOLF_CONSECRGB flag means that tbl is an array of RGB values, each of
which is 4 bytes long (which is why we can "fake" it with ULONGs).
Currently, you must use this flag for all calls to GpiCreatePalette (and
your table must be an array of RGBs / ULONGs).
4. The 0L parameter is appropriate for PHYSCOLO, since all we're doing is
examining the physical palette. Later, I'll try to convince you that it's
appropriate for every other use of GpiCreatePalette, too.
STEP FOUR: Select the Palette into the PS.
This is easy:
GpiSelectPalette (hps, hpal)
STEP FIVE: Realize the Palette.
You realize the palette when your program changes its logical palette or in
response to OS/2's new WM_REALIZEPALETTE message. From what I've seen,
WM_REALIZEPALETTE is sent to a Presentation Manager program when that program
moves into the foreground, or when some other program changes an (unreserved)
palette entry. (Reserved palette entries are ones used for palette animation;
see below.)
If your program doesn't start in the foreground, it should realize the logical
palette before it first draws using that palette. Otherwise, the client area
will be drawn in black. That's what the
STATIC BOOL firstPaint = TRUE;
...
if (hasPalMan && firstPaint {
...
WinRealizePalette (hwnd, hps, &palSize);
}
stuff in the WM_PAINT case is for.
The parameters to WinRealizePalette are straightforward, though I wish I
understood why it's necessary to pass a pointer to the palette size rather than
the palette size itself.
The other time PHYSCOLO calls WinRealizePalette is when handling
WM_REALIZEPALETTE. When PHYSCOLO receives a WM_REALIZEPALETTE message, it
merely calls WinRealizePalette and ignores the return value. This is a little
atypical, as I'll explain below.
STEP SIX: Use the palette.
This is easy. Looking at PHYSCOLO's listing, we see the line
GpiSetColor (hps, j);
What this means is, use the color of the j-th logical palette entry for your
next drawing operation. That's all there is to it.
STEP SEVEN: Clean up by deselecting the palette, destroying it, and destroying
the PS.
When you're completely finished with your palette, you should deselect it and
destroy it. When you're completely finished with your PS, you should destroy
it. Usually, this will be when your program is ending, which is why all these
tasks are handled in the WM_DESTROY code for PHYSCOLO:
if (hasPalMan) {
GpiSelectPalette (hps, NULLHANDLE);
GpiDestroyPalette (hpal);
}
GpiDestroyPS (hps);
This should be completely straightforward. Be sure you get rid of every palette
and PS when you're done with it.
OTHER USES OF PALETTE MANAGER
We've gone through PHYSCOLO and seen how to examine the physical palette. What
changes do we have to make to create a custom logical palette for drawing, or
for displaying a bitmap, or for animating a palette? The answer is, not many.
Most of the changes are in Steps Three and Five.
SCALE
This program draws a grayscale, or a redscale, or a cyanscale, or .... See
SCALE.DOC for details.
If we consider how it uses the Palette Manager, SCALE isn't really all that
different from PHYSCOLO. It does Steps One and Two (checking for the Palette
Manager and creating the PS and DC) the same way that PHYSCOLO does. When we
reach Step Three, things are slightly different. In SCALE, the entries in tbl
represent RGB triples, and they take the form:
tbl [j] = red * 65536 + green * 256 + blue * 1;
where "red," "green," and "blue" are the RGB values for the color you desire in
logical palette entry j. These values range from 0 (meaning none of that
primary) to 255 (meaning the maximum amount of that primary). Here's an
example: Bright cyan is formed by mixing the maximum amount of green with the
maximum amount of blue. Numerically, this would be 255 * 256 + 255 * 1, or
65535. So, for example, if tbl[3] had a value of 65535, this would mean that
the third entry in the logical palette would be bright cyan. (We don't use the
PC_EXPLICIT flag since we want to create colors, rather than examine physical
palette entries.)
Note that I still use 0L in the call to GpiCreatePalette. Why? Well, there are
two possible alternative options (which can even be OR'd together):
LCOL_PURECOLOR. According to the online docs (in the OS/2 Toolkit), "If this
option is set, only pure colors are used and no dithering is done." Well, I've
never used this option and I've never seen any dithering. In my honest opinion,
Palette Manager does the closest match instead of dithering, since dithering
wouldn't work for narrow lines (or single pixels!), and since it'd be too
computationally expensive for filled areas. Anyway, you can try this flag if
you want; I don't use it and don't plan to.
LCOL_OVERRIDE_DEFAULT_COLORS. OS/2 Presentation Manager sets aside about 20
colors for the user interface (eg, for the color of Window title bars). This
means that you can't ever really get 256 colors. Unless you use this flag, that
is. However, using this flag will screw up the look of the user interface. In
my honest opinion, it's not worth it; sort your palette and live with the fact
that you'll "only" get 236 distinct colors.
Step Four is the same old "GpiSelectPalette (hps, hpal)."
Step Five is half the same (in handling firstPaint), but half different. In the
handling of WM_REALIZEPALETTE, we actually use the return value of
WinRealizePalette:
ULONG palSize = colorsToShow;
if (WinRealizePalette (hwnd, hps, &palSize)) {
WinInvalidateRect (hwnd, NULL, FALSE);
}
What does this do? Basically, when you call WinRealizePalette, it returns the
number of palette entry requests that it could NOT meet. If you asked for a
palette with 16 colors and it could only give you 13, the other 3 will be
matched to the closest available colors, and WinRealizePalette will return 3.
In this case, you should redraw your client area, and that's what the
"WinInvalidateRect" call does.
Step Six, using the palette, is much the same as it was for PHYSCOLO. Notice,
however, that SCALE also has a routine to handle things if the machine doesn't
even pay lip service to supporting Palette Manager. That's what the
"GpiQueryColorIndex" line is about. On a system without Palette Manager, this
returns the argument to GpiSetColor that will return the closest match to the
RGB value you want.
Finally, Step Seven is the same for SCALE as it was for PHYSCOLO.
GRAYBITM
This program creates a small bitmap with grayscale shapes and displays it on
the screen (stretching it to fit the client area). It's pretty much the same as
SCALE. There are a couple of differences: It creates the palette before
creating a PS, and it uses the logical palette in two different PSes; the one
that's used to create the bitmap (hpsMem in the "CreateBitmap" procedure), and
the one that's used to display the bitmap (hps in "ClientWinProc"). These
differences aren't really that important, though the latter shows that you can
"share" palettes by sharing the HPAL.
Notice that handling WM_PAINT is simplicity itself: We just call WinDrawBitmap.
But why didn't we use GpiBitBlt? And why do the colors get screwed up when we
resize (eg, maximize) the GRAYBITM window? We'll tackle these issues when we
talk about the bugs in the Palette Manager.
ZOOM
This program does palette animation. What's that? Well, suppose you do the
following:
o Create a logical palette with space for four colors, with all four palette
entries set to the window background color.
o Draw four non-overlapping filled circles using each of the four colors, with
the circle that uses color 0 on the left, circle 1 to the right of it, and so
on.
Okay, so at this point you've drawn four "invisible" circles. Now suppose you
set the first entry in the logical palette to blue (or any color that isn't the
background). Boom, a circle appears on the left. After a brief pause, change
the first color in the logical palette back to the background, and change the
second color to blue. Then change the second color to the background and the
third color to blue. And so on. If you time these well, this makes it look like
an animated sequence of a circle moving from left to right across your client
area. But the thing worth pointing out is, you don't have to do any fancy
drawing or undrawing in real-time; all you're doing is changing palette
entries. Since you're only changing palette entries, this sort of animation can
go very fast, once you've finished the complicated initial drawing.
ZOOM does something similar: It draws 16 overlapping squares, creates a palette
with spaces for 16 colors, and then animates. The result is a little like
moving down a long corridor. (For a real cheap thrill, run TESTZOOM.CMD,
CTRL-ESC to get the Window List, select all the Zoom sessions, and Tile them.
If one of these windows has the focus, click on the desktop to "get rid" of
it.)
From a programmer's standpoint, ZOOM is yet another straightforward use of
Palette Manager. It carries out Steps One and Two in the usual way (though it
gives an error message and dies instead of just "trudging on" if it can't find
Palette Manager -- since this program exists only to demonstrate palette
animation, there's no point in having it continue.)
In Step Three, things are slightly different:
tbl [j] = PC_RESERVED * 16777216 + ...
What's this PC_RESERVED flag? Well, as I said before, if Palette Manager can't
give you all the colors you requested, it will use the closest match among the
physical palette. (That's why we do a WinInvalidateRect if WinRealizePalette
returns a nonzero value.) However, if you use the PC_RESERVED flag, Palette
Manager will not match anything with that particular palette entry. This is
important if the RGB values of the palette entry will be changing rapidly (the
overhead of keeping the closest match up-to-date would be too great). Since
changing RGB values rapidly is the heart of palette animation, you should
always use the PC_RESERVED flag for palette animation.
Step Four is still the same. Steps Five and Six are different. In fact, I'd say
that Steps Five and Six are now intertwined, since the way you "use" a palette
for animation is to constantly change it, and you have to realize the palette
every time you change it. Hence:
ULONG tmp = tbl [0];
...
tbl [j] = tbl [j + 1];
tbl [shadesToShow] = tmp;
GpiAnimatePalette (hpal, LCOL_CONSECRGB, 0, shadesToShow, tbl);
GpiAnimatePalette is, naturally enough, the function that actually animates
(ie, changes the RGB values of) the logical palette. Its arguments are the
handle to the palette to change, the ubiquitous LCOL_CONSECRGB, the first index
to change, the number of indexes to change, and the table that holds the new
RGB values. The call above changes all the palette entries; to change only
entries 3 through 7, change the "0" above to "3", and the "shadesToShow" to
"5". (The values in tbl would still be the same.)
Finally, Step Seven is the same as usual.
PALETTE MANAGER BUGS
There are several reasons why this guide is unofficial rather than official.
One reason is that it describes what I've discovered in the school of hard
knocks, which is sometimes quite different from what the official documents
say. Another reason is that I don't have any access to official IBM folks, so
there may well be errors in this article. I've described how I've used Palette
Manager to the best of my ability, but it could be that there are simpler, more
elegant ways to carry out some of these tasks. I doubt it, but we'll never know
until the IBM documentation improves.
(Another reason that this is unofficial is that I have no compunction against
slamming IBM when I think they deserve it.)
Finally, one of the important reasons why this is an unofficial guide is that
I'm going to tell you that the current incarnations of Palette Manager have
some real bugs, including one that's very serious. Namely:
THE FATAL ET4000 BUG (or, How To Hang Your Computer Using Palette Manager)
This is the most important fact in this article; if you forget everything else,
remember this. And warn anyone who uses your program about it!
If you're running one of the 256-color ET4000 SuperVGA drivers from the Service
Pack, AND IF a program changes the physical palette (either an OS/2 program
that uses Palette Manager or a seamless WinOS/2 program), THEN !!DON'T MOVE AN
ICON ON THE OS/2 DESKTOP!! IF YOU DO, YOUR COMPUTER WILL HANG!
Your computer won't be safe until the physical palette has reverted to normal.
This won't happen until after the program(s) that changed the palette are
finished. And even then, it can take a while; one way to force the palette back
is to start PHYSCOLO (or move it to the foreground) and then click on the
desktop. The palette will revert to normal.
TRIDENT USERS BEWARE! THIS BUG MAY ALSO OCCUR FOR YOU (I couldn't get
verification by press time).
Fortunately, this bug does NOT occur in the XGA drivers. Neither does it occur
in the latest 256-color drivers from the OS/2 2.1 beta (version 6.479), so
ET4000 users may want to switch to the beta. I hope the fact that the bug isn't
in the beta means that IBM has fixed it, but I haven't heard anything official
from them, so take care.
Unfortunately, this isn't the only bug in Palette Manager. I know of three
others:
THE UNIVERSAL BUG: Start a program that uses Palette Manager to draw in its
window (eg, SCALE or GRAYBITM). Drop down the system menu over the client area.
Click elsewhere to get rid of the menu. The area under the menu won't redraw
correctly. This bug occurs on every platform that's known to support Palette
Manager, though it's less obvious on XGAs and 2.1 beta-using ET4000s than on
Tridents and ET4000s with the Service Pack drivers.
THE GPIBITBLT BUG: If you use GpiBitBlt to render a bitmap that uses a logical
palette, it'll be drawn with the wrong colors on ET4000s and Tridents.
Guaranteed. However, it'll be drawn fine on XGAs.
THE WINDRAWBITMAP SCALING BUG: If you use WinDrawBitmap with parameters that
require the bitmap to stretch or shrink, it'll be drawn with the wrong colors
on ET4000s and Tridents. Again, though, it'll look fine on XGAs.
I have sent bug reports from IBM on all of these, as well as sample programs
that demonstrate the GpiBitBlt bug. However, I'm just one voice crying in the
wilderness: If you want to see these bugs fixed, PLEASE get the file
OS2PROB.TXT from the /pub/os2/2.0/info directory on ftp-os2.nmsu.edu, fill it
out, and e-mail it to 76711.610@compuserve.com. Make sure that you only report
ONE bug on each form; otherwise, you'll just get a note back from IBM asking
you to resubmit your report on several forms. As long as I am the only person
asking for these bugs to be fixed, they'll probably be a low priority for IBM.
FINAL THOUGHTS
Don't let these bugs get you down; Palette Manager is a fine subsystem that
lets you accomplish a lot of great effects. My hat's off to IBM for giving us
such a simple and elegant way to use palettes, and my hat's off to YOU for the
fine Palette-Manager-using programs that I hope you'll be writing soon ;-).
Raja Thiagarajan / sthiagar@bronze.ucs.indiana.edu / 2-21-93
ΓòÉΓòÉΓòÉ 4.4. Advanced GPI:Retained Segments and Transformations ΓòÉΓòÉΓòÉ
Advanced GPI:
Retained Segments
and
Transformations
ΓòÉΓòÉΓòÉ 4.4.1. Advanced GPI ΓòÉΓòÉΓòÉ
[The code presented in this article is contained in GPI.ZIP -Ed.]
When you say "Graphic Program Interface" what first comes to mind is a set of
about a dozen and a half system calls to draw lines, boxes, circles, and other
kinds of graphic elements. But, I may ask you, did you know that by using OS/2
Presentation Manager, you could refresh your entire window, whatever you draw,
with one call? Did you know that you can take graphic "segments" and do things
like rotate them, translate them, and scale them, without actually having to
write the code to do the matrix math yourself? Well, thats what we're going to
be talking about. There is a whole host of functions that are built into the
GPI of OS/2's presentation manager that perform these features, plus more.
Unfortunately, these functions are not normally used by the common user, and
thats why I'm going to be talking about them here. This is an introduction to
the Advanced GPI functions that many of us don't usually think about.
Just as an introduction, I'm assuming that you've programmed OS/2 presentation
manager programs before. Therefore, I'm not going to go into the specifics of
the calls such as WinCreateStdWindow, or any of the other Window functions,
since this article mainly deals with Gpi functions. But, if you've never
programmed PM before, I do think that you should still be able to follow this
article through to the end, and by looking at the source code, which I hope is
clear and straightforward, you should be able to pick up with what I'm talking
about. So, I would also recommend that you have a copy of the source code
around while you're reading this, since I'll be talking about distinct parts of
the code, and to get a better view of whats going on, you should see the
context of how the call is used.
First, we're going to talk about presentation spaces. What exactly is a
presentation space and how can we use it to help us? Very generally, a
presentation space is the part of the system that takes your calls to the GPI
functions, and generates the pictures that you see on the screen. In most
cases, this mapping of graphic calls to displayed results is normally a
pass-through operation, that is, the things that you do aren't retained in
memory, other than the results that you see on the screen. This is the default
behavior of the presentation space that is associated with the client window
that would be created by a WinCreateStdWindow call, this type of presentation
space is called the "Cached Micro-PS." But, there are other types of
presentation spaces, the most important of these being the "Normal PS." What a
Normal PS allows you to do is take the graphic calls that you're sending it,
and remember them for later operations, like refreshing the window, or rotating
your objects, or whatever you like. A Normal PS also supports correlation
functions that will let you know if any of your graphic segments pass through a
certain rectangle of the screen, that you define. This correlation is
wonderful for user interface tasks, such as clicking on an object to select it.
We'll see how to use these functions later in the article.
The program that is developed here displays nine objects on the screen, each of
which is its own segment in the presentation space. By clicking on an object,
with either the right or left button, you can select an object. When an object
is selected, you can use the '+' and '-' keys to rotate it, or drag with the
right mouse button to translate it. The '=' key does the same thing as the '+'
key, for ease of use.
Since we know that the default Presentation Space create with the
WinCreateStdWindow call isn't a Normal PS, we have to figure out how to create
the PS that we really want. Looking through our list of the GPI functions, we
might take notice of GpiCreatePS.
HPS GpiCreatePS(HAB hab,HDC hdc,PSIZEL pszl,LONG loptions)
"hab" is the Handle to our Anchor Block that we got when we did our first
WinInitialize call. This should be readily available as a global variable to
our program. HDC is the Handle of the Device Context that is associated with a
window, or in layman's terms, a handle that describes what type of device we're
working with. This is readily available by calling the WinOpenWindowDC
function. So, all we need now are the pszl that defines the size of the
presentation space that we want to create, and some options. Looking at the
functional description of the call, we see that if we specify (0,0) as the size
of the PS we're creating, a default size for our device will be used instead.
So, we're left with some options, that describe what type of PS we're going to
be creating. In our case, we want the Page Units to be pixels (since we're
drawing on the screen), we want a Normal PS, and we want to asociate the PS
with the hdc that we're dealing with, so we tell it to automatically do this
for us. So, our final version of the call ends up being:
sizel.cx=0;
sizel.cy=0;
hps=GpiCreatePS(hab, hdc, &sizel, PU_PELS | GPIF_DEFAULT
| GPIT_NORMAL | GPIT_ASSOC);
And, we can use this HPS throughout our program, as long as we follow a couple
simple conventions, the main one being that we destroy it at the end of our
program. Normal PS's take up a lot of memory, and you don't want to be using
them where you don't have to. Use a normal PS only when you want to use the
functions that go along with it. You'll quickly run out of memory if you use a
normal PS for every window that you create.
Looking at our WM_CREATE message in our client window procedure, we see the
above call to GpiCreatePS, along with a couple other suspicious looking calls,
the first of these being the GpiSetDrawingMode command. Before explaining the
call, I'll explain a little more about the process that will be going on in the
program.
We know that we have now a Normal PS instead of a Cached Micro PS and we know
that we can have retained graphics using this normal PS. Well, it would seem
fairly obvious that there has to be some way of more readily organizing the
information (graphics) that we're going to be retaining. What the Gpi can do
for us is break our individual calls to Gpi primitives into larger blocks, each
of which is a segment. Each segment has a "tag" that goes along with it. This
"tag" is just a number that we assign to the segment so that we can remember
which one is which. The segments also have numbers, and we'll order them
sequentially. In our program, the segment number and the tag are the same
number, for simplicity's sake. Note that to do the functions that we're doing,
each segment must have its own unique non-zero tag. Along with the tag, a
segment transform matrix is also stored. If you're not familiar with computer
graphics and transform matrices, just think of this as something that will
specify the position and rotation angle of the segment. It can also specify
the scaling value of a segment, but we won't deal with those calls in this
example program. The calls to the scaling routines are very similar to the
ones for translation and rotation, so if you have a reference manual that
describes these functions, you should be able to use them with no problem. So,
we've got our segments. For this program, we're going to have nine segments.
Two will be five pointed stars, one a square with rounded corners, one an oval,
and the rest will be squares. They'll be arranged in a 3x3 grid on the screen.
We also must mote that matrices in OS/2 are mainly composed of fixed point
numbers instead of floating point. This allows the Gpi to not require a
floting point coprocessor and still run at a resonable speed on slow computers.
Thus, there is a macro called MAKEFIXED that takes two arguments, an integer
portion and a fractional portion of the number. As a little background, to
represent a number as a fixed point, call our number 'n'. You would use the
following code:
i=(INT)n;
f=(n*65536)-((INT)n*65536);
fixed=MAKEFIXED(i,f);
So, back to talking about the GpiSetDrawingMode command. What this does is
tell our presentaion space to do one of the following things: Draw only, draw
and retain, or retain only. We're going to be using the retain only function,
so that we can enter the segments into the presentation space, then draw them
ourselves with other specialized calls later. So, we use the call
GpiSetDrawingMode(hps,DM_RETAIN);
To set the drawing mode. The other suspicious looking call is the
GpiSetPickApertureSize call. I'll come back to this later. What it does is
set the size of the rectangle that we'll be using as our correlation area.
Now, the last thing that's there is the SetupSegments call. This is a
procedure that was written to draw the nine segments, and set their initial
segment transform matrices so that they're not rotated, and so that they're
arranged in a 3x3 grid. Looking at this procedure, we see some calls that look
somewhat unfamilar. GpiSetInitialSegmentAttrs. Oh, this must have to do
something with the segment attributes. Yes, it does. Every segment has some
flags that go along with it, this just tells the system "for every new segment
that you make, set these options from now on" The options that are most
commonly used are:
1. ATTR_VISIBLE The segment will be visible -- i.e. will be drawn
2. ATTR_DETECTABLE The segment will be detectable by the correlation
functions
3. ATTR_CHAINED The segment will be in the segment chain.
The segment chain is the list of segments that will be drawn by the
GpiDrawChain call. So, in our case, we want all of our segments to be in the
segment chain. Now, the second unusual looking call is the GpiScale call.
What we're doing here is initializing the variable 'identity' so that it
contains the identity matrix, so that we can set our viewing transformation to
be the identity. What this means is that if we say our square is 30x30, it
will come out as 30 pixels on a side, exactly. So, we call
GpiSetViewingTransformMatrix with the appropriate arguments. The one that
might look suspicious is the second one, the 9L. This specifies that there are
nine elements in our matrix. This is because for two dimensional
transformations, you deal with 3x3 matrices. The rest of the code is organized
as follows:
GpiOpenSegment();
GpiSetTag();
GpiSetColor();
GpiTranslate();
GpiSetSegmentTransformMatrix();
.
.
.
Draw our segment
.
.
.
GpiSetModelTransformMatrix();
GpiCloseSegment();
What we're doing is keeping a counter of the segments that we've drawn, so that
each segment will have a unique number. We open that segment, set its tag, and
its color. The GpiTranslate call sets the matrix 'translate' to be matrix that
will translate the image drawn from (0,0) to the point specified in
ptlTranslate. Then, using the GpiSetSegmentTransformMatrix call, we set the
matrix for the current segment, to be the translation matrix that we just
computed. Along with the individual segment transform matrices for each
segment, there is a model transform matrix which is applied to all segments
drawn. When a segment is drawn, its transform matrix gets multiplied to the
model transform matrix, resulting in an accumulation of the transforms. To get
around this effect, at the end of each segment we set the model transform to be
the identity. We then close the segment we're working with.
So, after the SetupSegments call, we've got nine retained segments, each with
its own individual transform matrix to put it in the right spot on the screen.
The hard part is through. We've already drawn the innards of our segments, an
operation that only has to happen once, and we've also set their initial
values, something that only has to happen once. From here on out, its just the
accumulation of the deltas that make up the other operations we can do on the
segments.
Looking at the WM_PAINT messaage, which is the next things that happens, we see
that it is very short and concise. We erase the presentation space, set the
forground mix to XOR mode, so that we can easily erase a segment once we've
drawn it, then we call the infamous GpiDrawChain, which draws all the chained
segments, along with applying their individual transform matrices. Note that
the WinBeginPaint call in our WM_PAINT message is probably a little different
than those that you've seen before. Because WinBeginPaint usually returned a
hps, but we have a PS already, we have to tell it that, so the second argument
to the call is our current hps. This way it doesn't get obliterated by the new
PS that would have been created.
The next message that I'll look at is the WM_BUTTON?DOWN messages, they're both
the same, so I'll just talk about WM_BUTTON1DOWN. What we're going to be doing
here is correlating the retained segments to see if we've clicked on one or
not. To do this we have to do a couple of setup things first. Intuitively, we
have to set the size and position of the rectangle that we're going to be
correlating with, then do the actual correlation. Because the size of the
correlation rectangle doesn't change throughout the program, I've placed it in
the WM_CREATE message, so it only gets executed once. Take a look at the code,
it sets the variable 'size' so that its 10 pixels square, then calls
GpiSetPickApertureSize. This calls are fairly obvious, first the hps, then the
options, then the pointer to the variable 'size'. The option PICKAP_REC tells
the Gpi system that we want to use the value specified in the third argument,
as opposed to using some default vaule. Now for the position of the pick
aperture. Back to the WM_BUTTON1DOWN message, we see that the variable 'ptl'
is set to be the current position of the mouse, and we call
GpiSetPickAperturePosition. Its arguments are self explanatory. Next comes
the neat call, the correlation.
lNumHits=GpiCorrelateChain( HPS hps,
LONG lType,
POINTL pptlPick,
LONG lMaxHits,
LONG lMaxDepth,
PLONG alSegTag);
lType specifies what type of segments you want to correlate with. In our case,
we want to do all segments, so we specify PICKSEL_ALL. The pptlPick argument
is again the position of the pick aperture. lMaxHits and lMaxDepth together
specify the maximum number of hits that will be recorded by this function. In
my case, as in most cases, both of these arguments will have the value one,
since we only are interested in one segment at a time. The PLONG alSegTag is
an output array of segment number and tag value pairs. For example, on return,
if we've hit a segment alSegTag[0] will be the segment number of the selected
segment, and alSegTag[1] will be its tag vaule. The routine returns the number
of segments hit, so in our case, this will be either zero or one, and we're
only interested in the case where its nonzero. If the call returns nonzero, we
set our variable 'selected' to be the index of the currently selected segment,
and continue, otherwise we set 'selected' to be zero, to signify that nothing
is currently selected.
Now, when we get a WM_MOUSEMOVE message, and when button two is down, we do
some more magic. What we want to accomplish here is to figure out how far the
mouse was moved at each step, and then add that increment to the current
position of the currently selected segment. The first thing that we do is draw
the segment in XOR mode, which will erase it from the screen. We compute the
position delta, and then use GpiTranslate to get the matrix that represents
that translation. Using GpiSetSegmnetTransformMatrix with the TRANSFORM_ADD
parameter, we then add this delta translation to the total translation. The
TRANSFORM_ADD parameter tells the Gpi to postmultiply the matrix onto the
current matrix. Because we want all rotations to come before all translations,
we postmultiplt translations, and premultiply rotations. The last thing that we
do here is draw the segment again, so that it appears in its new place. The
GpiDrawSegment call draws just one segment, which is specified as the second
argument to the call.
The last part of our program that we havn't looked at yet is the WM_CHAR
message. It too is fairly simple. It sets the rotation angle to be 3.0
degrees, then if the '-' key has been pressed, negates this value. It then
premultiplies the right matrix onto the segment transform matrix with the
GpiRotate and GpiSetSegmentTransformMatrix calls. Note that the parameter to
GpiSetSegmentTransformMatrix is TRANSFORM_PREEMPT.
I've described all the Gpi functions that allow you to create segments, and
modify the segment transform matrices so that you can rotate and translate
them. Hopefully I've given enough background so that you can write your own
programs that use these advanced Gpi functions, along with the others that deal
with retained segments and segment transform matrices. In most cases, these
calls aren't as hard as they seem, and it is guaranteed that using these calls
will both save you programming time, and increase the speed of your own code.
Have fun!
ΓòÉΓòÉΓòÉ 5. Columns ΓòÉΓòÉΓòÉ
o Questions and Answers
o Introduction to PM
o Project Barrel
ΓòÉΓòÉΓòÉ 5.1. Questions and Answers ΓòÉΓòÉΓòÉ
Questions
and
Answers
ΓòÉΓòÉΓòÉ 5.1.1. Introduction ΓòÉΓòÉΓòÉ
Welcome to the first "Questions and Answers"! Each month, I collect various
questions sent to me via email and try to answer each directly; the ones that I
feel contribute the most to developers, whether in terms of information or as a
nifty trick to tuck into your cap, get published in this column.
To submit a question, send mail to my email address - os2man@panix.com - and be
sure to grant permission to publish your question (those that forget will not
be considered for publication).
ΓòÉΓòÉΓòÉ 5.1.1.1. "Smart icons" ΓòÉΓòÉΓòÉ
Stefan Gruendal (Stefan_Gruendel@wue.maus.de) writes:
But to my question: How do I build my own "smart icons", i.e. bitmaps on
pushbuttons, that optically "move into the screen"? I didn't find any way to
achieve this with the Toolkit's Dialog Editor. But as mentioned above, I know
there's a way.
Starting with OS/2 2.0, a new button style was added - BS_ICON - which allows
you to do what you are trying to accomplish. It works, as far as I know, only
for pushbuttons, and the icon or bitmap is taken from the resource file, whose
resource id is specified in the pushbutton text.
For example:
In FOO.H:
#define IDBM_BUTTON 256
#define IDPB_BUTTON 257
In FOO.RC:
BITMAP IDBM_BUTTON BUTTON.BMP
In FOO.C:
sprintf(achText,"#%d",IDBM_BUTTON);
hwndButton=WinCreateWindow(hwndClient,
WC_BUTTON,
achText,
WS_VISIBLE|BS_PUSHBUTTON|BS_ICON,
10,
10,
32,
32,
hwndClient,
HWND_TOP,
IDPB_BUTTON,
NULL,
NULL);
The bitmap is stretched or compressed to fill the button.
ΓòÉΓòÉΓòÉ 5.1.1.2. Finding yourself ΓòÉΓòÉΓòÉ
Raja Thiagarajan (sthiagar@bronze.ucs.indiana.edu) writes:
Can a PM program tell if there's a previous instance of itself running? In
Win3.x (but apparently NOT Win32), there's a hPrevInst handle; is there an OS/2
2.0 equivalent? Basically, I'm thinking in terms of a program that would try
to borrow resources from a previous instance if a previous instance is running.
(Specifically, if my palette animation program gets started twice, the second
instance oughta share palettes with the first instance!)
What you're really asking is two questions:
1. How can I determine if a previous instance of my application is already
running?
2. How can I share resources between multiple instances of my application?
To answer your first question, you need to enumerate all of the main windows
present on the desktop, and figure out if any of them are yours. This is
achieved using the following code:
HWND queryAppInstance(PCHAR pchClassWanted)
{
HENUM heEnum;
HWND hwndTop;
HWND hwndClient;
CHAR achClass[256];
heEnum=WinBeginEnumWindows(HWND_DESKTOP);
hwndTop=WinGetNextWindow(heEnum);
while (hwndTop!=NULLHANDLE) {
hwndClient=WinWindowFromID(hwndTop,FID_CLIENT);
if (hwndClient!=NULLHANDLE) {
WinQueryClassName(hwndClient,sizeof(achClass),achClass);
if (strcmp(achClass,pchClassWanted)==0) {
WinEndEnumWindows(heEnum);
return hwndClient;
} /* endif */
} /* endif */
hwndTop=WinGetNextWindow(heEnum);
} /* endwhile */
WinEndEnumWindows(heEnum);
return NULLHANDLE;
}
To answer your second question, the only way that I know of to share resources
is to place them in a DLL. The procedure to do this is as follows:
o Create a dummy source file with an empty procedure in it.
o Compile the source file and link as a DLL.
o Add your resources to the DLL in the same manner as you would to an
executable.
Then, when your application starts, you simply call WinLoadLibrary (the
preferred way) or DosLoadModule to load the DLL. These functions return a
handle to the DLL which must then be used in any function which loads resources
(e.g. GpiLoadBitmap, WinLoadPointer, etc.).
Note that this procedure does not require knowing the window handle of any
previous instance of your application because OS/2 implements DLLs in a shared
fashion (which I suspect is one of the reasons they were created in the first
place). All you need to know is the name of the DLL. This technique can also
be used to share resources between different applications.
ΓòÉΓòÉΓòÉ 5.1.1.3. Device independence ΓòÉΓòÉΓòÉ
A reader who desires to remain anonymous writes:
Generally: My understanding was that OS/2 would handle printing for me. That
is to say that I wouldn't have to create separate printer drivers for every
printer under the sun (or any for that matter). Since I am creating an image
on the screen that is device independent (well, mostly anyway), is there an
easy way to get printer output?
PM achieves a level of device independence by defining a logical output space.
This logical output space is then bound to a physical output space, which
creates a mapping of logical characteristics to their physical counterparts.
The logical and physical output spaces are referred to as the presentation
space and the device context (HPS and HDC) and are bound to one another by
using either the GpiAssociate function or by specifying GPIA_ASSOC to the
GpiCreatePS function.
The easiest way to accomplish what you desire is to organize your drawing code
into one or more functions with a single entrypoint that accepts an HPS as a
parameter. Then, when you want to draw to the screen, you can call
WinGetPS/WinBeginPaint to get an HPS and call the function. When you want
hardcopy, you call DevOpenDC to get an HDC and GpiCreatePS to get an HPS and
call the function.
Note that to get hardcopy, you need to perform some additional setup to get
things to work properly. The two most important things are that you initialize
the DEVOPENSTRUC structure properly before calling DevOpenDC and that you send
the following escape codes (via DevEscape) at the following times:
hdcPrn=DevOpenDC(...);
hpsPrn=GpiCreatePS(...);
DevEscape(...,DEVESC_STARTDOC,...);
if (!doDraw(hpsPrn)) {
DevEscape(...,DEVESC_ABORTDOC,...);
} /* endif */
DevEscape(...,DEVESC_ENDDOC,...);
GpiDestroyPS(hpsPrn);
DevCloseDC(hdcPrn);
I'm not sure because I can't seem to find my copy anywhere, but I belive that
the book by Graham Winn (entitled something to the effect of "Building
applications using the OS/2 Presentation Manager") dedicates a chapter to the
nuances of printing.
ΓòÉΓòÉΓòÉ 5.2. Introduction to PM ΓòÉΓòÉΓòÉ
Introduction to
PM Programming
Part I
ΓòÉΓòÉΓòÉ 5.2.1. Overview ΓòÉΓòÉΓòÉ
Overview
It can be quite a daunting task to sit down and learn to write Presentation
Manager programs. There is so much ground to cover, but fear not - getting over
the initial learning curve is the hardest part. Once you have a grasp of the
basics, it is fairly easy to pick up on more advanced techniques.
Here I intend to provide a starting point for C programmers to get into PM, so
that you do have a solid grounding. Now unfortunately there are not many books
on PM which explain not just the what, but the why. That is, not just
presenting a syntax diagram and saying that (for example) WinBeginPaint is used
when you want to paint a window, but why you need it, when to use it (and not
WinGetPS ), and how to use it.
I will endeavour to explain the why, so you understand exactly what you are
doing, instead of blindly copying code from examples. You will see the code,
and at each step we will discuss the important sections, so you can see just
what each API does. You certainly don't have to remember every single API
though (there are hundreds). As you have a need to do something, you will learn
the new APIs required to achieve that.
Please remember that, as this is an introductory article, it is not presented
as being 100% technically correct and accurate. It is more to give the reader a
practical feel for what is going on. Technical references for technical
questions appear in the Bibliography. And remember, you can always read the
headers!
ΓòÉΓòÉΓòÉ 5.2.2. Scope ΓòÉΓòÉΓòÉ
Scope
I am assuming that you are a competent C programmer, and have a working
knowledge of OS/2 from a user's perspective. The sample code here was produced
with IBM's C Set/2, and is ANSI C. I do not assume anything but a familiarity
with GUIs in general.
ΓòÉΓòÉΓòÉ 5.2.3. Trademarks etc. ΓòÉΓòÉΓòÉ
Trademarks etc.
Please note that any trademarks referred to in this article remain the property
of their respective companies.
#include <std_disclaimer.h>
ΓòÉΓòÉΓòÉ 5.2.4. Presentation Manager ΓòÉΓòÉΓòÉ
Presentation Manager
Presentation Manager is the GUI which sits atop OS/2. It consists mainly of a
series of DLLs which contain all the controls, APIs and other stuff which
makes PM tick. The WPS is in effect a PM application itself, albeit with
certain other special properties. One good thing about PM is that because the
bulk of the code to handle the user interface is in DLLs, applications tend to
be smaller, as they contain largely just the code to handle the processing of
the information. This lets the programmer focus on the "guts" of the program,
rather than being concerned with the user interface. Also, PM provides a
consistent and intuitive interface for the user along standard design
guidelines. Of course, as you will no doubt see, there is still a great deal to
learn about PM!
ΓòÉΓòÉΓòÉ 5.2.4.1. The Event Driven Model ΓòÉΓòÉΓòÉ
The Event Driven Model
GUIs are modelled on an Event Driven paradigm. This is better understood when
contrasted with the sequential nature of conventional programs. Imagine the
following ficticious code which might appear in the menu of a program:
DisplayMenu();
while (!kbhit())
UpdateTime();
c=getch();
if (c==0x27)
Quit();
You can see that in the while loop, the program is constantly polling the
keyboard to check if a key has been pressed. This is a very inefficient
practice, as the machine spends most of its time in that loop, doing nothing.
In a multitasking environment, this waste of CPU time reduces system
throughput, as the CPU could be doing something more useful.
In OS/2 however, programs do not poll for input (whether from the mouse or
keyboard). All input is managed by PM itself. This means that when the user
clicks the mouse on a button for example, PM handles this event. It puts the
event into a queue for that application, and the function which handles this
event (the window procedure) only gets called when it has a message waiting for
it. As a result, the program only acts when it needs to, rather than constantly
checking if it actually has something to do.
So rather than following sequentially through the program, PM programs send,
receive and respond to events. This technique requires a shift in thinking, but
with practice it will become clear. It is arguably a more intuitive model for
programming, and it also promotes good technique.
ΓòÉΓòÉΓòÉ 5.2.4.1.1. Windows Everywhere ΓòÉΓòÉΓòÉ
Windows Everywhere
A Window is simply a rectangular area on the screen. A window can have certain
properties or flags, and PM is full of them. They may or may not have a frame
or border, a titlebar, or a menu.
You may only think of a window as being the Frame window of an application.
However buttons are windows too. So are dialog boxes, list boxes, scrollbars -
in fact each control in PM is a window. We will explore this further in a
future article, but for now just take it that a window is rectangle.
Don't worry too much if this sounds a bit vague - it will become clear once you
start making your own windows.
ΓòÉΓòÉΓòÉ 5.2.4.1.2. Getting the Message ΓòÉΓòÉΓòÉ
Getting the Message
The messages discussed in our talk of Events is simply a C struct, which looks
like this:
typedef struct _QMSG /* Queue Message */
{
HWND hwnd;
ULONG msg;
MPARAM mp1;
MPARAM mp2;
ULONG time;
POINTL ptl;
ULONG reserved;
} QMSG;
This struct is a key element in PM. The fields are explained thus:
Γûá hwnd - Window Handle - unique reference for a given window.
Γûá msg - A system- or user-defined message
Γûá mp1 and mp2 - Message parameters (meaning dependent upon the message). Can
be a number, a pointer, a handle, etc.
Γûá time - The time stamp when the event ocurred.
Γûá ptl - The (x,y) co-ordinates of the mouse.
Γûá reserved - Just what it says!
All messages have a unique number, and the system messages are all #defined in
the OS/2 headers. Some typical messages include:
ΓöîΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö¼ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÉ
ΓöéMessage ΓöéDescription Γöé
Γö£ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöñ
ΓöéWM_CHAR ΓöéWhen the user presses a key Γöé
Γö£ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöñ
ΓöéWM_CLOSE Γöéif the user chooses to Exit the program Γöé
Γö£ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö╝ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöñ
ΓöéWM_PAINT Γöéwhen an area of the screen needs to be Γöé
Γöé Γöédrawn Γöé
ΓööΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö┤ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÿ
You will not normally deal with the QMSG structure directly - your window
procedure will handle this, and deals only with the handle, the message and the
two parameters (mp1 and mp2).
ΓòÉΓòÉΓòÉ 5.2.4.1.3. Having a Handle on Things ΓòÉΓòÉΓòÉ
Having a Handle on Things
A handle is a unique number or reference for any given system object. The most
common handles refer to windows. An application can have any number of windows,
and handles are a convenient way to keep track of them. Handles are also used
internally by PM to manage its own information on the system.
ΓòÉΓòÉΓòÉ 5.2.4.1.4. Window Procedures ΓòÉΓòÉΓòÉ
Window Procedures
A Window Procedure is one of the most important parts of any PM application.
Its job is to handle all of the messages that get sent to its window. It
usually consists of a big switch statement, with a case for each message. You
can handle as many or as few messages as you wish, because (fortunately!) one
of the APIs in PM is a default window procedure. So any message you don't need
or want to handle, you pass on to WinDefWindowProc, which handles it in the
default manner.
Well, enough of this banter - let's do something useful and make a PM program.
ΓòÉΓòÉΓòÉ 5.2.5. Your first PM Program ΓòÉΓòÉΓòÉ
Your first PM Program
Let's start simply by getting a plain window on screen. I will show you the
code, followed by explanations, with the full source at the end of this
section.
/* Definitions */
#define INCL_WINFRAMEMGR
#define MYAPP "MyApp"
#define ID_MYWINDOW 42
If you look at the file OS2.H, you will see a whole bunch of #ifdefs, and
#includes. Because the header files are so large and numerous, and you don't
always want to include everything since you may not use all of it, all you need
do is #define which parts you need to include. There are certain things which
are included by default, so here we only neet to define INCL_WINFRAMEMGR, for
the definition of WM_ERASEBACKGROUND, as all the rest is already there. The
most important header is PMWIN.H, so take some time to browse through it as it
has some very important things in it.
/* Includes */
#include <os2.h>
Having defined which sections we need from the headers, all we need at the
moment is to include OS2.H.
/* Prototypes */
MRESULT EXPENTRY MyWinProc (HWND, ULONG, MPARAM, MPARAM);
We prototype our functions, like all good ANSI programmers!
/* Global Variables */
HAB hab;
HWND hwndClient,
hwndFrame;
The HAB stands for a Handle to the Anchor Block. As you know, a handle is just
a reference, but the anchor block is an internal data structure which PM
manages. The origin of the name is a little obscure. Anyway, each instance of
an application has an HAB. There are few times when you actually use it,
usually only during the initialization of your program. For now, let's say it
is like a handle to your application.
Now we have two other handles, this time handles to windows. Notice that we
have two handles. One for the client, one for the frame. This requires a little
more detail.
The Frame Window is the window from the edges of the resizeable frame border.
It contains the border, titlebar, minimize and maximize buttons, system menu,
and optionally a menu.
The Client Window is the area of whitespace bordered by the frame and the menu.
This is the space where information is displayed, and the user interacts with.
Take a look at the System Editor for example. The frame window encompasses the
entire application window. The client area is the area of white where the text
appears (the edit window). Having a separate client window makes it easier to
paint, scroll, and other such operations.
/* main function */
void main (void)
{
HMQ hmq;
QMSG qmsg;
ULONG flCreate;
hab = WinInitialize (0);
if (!hab)
return;
Here we launch straight into the main function. First we declare some local
variables. The HMQ is a handle to a message queue. When an application is
created, it needs a queue to keep the pending messages in, as they await
processing by the window procedure.
The QMSG is the data structure we looked at earlier (see Messages). This is
used in the main message loop below. The flCreate is used to keep the flags we
need to create our window.
The very first thing we do is call WinInitialize. This initializes certain
things in PM and gives us an HAB. If we could not get a HAB, something really
bad has gone wrong, so we exit immediately.
hmq = WinCreateMsgQueue (hab, 0);
Here we create our message queue with our HAB, and get a handle to it so we can
refer to it later.
WinRegisterClass(
hab,
MYAPP,
(PFNWP) MyWinProc,
CS_SIZEREDRAW,
0);
Because each application behaves differently and has their own window
procedure, PM needs to know certain things about your application. This is
called registering. You give it your HAB, a unique string (called the class
name) which identifies your program, a pointer to your window procedure (which
is why you cast it) and any particular flags you may require. These flags are
called Class Styles, and CS_SIZEREDRAW means we want our application to be
redrawn if it is resized. The last parameter allows us to reserve some space
within each window for instance-specific data, but we don't need this yet.
ΓòÉΓòÉΓòÉ 5.2.5.1. Creating the Window ΓòÉΓòÉΓòÉ
Creating the Window
flCreate = FCF_TITLEBAR | FCF_SYSMENU |
FCF_SIZEBORDER | FCF_MINMAX |
FCF_TASKLIST | FCF_SHELLPOSITION ;
hwndFrame = WinCreateStdWindow(
HWND_DESKTOP,
WS_VISIBLE,
&flCreate,
MYAPP,
"Sample PM App",
0L,
NULLHANDLE,
ID_MYWINDOW,
&hwndClient);
First we set up a variable which has ORed together a bunch of flags (called
Frame Control Flags) to tell PM what sort of window we want. They mean (in
order) we want a titlebar, a system menu, a resizeable border, both minimize
and maximize buttons, we want it to appear in the task list, and we don't care
what it's initial size or position is (let the shell decide).
Now comes the actual window creation. We specify the parent window (usually the
desktop, as is here). WS_VISIBLE means we want it to be visible, pass the frame
control flags, our class name (so it knows which window procedure to use), the
title of the window, then any other special flags. NULLHANDLE tells it to look
for Resources (such as icons, menus, dialogs, etc) in the executable itself. We
pass the ID of the window (to distinguish it from other windows, and to
associate resources with), and finally get a handle to our client window. I
hope you're keeping up so far, because we're almost there!
while (WinGetMsg (hab,&qmsg,(HWND)NULL,0,0))
WinDispatchMsg (hab,&qmsg);
This is the most crucial part of the program, and is really what gives it the
capability to respond to events. It is a constant loop which gets a message
from the queue and dispatches it to the window procedure. WinGetMsg will keep
going until it receives the WM_QUIT message. WinDispatchMsg actually gets PM to
call your window procedure, which is why all window procedures have the same
parameters (hwnd, msg, mp1, mp2). The other parameters in WinGetMsg allow you
to do fancy things which we won't get into now.
WinDestroyWindow(hwndFrame);
WinDestroyMsgQueue(hmq);
WinTerminate(hab);
}
This is just for cleanup - once the user exits the application, we clean up and
release the resources we used.
/* our window procedure */
MRESULT EXPENTRY MyWinProc (HWND hwnd, ULONG msg, MPARAM mp1, MPARAM mp2)
{
As I mentioned above, because PM calls your window procedure, you must have it
declared exactly like this. EXPENTRY tells the compiler that this function will
be in the list of exports (a list of " public" functions).
switch (msg)
{
case WM_ERASEBACKGROUND:
return (MRESULT) TRUE;
break;
Here we begin our amazing switch statement, which branches on the message. For
each case, we have a Window Message. This first one is PM asking if we want it
to erase the background for us. If we return true, it will do so. If we want to
paint the background (or client area) ourselves, we would return False.
case WM_CLOSE:
if (WinMessageBox(HWND_DESKTOP,HWND_DESKTOP,
"Are you sure you want to quit?","Sample",
0,MB_YESNO | MB_QUERY) == MBID_YES)
WinPostMsg(hwnd,WM_QUIT,0L,0L);
break;
This case is somewhat special. WM_CLOSE is sent when the user presses Alt-F4,
selects Close from the System menu, double clicks on the System menu, selects
Close from the Window List, or selects File|Exit from the menu (if there is
one). This gives us a chance to ask the user if they are sure they want to
quit, and make sure there are no changes to any files they might wish to save.
Here we use the WinMessageBox API, passing it the parent and owner, the message
we wish to display, the title of the message box, an ID (used for providing
Help), and some flags. The flags specify that we want Yes and No buttons, and a
Query icon. The call will return MBID_YES if they pressed the Yes button, in
which case we send our application the WM_QUIT message. This causes WinGetMsg
(in main) to return FALSE, the while loop to terminate, and finally cleanup
before exiting.
default:
return WinDefWindowProc (hwnd, msg, mp1, mp2);
}
return FALSE;
}
This part makes life easy for us. There are a myriad of other messages that our
application receives, and WinDefWindowProc will handle them all for us. This is
part of the beauty of the event-driven model - we grab only the messages which
interest us, and let PM do the rest. This gives us a standard look and feel,
with no extra work.
Now we have written a bare-bones PM application, follow the instructions in the
next section to run it.
ΓòÉΓòÉΓòÉ 5.2.5.2. Instructions ΓòÉΓòÉΓòÉ
Instructions
You can extract the source code to a file. To do this, for each file, go to the
code, press Ctrl-F. This will create a file called TEXT.TMP in the root
directory of the current drive. Go to an OS/2 Window and copy that file to a
working directory, renaming it to the respective name (which appears in the
title). You should be able to compile and run the program straight away. Just
issue the command:
[C:\WORK\PM]nmake /f step01.mak
And then run:
[C:\WORK\PM]step01
You should see a plain window with a titlebar. Closing the window will confirm
with a dialog. Once you have seen the program running, go back and re-read the
section explaining the code, so you can see just what is happening.
ΓòÉΓòÉΓòÉ 5.2.6. Sample Code ΓòÉΓòÉΓòÉ
Sample Code
[The code presented in this article is also available in 'intropm.zip'. -Ed.]
Γûá STEP01.C
Γûá STEP01.MAK
ΓòÉΓòÉΓòÉ 5.2.6.1. STEP01.C ΓòÉΓòÉΓòÉ
/* Definitions */
#define INCL_WINFRAMEMGR
#define MYAPP "MyApp"
#define ID_MYWINDOW 42
/* Includes */
#include <os2.h>
/* Prototypes */
MRESULT EXPENTRY MyWinProc (HWND, ULONG, MPARAM, MPARAM);
/* Global Variables */
HAB hab;
HWND hwndClient,
hwndFrame;
/* main function */
void main (void)
{
HMQ hmq;
QMSG qmsg;
ULONG flCreate;
hab = WinInitialize (0);
if (!hab)
return;
hmq = WinCreateMsgQueue (hab, 0);
WinRegisterClass(
hab,
MYAPP,
(PFNWP) MyWinProc,
CS_SIZEREDRAW,
0);
flCreate = FCF_TITLEBAR | FCF_SYSMENU |
FCF_SIZEBORDER | FCF_MINMAX |
FCF_TASKLIST | FCF_SHELLPOSITION ;
hwndFrame = WinCreateStdWindow(
HWND_DESKTOP,
WS_VISIBLE,
&flCreate,
MYAPP,
"Sample PM App",
0L,
NULLHANDLE,
ID_MYWINDOW,
&hwndClient);
while (WinGetMsg (hab,&qmsg,(HWND)NULL,0,0))
WinDispatchMsg (hab,&qmsg);
WinDestroyWindow(hwndFrame);
WinDestroyMsgQueue(hmq);
WinTerminate(hab);
}
/* our window procedure */
MRESULT EXPENTRY MyWinProc (HWND hwnd, ULONG msg, MPARAM mp1, MPARAM mp2)
{
switch (msg)
{
case WM_ERASEBACKGROUND:
return (MRESULT) TRUE;
break;
case WM_CLOSE:
if (WinMessageBox(HWND_DESKTOP,HWND_DESKTOP,
"Are you sure you want to quit?","Sample",
0,MB_YESNO | MB_QUERY) == MBID_YES)
WinPostMsg(hwnd,WM_QUIT,0L,0L);
break;
default:
return WinDefWindowProc (hwnd, msg, mp1, mp2);
}
return FALSE;
}
ΓòÉΓòÉΓòÉ 5.2.6.2. STEP01.MAK ΓòÉΓòÉΓòÉ
#--------------------------------------------------------------------
#
# Makefile for STEP01.MAK
#
#--------------------------------------------------------------------
CC = icc
LINK = link386
CFLAGS = /c /Ti
LFLAGS = /pmtype:pm /debug
┬╖c.obj:
$(CC) $(CFLAGS) $*.c
all: step01.exe
step01.exe: step01.obj
$(LINK) $(LFLAGS) step01,,,os2386,;
ΓòÉΓòÉΓòÉ 5.2.7. What Next? ΓòÉΓòÉΓòÉ
What Next?
Now that you have a basic PM application running, in the next issue we will add
a menu, some dialogs, and respond to some messages that do something useful. It
may seem like a lot of code to just get a plain window to appear, but we will
see how easy it is to add features next month.
I welcome any feedback on this article (netmail preferred) - any comments or
suggestions you may have, questions on this article, or things you would like
to see in a future article. I hope you have learned something!
ΓòÉΓòÉΓòÉ 5.2.8. Bibliography ΓòÉΓòÉΓòÉ
Bibliography
The following references were used in the preparation of this article:
Γûá OS/2 Version 2.0 - The Redbooks
Γûá OS/2 Version 2.0 Technical Library
ΓòÉΓòÉΓòÉ 5.3. Project Barrel ΓòÉΓòÉΓòÉ
Project Barrel
ΓòÉΓòÉΓòÉ 5.3.1. Project Ideas ΓòÉΓòÉΓòÉ
This column is a place where I intend to throw out ideas that I have come
across, either by thinking of them myself or by listening around on the net.
Some of these ideas are more practical than others; all of them should be worth
at least thinking about.
Feel free to attack anything you see here; similarly, feel free to send in your
ideas, in as much or as little detail as you wish.
Icon Management - A commercial application is available that does most of this
now. However, Windows users typically don't have to pay for this level of
utility and there's no reason we should either. Ideally, the program would
allows for easy movement into and out of zipfiles (or a similar compressed
storage archive) and allow for drag 'n drop attachment to programs. The ability
to view more than the 100 or so the drives object can view would also be a
plus.
Network software - Let's face it, the software that comes with TCP/IP 1.2.1 is
crap. Telnet only runs in 25 line mode, LaMail is a hideous ordeal to set up,
RN doesn't even work. There's a lot of opportunity here to write one of those
apps that people use every day of their lives.
Games - OS/2 Klondike is nice, but how about something that really shows off
what a 32 bit multithreaded program can do? For Presentation Manager, StarTrek
springs to mind as a possibility, as does an RPG a'la Castle of the Winds. In
fullscreen mode anything is possible. I would also like to see some multiplayer
network games - I just saw a friend playing Bolo on the Mac and didn't see any
reason why I shouldn't have something like it on my machine.
Emacs - Yes, I actually like the beast (I did this entire magazine with Emacs).
What I would like (and I have a vested interest in this) is an emacs major mode
for editing .IPF files. One that would match tags (in the same way that c-mode
matches parentheses) and provide for a consistent style with headers, comments,
etc.
Etc. - Ever look and see how HUGE the Windows areas are on most BBS's? There's
no reason why there shouldn't be that much stuff out there for OS/2 - the two
systems are similar in programming style and there's even an entire C compiler
for OS/2 free for the asking. Anytime you see a Windows program you like,
figure out what it is you like and port it!
ΓòÉΓòÉΓòÉ 6. Future Attractions ΓòÉΓòÉΓòÉ
Coming up in the future, we have:
o Introduction to PM Part 2
o Writing Installable File Systems
o Getting Started with IPF
o And much more!
ΓòÉΓòÉΓòÉ 7. Contributors to this issue ΓòÉΓòÉΓòÉ
o Steve Luzynski
o David Charlap
o Larry Salomon
o Raja Thiagarajan
o Gavin R Baker
o Steve Lacy
ΓòÉΓòÉΓòÉ 7.1. Steve Luzynski ΓòÉΓòÉΓòÉ
Steve Luzynski is the editor and creator of this magazine. He is currently a
Computer Engineering student at Case Western Reserve University in Cleveland,
OH, where he spends a lot of time being cold. Steve has yet to release any
programs for OS/2 as a direct result of: 1) editing this magazine; and 2)
having to waste time going to class when there are programs to write. Steve can
by reached via e-mail at 'sal8@po.cwru.edu' or on Compuserve at 72677,2140.
ΓòÉΓòÉΓòÉ 7.2. David Charlap ΓòÉΓòÉΓòÉ
David Charlap is 23 years old and is a full-time student at New Jersey
Institute Of Technology. He is pursuing a Masters degree in computer science.
He learned OS/2 programming while working for a software development company
and is currently writing small OS/2 applications for shareware consumption.
MineSweeper is his first application that has been released to the general
public. David may be reached by e-mail at:
dic5340@hertz.njit.edu
He may also be reached at the following address:
David Charlap
8 Snow Ridge
Denville, NJ 07834
USA
ΓòÉΓòÉΓòÉ 7.3. Larry Salomon ΓòÉΓòÉΓòÉ
Larry Salomon wrote his first Presentation Manager application for OS/2 version
1.1 in 1989. Since that time, he has written numerous VIO and PM applications,
including the Scramble applet included with OS/2 and the I-Brow/Magnify/Screen
Capture trio included with the IBM Professional Developers Kit CD-ROM currently
being distributed by IBM. Currently, he works for International Masters
Publishers in Stamford, Connecticut and resides in Bellerose, New York with his
wife Lisa.
ΓòÉΓòÉΓòÉ 7.4. Raja Thiagarajan ΓòÉΓòÉΓòÉ
Raja Thiagarajan was born in Madras, India in 1965, but moved to Bloomington,
Indiana by the age of two in order to get a 25-year headstart on writing "An
Unofficial Guide to the Palette Manager." Raja has college degrees in
Astrophysics and Computer Science, but will never understand NASA's priorities,
the C preprocessor, Scheme's call/cc, or Prolog's cut operator. Raja would love
to hear from you; send e-mail to sthiagar@bronze.ucs.indiana.edu
ΓòÉΓòÉΓòÉ 7.5. Gavin R Baker ΓòÉΓòÉΓòÉ
Gavin R Baker
Gavin R Baker is the man behind ThinkSoft, a consulting firm based in
Melbourne Australia which specialises in developing custom software. He has
experience in Assembler, Pascal, C, C++, (and a lot of other languages), and
has worked with Unix, DOS, Windows, OS/2, VMS and Pick operating systems. He is
an active member of Team OS/2. When he isn't programming, he is also a
musician, an actor, and wastes lots of time reading Net News. He can be
contacted thusly:
net: demogrb@lust.latrobe.edu.au
bix: gbaker
cis: 100026,270
ΓòÉΓòÉΓòÉ 7.6. Steve Lacy ΓòÉΓòÉΓòÉ
Steve Lacy is a third year computer science student at Carnegie Mellon
University, and is currently overoccupied with school, and his job as an
undergraduate research programmer for the Digital Mapping Lab at CMU. You can
reach Steve via e-mail at sl31@andrew.cmu.edu. He would be glad to hear any and
all of your comments about the previous article. Steve also spends his free
time collecting CD's, and unfortunately spends far too much money in the
process. Remember, diversity is knowledge, and personal gratification
overrides the need for food in many cases.....
ΓòÉΓòÉΓòÉ <hidden> ΓòÉΓòÉΓòÉ
Graphical User Interface
An interface usually comprising multiple overlapping windows, icons, pull-down
menus, and the mouse as a pointing device. It is recognised that the GUI was
invented by Xerox at PARC (Paolo Alto Research Centre) in the late 60's (??).
ΓòÉΓòÉΓòÉ <hidden> ΓòÉΓòÉΓòÉ
Dynamic Link Library
An executable module which can be shared between multiple applications,
containing code and/or resources. It is loaded dynamically at run-time rather
than being statically linked at compile time. This reduces memory requirements
and allows code independance and code sharing.
ΓòÉΓòÉΓòÉ <hidden> ΓòÉΓòÉΓòÉ
Multitasking
Where the CPU shares itself between more than one task, by giving each task a
slice of processing time.
ΓòÉΓòÉΓòÉ <hidden> ΓòÉΓòÉΓòÉ
Workplace Shell
The Workplace Shell is the Object Oriented shell for OS/2.
ΓòÉΓòÉΓòÉ <hidden> ΓòÉΓòÉΓòÉ
Application Programming Interface
API is a general term for a set of functions which provide a standard set of
services to an application. In this article, we refer to the OS/2 APIs as the
calls to OS/2 and PM which make up the system services in the kernel and the
GUI.
ΓòÉΓòÉΓòÉ <hidden> ΓòÉΓòÉΓòÉ
Parent
The parent-child relationship establishes a hierarchy of windows in the system.
Child windows are clipped to their parent windows; that is no child window will
apear outside the boundary of its parent.
ΓòÉΓòÉΓòÉ <hidden> ΓòÉΓòÉΓòÉ
Owner
The owner of a window will receive messages from the windows it owns.